On Wed, May 14, 2025 at 11:42:15PM +0200, Pablo Neira Ayuso wrote: > This patch uses the new binding infrastructure to link chains and sets: > > - Chain-to-chain and chain-to-set bindings already exists, so it is > relatively trivial to add the code to add, deactivate, activate and > delete the bindings. > > - Set-to-chain is a new binding required by verdict maps, this requires > to release this new type of binding from the commit/abort path. > Special cases are the deletion of sets with elements and netns/netlink > release path, that require the removal of all set-to-chain bindings. > Another special case is anonymous sets, that also require to delete > all set-to-chain bindings when unbound. > > The binding graph is not yet used to validate rulesets, this is enabled > in the follow up patch. > > Signed-off-by: Pablo Neira Ayuso <pablo@xxxxxxxxxxxxx> > --- > include/net/netfilter/nf_tables.h | 2 +- > net/netfilter/nf_tables_api.c | 149 ++++++++++++++++++++++++++++-- > net/netfilter/nft_immediate.c | 25 ++++- > 3 files changed, 164 insertions(+), 12 deletions(-) > > diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h > index 807097746d24..c33820fc1abd 100644 > --- a/include/net/netfilter/nf_tables.h > +++ b/include/net/netfilter/nf_tables.h > @@ -1929,7 +1929,7 @@ struct nft_trans_gc *nft_trans_gc_catchall_async(struct nft_trans_gc *gc, > struct nft_trans_gc *nft_trans_gc_catchall_sync(struct nft_trans_gc *gc); > > void nft_setelem_data_deactivate(const struct net *net, > - const struct nft_set *set, > + struct nft_set *set, > struct nft_elem_priv *elem_priv); > > int __init nft_chain_filter_init(void); > diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c > index 463ee7b4ec19..e92cccc834d9 100644 > --- a/net/netfilter/nf_tables_api.c > +++ b/net/netfilter/nf_tables_api.c > @@ -6077,6 +6077,7 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, > { > struct nft_set_binding *i; > struct nft_set_iter iter; > + int err; > > if (!list_empty(&set->bindings) && nft_set_is_anonymous(set)) > return -EBUSY; > @@ -6109,6 +6110,12 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, > if (!nft_use_inc(&set->use)) > return -EMFILE; > > + err = nft_add_chain_set_binding(ctx->chain, set); > + if (err < 0) { > + nft_use_dec_restore(&set->use); > + return err; > + } > + > binding->chain = ctx->chain; > list_add_tail_rcu(&binding->list, &set->bindings); > nft_set_trans_bind(ctx, set); > @@ -6117,14 +6124,27 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, > } > EXPORT_SYMBOL_GPL(nf_tables_bind_set); > > +static void nft_del_set_chain_binding_all(struct nft_set *set) > +{ > + struct nft_binding *binding, *next; > + struct nft_chain *chain; > + > + list_for_each_entry_safe(binding, next, &set->binding_list, list) { > + chain = (struct nft_chain *)binding->to.chain; > + nft_del_set_chain_binding(set, chain); > + } > +} > + > static void nf_tables_unbind_set(const struct nft_ctx *ctx, struct nft_set *set, > struct nft_set_binding *binding, bool event) > { > list_del_rcu(&binding->list); > + nft_del_chain_set_binding(ctx->chain, set); > > if (list_empty(&set->bindings) && nft_set_is_anonymous(set)) { > list_del_rcu(&set->list); > set->dead = 1; > + nft_del_set_chain_binding_all(set); > if (event) > nf_tables_set_notify(ctx, set, NFT_MSG_DELSET, > GFP_KERNEL); > @@ -6132,7 +6152,7 @@ static void nf_tables_unbind_set(const struct nft_ctx *ctx, struct nft_set *set, > } > > static void nft_setelem_data_activate(const struct net *net, > - const struct nft_set *set, > + struct nft_set *set, > struct nft_elem_priv *elem_priv); > > static int nft_mapelem_activate(const struct nft_ctx *ctx, > @@ -6193,6 +6213,7 @@ void nf_tables_activate_set(const struct nft_ctx *ctx, struct nft_set *set) > nft_clear(ctx->net, set); > } > > + nft_activate_chain_set_binding(ctx->chain, set); > nft_use_inc_restore(&set->use); > } > EXPORT_SYMBOL_GPL(nf_tables_activate_set); > @@ -6211,6 +6232,8 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, > else > list_del_rcu(&binding->list); > > + nft_deactivate_chain_set_binding(ctx->chain, set); > + nft_del_chain_set_binding(ctx->chain, set); > nft_use_dec(&set->use); > break; > case NFT_TRANS_PREPARE: > @@ -6220,6 +6243,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, > > nft_deactivate_next(ctx->net, set); > } > + nft_deactivate_chain_set_binding(ctx->chain, set); > nft_use_dec(&set->use); > return; > case NFT_TRANS_ABORT: > @@ -6228,6 +6252,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, > set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) > nft_map_deactivate(ctx, set); > > + nft_deactivate_chain_set_binding(ctx->chain, set); > nft_use_dec(&set->use); > fallthrough; > default: > @@ -7124,6 +7149,7 @@ static void __nft_set_elem_destroy(const struct nft_ctx *ctx, > nft_data_release(nft_set_ext_key(ext), NFT_DATA_VALUE); > if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) > nft_data_release(nft_set_ext_data(ext), set->dtype); > + // XXX gc Leftover? Cheers, Phil > if (destroy_expr && nft_set_ext_exists(ext, NFT_SET_EXT_EXPRESSIONS)) > nft_set_elem_expr_destroy(ctx, nft_set_ext_expr(ext)); > if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) > @@ -7454,6 +7480,27 @@ static void nft_setelem_remove(const struct net *net, > set->ops->remove(net, set, elem_priv); > } > > +static void nft_setelem_data_binding_remove(struct nft_set *set, > + struct nft_elem_priv *elem_priv) > +{ > + struct nft_set_ext *ext; > + > + ext = nft_set_elem_ext(set, elem_priv); > + if (set->dtype == NFT_DATA_VERDICT && > + nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) { > + struct nft_data *data = nft_set_ext_data(ext); > + struct nft_chain *chain; > + > + switch (data->verdict.code) { > + case NFT_JUMP: > + case NFT_GOTO: > + chain = data->verdict.chain; > + nft_del_set_chain_binding(set, chain); > + break; > + } > + } > +} > + > static void nft_trans_elems_remove(const struct nft_ctx *ctx, > const struct nft_trans_elem *te) > { > @@ -7471,6 +7518,8 @@ static void nft_trans_elems_remove(const struct nft_ctx *ctx, > atomic_dec(&te->set->nelems); > te->set->ndeact--; > } > + > + nft_setelem_data_binding_remove(te->set, te->elems[i].priv); > } > } > > @@ -7768,9 +7817,17 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, > nft_validate_chain_need(ctx, elem.data.val.verdict.chain); > } > > + if (desc.type == NFT_DATA_VERDICT && > + (elem.data.val.verdict.code == NFT_GOTO || > + elem.data.val.verdict.code == NFT_JUMP)) { > + err = nft_add_set_chain_binding(set, elem.data.val.verdict.chain); > + if (err < 0) > + goto err_parse_data; > + } > + > err = nft_set_ext_add_length(&tmpl, NFT_SET_EXT_DATA, desc.len); > if (err < 0) > - goto err_parse_data; > + goto err_binding; > } > > /* The full maximum length of userdata can exceed the maximum > @@ -7784,7 +7841,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, > err = nft_set_ext_add_length(&tmpl, NFT_SET_EXT_USERDATA, > ulen); > if (err < 0) > - goto err_parse_data; > + goto err_binding; > } > } > > @@ -7793,7 +7850,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, > timeout, expiration, GFP_KERNEL_ACCOUNT); > if (IS_ERR(elem.priv)) { > err = PTR_ERR(elem.priv); > - goto err_parse_data; > + goto err_binding; > } > > ext = nft_set_elem_ext(set, elem.priv); > @@ -7903,6 +7960,15 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, > kfree(trans); > err_elem_free: > nf_tables_set_elem_destroy(ctx, set, elem.priv); > +err_binding: > + if (nla[NFTA_SET_ELEM_DATA] != NULL) { > + if (desc.type == NFT_DATA_VERDICT && > + (elem.data.val.verdict.code == NFT_GOTO || > + elem.data.val.verdict.code == NFT_JUMP)) { > + nft_deactivate_set_chain_binding(set, elem.data.val.verdict.chain); > + nft_del_set_chain_binding(set, elem.data.val.verdict.chain); > + } > + } > err_parse_data: > if (nla[NFTA_SET_ELEM_DATA] != NULL) > nft_data_release(&elem.data.val, desc.type); > @@ -8007,30 +8073,90 @@ static int nft_setelem_active_next(const struct net *net, > return nft_set_elem_active(ext, genmask); > } > > +static void nft_setelem_data_hold(const struct net *net, > + const struct nft_set *set, > + const struct nft_set_ext *ext) > +{ > + if (set->dtype == NFT_DATA_VERDICT) { > + struct nft_data *data = nft_set_ext_data(ext); > + struct nft_chain *chain; > + > + switch (data->verdict.code) { > + case NFT_JUMP: > + case NFT_GOTO: > + chain = data->verdict.chain; > + nft_activate_set_chain_binding((struct nft_set *)set, chain); > + nft_use_inc_restore(&chain->use); > + break; > + } > + } > +} > + > static void nft_setelem_data_activate(const struct net *net, > - const struct nft_set *set, > + struct nft_set *set, > struct nft_elem_priv *elem_priv) > { > const struct nft_set_ext *ext = nft_set_elem_ext(set, elem_priv); > > if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) > - nft_data_hold(nft_set_ext_data(ext), set->dtype); > + nft_setelem_data_hold(net, set, ext); > if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) > nft_use_inc_restore(&(*nft_set_ext_obj(ext))->use); > } > > +static void nft_setelem_data_release(struct nft_set *set, > + const struct nft_set_ext *ext) > +{ > + struct nft_data *data = nft_set_ext_data(ext); > + > + if (set->dtype == NFT_DATA_VERDICT) { > + struct nft_chain *chain; > + > + switch (data->verdict.code) { > + case NFT_JUMP: > + case NFT_GOTO: > + chain = data->verdict.chain; > + nft_deactivate_set_chain_binding(set, chain); > + nft_use_dec(&chain->use); > + break; > + } > + } > +} > + > void nft_setelem_data_deactivate(const struct net *net, > - const struct nft_set *set, > + struct nft_set *set, > struct nft_elem_priv *elem_priv) > { > const struct nft_set_ext *ext = nft_set_elem_ext(set, elem_priv); > > if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) > - nft_data_release(nft_set_ext_data(ext), set->dtype); > + nft_setelem_data_release(set, ext); > if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) > nft_use_dec(&(*nft_set_ext_obj(ext))->use); > } > > +static void nft_setelem_data_abort(struct nft_set *set, > + struct nft_elem_priv *elem_priv) > +{ > + struct nft_set_ext *ext; > + > + ext = nft_set_elem_ext(set, elem_priv); > + if (set->dtype == NFT_DATA_VERDICT && > + nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) { > + struct nft_data *data = nft_set_ext_data(ext); > + struct nft_chain *chain; > + > + switch (data->verdict.code) { > + case NFT_JUMP: > + case NFT_GOTO: > + chain = data->verdict.chain; > + nft_deactivate_set_chain_binding(set, chain); > + nft_del_set_chain_binding(set, chain); > + break; > + } > + } > +} > + > /* similar to nft_trans_elems_remove, but called from abort path to undo newsetelem. > * No notifications and no ndeact changes. > * > @@ -8057,6 +8183,7 @@ static bool nft_trans_elems_new_abort(const struct nft_ctx *ctx, > if (!nft_setelem_is_catchall(te->set, te->elems[i].priv)) > atomic_dec(&te->set->nelems); > > + nft_setelem_data_abort(te->set, te->elems[i].priv); > removed = true; > } > > @@ -11290,6 +11417,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) > case NFT_MSG_DESTROYSET: > nft_trans_set(trans)->dead = 1; > list_del_rcu(&nft_trans_set(trans)->list); > + nft_del_set_chain_binding_all(nft_trans_set(trans)); > nf_tables_set_notify(&ctx, nft_trans_set(trans), > trans->msg_type, GFP_KERNEL); > break; > @@ -12258,9 +12386,10 @@ static void __nft_release_table(struct net *net, struct nft_table *table) > list_for_each_entry_safe(set, ns, &table->sets, list) { > list_del(&set->list); > nft_use_dec(&table->use); > - if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) > + if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) { > nft_map_deactivate(&ctx, set); > - > + nft_del_set_chain_binding_all(set); > + } > nft_set_destroy(&ctx, set); > } > list_for_each_entry_safe(obj, ne, &table->objects, list) { > diff --git a/net/netfilter/nft_immediate.c b/net/netfilter/nft_immediate.c > index 02ee5fb69871..0b886725ed37 100644 > --- a/net/netfilter/nft_immediate.c > +++ b/net/netfilter/nft_immediate.c > @@ -76,9 +76,16 @@ static int nft_immediate_init(const struct nft_ctx *ctx, > switch (priv->data.verdict.code) { > case NFT_JUMP: > case NFT_GOTO: > - err = nf_tables_bind_chain(ctx, chain); > + err = nft_add_chain_binding(ctx->chain, chain); > if (err < 0) > goto err1; > + > + err = nf_tables_bind_chain(ctx, chain); > + if (err < 0) { > + nft_deactivate_chain_binding(ctx->chain, chain); > + nft_del_chain_binding(ctx->chain, chain); > + goto err1; > + } > break; > default: > break; > @@ -106,6 +113,8 @@ static void nft_immediate_activate(const struct nft_ctx *ctx, > case NFT_JUMP: > case NFT_GOTO: > chain = data->verdict.chain; > + nft_activate_chain_binding(ctx->chain, chain); > + > if (!nft_chain_binding(chain)) > break; > > @@ -152,6 +161,20 @@ static void nft_immediate_deactivate(const struct nft_ctx *ctx, > case NFT_JUMP: > case NFT_GOTO: > chain = data->verdict.chain; > + switch (phase) { > + case NFT_TRANS_PREPARE_ERROR: > + case NFT_TRANS_PREPARE: > + nft_deactivate_chain_binding(ctx->chain, chain); > + break; > + case NFT_TRANS_ABORT: > + case NFT_TRANS_RELEASE: > + nft_deactivate_chain_binding(ctx->chain, chain); > + fallthrough; > + case NFT_TRANS_COMMIT: > + nft_del_chain_binding(ctx->chain, chain); > + break; > + } > + > if (!nft_chain_binding(chain)) > break; > > -- > 2.30.2 > > >