Skip to content
This repository has been archived by the owner on Sep 11, 2019. It is now read-only.

Commit

Permalink
netfilter: nf_tables: unbind set in rule from commit path
Browse files Browse the repository at this point in the history
Anonymous sets that are bound to rules from the same transaction trigger
a kernel splat from the abort path due to double set list removal and
double free.

This patch updates the logic to search for the transaction that is
responsible for creating the set and disable the set list removal and
release, given the rule is now responsible for this. Lookup is reverse
since the transaction that adds the set is likely to be at the tail of
the list.

Moreover, this patch adds the unbind step to deliver the event from the
commit path.  This should not be done from the worker thread, since we
have no guarantees of in-order delivery to the listener.

This patch removes the assumption that both activate and deactivate
callbacks need to be provided.

Fixes: cd5125d ("netfilter: nf_tables: split set destruction in deactivate and destroy phase")
Reported-by: Mikhail Morfikov <mmorfikov@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
  • Loading branch information
ummakynes committed Feb 4, 2019
1 parent 4e35c1c commit f6ac858
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 83 deletions.
17 changes: 13 additions & 4 deletions include/net/netfilter/nf_tables.h
Original file line number Diff line number Diff line change
Expand Up @@ -469,9 +469,7 @@ struct nft_set_binding {
int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding);
void nf_tables_unbind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding);
void nf_tables_rebind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding);
struct nft_set_binding *binding, bool commit);
void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set);

/**
Expand Down Expand Up @@ -721,6 +719,13 @@ struct nft_expr_type {
#define NFT_EXPR_STATEFUL 0x1
#define NFT_EXPR_GC 0x2

enum nft_trans_phase {
NFT_TRANS_PREPARE,
NFT_TRANS_ABORT,
NFT_TRANS_COMMIT,
NFT_TRANS_RELEASE
};

/**
* struct nft_expr_ops - nf_tables expression operations
*
Expand Down Expand Up @@ -750,7 +755,8 @@ struct nft_expr_ops {
void (*activate)(const struct nft_ctx *ctx,
const struct nft_expr *expr);
void (*deactivate)(const struct nft_ctx *ctx,
const struct nft_expr *expr);
const struct nft_expr *expr,
enum nft_trans_phase phase);
void (*destroy)(const struct nft_ctx *ctx,
const struct nft_expr *expr);
void (*destroy_clone)(const struct nft_ctx *ctx,
Expand Down Expand Up @@ -1323,12 +1329,15 @@ struct nft_trans_rule {
struct nft_trans_set {
struct nft_set *set;
u32 set_id;
bool bound;
};

#define nft_trans_set(trans) \
(((struct nft_trans_set *)trans->data)->set)
#define nft_trans_set_id(trans) \
(((struct nft_trans_set *)trans->data)->set_id)
#define nft_trans_set_bound(trans) \
(((struct nft_trans_set *)trans->data)->bound)

struct nft_trans_chain {
bool update;
Expand Down
85 changes: 41 additions & 44 deletions net/netfilter/nf_tables_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,23 @@ static void nft_trans_destroy(struct nft_trans *trans)
kfree(trans);
}

static void nft_set_trans_bind(const struct nft_ctx *ctx, struct nft_set *set)
{
struct net *net = ctx->net;
struct nft_trans *trans;

if (!nft_set_is_anonymous(set))
return;

list_for_each_entry_reverse(trans, &net->nft.commit_list, list) {
if (trans->msg_type == NFT_MSG_NEWSET &&
nft_trans_set(trans) == set) {
nft_trans_set_bound(trans) = true;
break;
}
}
}

static int nf_tables_register_hook(struct net *net,
const struct nft_table *table,
struct nft_chain *chain)
Expand Down Expand Up @@ -211,18 +228,6 @@ static int nft_delchain(struct nft_ctx *ctx)
return err;
}

/* either expr ops provide both activate/deactivate, or neither */
static bool nft_expr_check_ops(const struct nft_expr_ops *ops)
{
if (!ops)
return true;

if (WARN_ON_ONCE((!ops->activate ^ !ops->deactivate)))
return false;

return true;
}

static void nft_rule_expr_activate(const struct nft_ctx *ctx,
struct nft_rule *rule)
{
Expand All @@ -238,14 +243,15 @@ static void nft_rule_expr_activate(const struct nft_ctx *ctx,
}

static void nft_rule_expr_deactivate(const struct nft_ctx *ctx,
struct nft_rule *rule)
struct nft_rule *rule,
enum nft_trans_phase phase)
{
struct nft_expr *expr;

expr = nft_expr_first(rule);
while (expr != nft_expr_last(rule) && expr->ops) {
if (expr->ops->deactivate)
expr->ops->deactivate(ctx, expr);
expr->ops->deactivate(ctx, expr, phase);

expr = nft_expr_next(expr);
}
Expand Down Expand Up @@ -296,7 +302,7 @@ static int nft_delrule(struct nft_ctx *ctx, struct nft_rule *rule)
nft_trans_destroy(trans);
return err;
}
nft_rule_expr_deactivate(ctx, rule);
nft_rule_expr_deactivate(ctx, rule, NFT_TRANS_PREPARE);

return 0;
}
Expand Down Expand Up @@ -1929,9 +1935,6 @@ static int nf_tables_delchain(struct net *net, struct sock *nlsk,
*/
int nft_register_expr(struct nft_expr_type *type)
{
if (!nft_expr_check_ops(type->ops))
return -EINVAL;

nfnl_lock(NFNL_SUBSYS_NFTABLES);
if (type->family == NFPROTO_UNSPEC)
list_add_tail_rcu(&type->list, &nf_tables_expressions);
Expand Down Expand Up @@ -2079,10 +2082,6 @@ static int nf_tables_expr_parse(const struct nft_ctx *ctx,
err = PTR_ERR(ops);
goto err1;
}
if (!nft_expr_check_ops(ops)) {
err = -EINVAL;
goto err1;
}
} else
ops = type->ops;

Expand Down Expand Up @@ -2511,7 +2510,7 @@ static void nf_tables_rule_destroy(const struct nft_ctx *ctx,
static void nf_tables_rule_release(const struct nft_ctx *ctx,
struct nft_rule *rule)
{
nft_rule_expr_deactivate(ctx, rule);
nft_rule_expr_deactivate(ctx, rule, NFT_TRANS_RELEASE);
nf_tables_rule_destroy(ctx, rule);
}

Expand Down Expand Up @@ -3708,39 +3707,30 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
bind:
binding->chain = ctx->chain;
list_add_tail_rcu(&binding->list, &set->bindings);
nft_set_trans_bind(ctx, set);

return 0;
}
EXPORT_SYMBOL_GPL(nf_tables_bind_set);

void nf_tables_rebind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding)
{
if (list_empty(&set->bindings) && nft_set_is_anonymous(set) &&
nft_is_active(ctx->net, set))
list_add_tail_rcu(&set->list, &ctx->table->sets);

list_add_tail_rcu(&binding->list, &set->bindings);
}
EXPORT_SYMBOL_GPL(nf_tables_rebind_set);

void nf_tables_unbind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding)
struct nft_set_binding *binding, bool event)
{
list_del_rcu(&binding->list);

if (list_empty(&set->bindings) && nft_set_is_anonymous(set) &&
nft_is_active(ctx->net, set))
if (list_empty(&set->bindings) && nft_set_is_anonymous(set)) {
list_del_rcu(&set->list);
if (event)
nf_tables_set_notify(ctx, set, NFT_MSG_DELSET,
GFP_KERNEL);
}
}
EXPORT_SYMBOL_GPL(nf_tables_unbind_set);

void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set)
{
if (list_empty(&set->bindings) && nft_set_is_anonymous(set) &&
nft_is_active(ctx->net, set)) {
nf_tables_set_notify(ctx, set, NFT_MSG_DELSET, GFP_ATOMIC);
if (list_empty(&set->bindings) && nft_set_is_anonymous(set))
nft_set_destroy(set);
}
}
EXPORT_SYMBOL_GPL(nf_tables_destroy_set);

Expand Down Expand Up @@ -6535,6 +6525,9 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
nf_tables_rule_notify(&trans->ctx,
nft_trans_rule(trans),
NFT_MSG_DELRULE);
nft_rule_expr_deactivate(&trans->ctx,
nft_trans_rule(trans),
NFT_TRANS_COMMIT);
break;
case NFT_MSG_NEWSET:
nft_clear(net, nft_trans_set(trans));
Expand Down Expand Up @@ -6621,7 +6614,8 @@ static void nf_tables_abort_release(struct nft_trans *trans)
nf_tables_rule_destroy(&trans->ctx, nft_trans_rule(trans));
break;
case NFT_MSG_NEWSET:
nft_set_destroy(nft_trans_set(trans));
if (!nft_trans_set_bound(trans))
nft_set_destroy(nft_trans_set(trans));
break;
case NFT_MSG_NEWSETELEM:
nft_set_elem_destroy(nft_trans_elem_set(trans),
Expand Down Expand Up @@ -6682,7 +6676,9 @@ static int __nf_tables_abort(struct net *net)
case NFT_MSG_NEWRULE:
trans->ctx.chain->use--;
list_del_rcu(&nft_trans_rule(trans)->list);
nft_rule_expr_deactivate(&trans->ctx, nft_trans_rule(trans));
nft_rule_expr_deactivate(&trans->ctx,
nft_trans_rule(trans),
NFT_TRANS_ABORT);
break;
case NFT_MSG_DELRULE:
trans->ctx.chain->use++;
Expand All @@ -6692,7 +6688,8 @@ static int __nf_tables_abort(struct net *net)
break;
case NFT_MSG_NEWSET:
trans->ctx.table->use--;
list_del_rcu(&nft_trans_set(trans)->list);
if (!nft_trans_set_bound(trans))
list_del_rcu(&nft_trans_set(trans)->list);
break;
case NFT_MSG_DELSET:
trans->ctx.table->use++;
Expand Down
6 changes: 5 additions & 1 deletion net/netfilter/nft_compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -587,10 +587,14 @@ static void nft_compat_activate_tg(const struct nft_ctx *ctx,
}

static void nft_compat_deactivate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
const struct nft_expr *expr,
enum nft_trans_phase phase)
{
struct nft_xt *xt = container_of(expr->ops, struct nft_xt, ops);

if (phase == NFT_TRANS_COMMIT)
return;

if (--xt->listcnt == 0)
list_del_init(&xt->head);
}
Expand Down
18 changes: 7 additions & 11 deletions net/netfilter/nft_dynset.c
Original file line number Diff line number Diff line change
Expand Up @@ -235,20 +235,17 @@ static int nft_dynset_init(const struct nft_ctx *ctx,
return err;
}

static void nft_dynset_activate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
struct nft_dynset *priv = nft_expr_priv(expr);

nf_tables_rebind_set(ctx, priv->set, &priv->binding);
}

static void nft_dynset_deactivate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
const struct nft_expr *expr,
enum nft_trans_phase phase)
{
struct nft_dynset *priv = nft_expr_priv(expr);

nf_tables_unbind_set(ctx, priv->set, &priv->binding);
if (phase == NFT_TRANS_PREPARE)
return;

nf_tables_unbind_set(ctx, priv->set, &priv->binding,
phase == NFT_TRANS_COMMIT);
}

static void nft_dynset_destroy(const struct nft_ctx *ctx,
Expand Down Expand Up @@ -296,7 +293,6 @@ static const struct nft_expr_ops nft_dynset_ops = {
.eval = nft_dynset_eval,
.init = nft_dynset_init,
.destroy = nft_dynset_destroy,
.activate = nft_dynset_activate,
.deactivate = nft_dynset_deactivate,
.dump = nft_dynset_dump,
};
Expand Down
6 changes: 5 additions & 1 deletion net/netfilter/nft_immediate.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,14 @@ static void nft_immediate_activate(const struct nft_ctx *ctx,
}

static void nft_immediate_deactivate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
const struct nft_expr *expr,
enum nft_trans_phase phase)
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);

if (phase == NFT_TRANS_COMMIT)
return;

return nft_data_release(&priv->data, nft_dreg_to_type(priv->dreg));
}

Expand Down
18 changes: 7 additions & 11 deletions net/netfilter/nft_lookup.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,17 @@ static int nft_lookup_init(const struct nft_ctx *ctx,
return 0;
}

static void nft_lookup_activate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
struct nft_lookup *priv = nft_expr_priv(expr);

nf_tables_rebind_set(ctx, priv->set, &priv->binding);
}

static void nft_lookup_deactivate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
const struct nft_expr *expr,
enum nft_trans_phase phase)
{
struct nft_lookup *priv = nft_expr_priv(expr);

nf_tables_unbind_set(ctx, priv->set, &priv->binding);
if (phase == NFT_TRANS_PREPARE)
return;

nf_tables_unbind_set(ctx, priv->set, &priv->binding,
phase == NFT_TRANS_COMMIT);
}

static void nft_lookup_destroy(const struct nft_ctx *ctx,
Expand Down Expand Up @@ -225,7 +222,6 @@ static const struct nft_expr_ops nft_lookup_ops = {
.size = NFT_EXPR_SIZE(sizeof(struct nft_lookup)),
.eval = nft_lookup_eval,
.init = nft_lookup_init,
.activate = nft_lookup_activate,
.deactivate = nft_lookup_deactivate,
.destroy = nft_lookup_destroy,
.dump = nft_lookup_dump,
Expand Down
18 changes: 7 additions & 11 deletions net/netfilter/nft_objref.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,20 +155,17 @@ static int nft_objref_map_dump(struct sk_buff *skb, const struct nft_expr *expr)
return -1;
}

static void nft_objref_map_activate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
struct nft_objref_map *priv = nft_expr_priv(expr);

nf_tables_rebind_set(ctx, priv->set, &priv->binding);
}

static void nft_objref_map_deactivate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
const struct nft_expr *expr,
enum nft_trans_phase phase)
{
struct nft_objref_map *priv = nft_expr_priv(expr);

nf_tables_unbind_set(ctx, priv->set, &priv->binding);
if (phase == NFT_TRANS_PREPARE)
return;

nf_tables_unbind_set(ctx, priv->set, &priv->binding,
phase == NFT_TRANS_COMMIT);
}

static void nft_objref_map_destroy(const struct nft_ctx *ctx,
Expand All @@ -185,7 +182,6 @@ static const struct nft_expr_ops nft_objref_map_ops = {
.size = NFT_EXPR_SIZE(sizeof(struct nft_objref_map)),
.eval = nft_objref_map_eval,
.init = nft_objref_map_init,
.activate = nft_objref_map_activate,
.deactivate = nft_objref_map_deactivate,
.destroy = nft_objref_map_destroy,
.dump = nft_objref_map_dump,
Expand Down

0 comments on commit f6ac858

Please sign in to comment.