FAILED: patch "[PATCH] netfilter: nf_tables: don't queue packet path object" failed to apply to 6.6-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 6.6-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.6.y
git checkout FETCH_HEAD
git cherry-pick -x 7904b94768e983bcb2be34a8d6d1f3450f5b838b
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026083157-seldom-action-9ebb@gregkh' --subject-prefix 'PATCH 6.6.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 7904b94768e983bcb2be34a8d6d1f3450f5b838b Mon Sep 17 00:00:00 2001
From: Fourie Zhang <[email protected]>
Date: Mon, 10 Aug 2026 19:35:01 +0800
Subject: [PATCH] netfilter: nf_tables: don't queue packet path object
notifications
All file:line references below are against v7.2-rc4 (ac5b0e5651b1). The trace was captured on 7.2.0-rc6-kasan72rc6 (075b74841bd0), where the same lines apply.
nft_obj_notify() is exported and reached from the packet path. Its only in-tree caller is nft_quota_obj_eval() (net/netfilter/nft_quota.c:68), which notifies with GFP_ATOMIC while evaluating a rule for a transiting packet, holding no mutex.
Since commit 67cc570edaa0 ("netfilter: nf_tables: coalesce multiple notifications into one skbuff") that notification is no longer sent immediately. __nft_obj_notify() queues it onto nft_net->notify_list via nft_notify_enqueue() (net/netfilter/nf_tables_api.c:1211), which is a bare list_add_tail(). notify_list has no lock of its own (include/net/netfilter/nf_tables.h:1951), it is serialised by commit_mutex:
the six other enqueue sites all run inside a netlink transaction, and the drain in nft_commit_notify() (net/netfilter/nf_tables_api.c:10746) does list_del() + kfree_skb() from nf_tables_commit() with commit_mutex held.
Sending packets through a chain that references a depleted quota object therefore races an unlocked list_add_tail() against list_del() + kfree_skb() on another CPU. The WRITE_ONCE(prev->next, new) in __list_add() then stores through an sk_buff that has already been freed:
BUG: KASAN: slab-use-after-free in __nft_obj_notify+0x2c5/0x2d0
Write of size 8 at addr ff110001047183c0 by task poc/76
CPU: 0 UID: 1000 PID: 76 Comm: poc Tainted: G W 7.2.0-rc6-kasan72rc6 #4
Call Trace:
<IRQ>
__nft_obj_notify (include/linux/list.h:164 include/linux/list.h:191
net/netfilter/nf_tables_api.c:1211
net/netfilter/nf_tables_api.c:8743)
nft_quota_obj_eval (net/netfilter/nft_quota.c:68)
nft_do_chain_inet
nf_hook_slow
__ip_local_out
ip_push_pending_frames
udp_send_skb
udp_sendmsg
__x64_sys_sendto
Allocated by task 77:
__alloc_skb (net/core/skbuff.c:704)
__nft_obj_notify (include/net/netlink.h:1055
net/netfilter/nf_tables_api.c:8731)
nft_quota_obj_eval (net/netfilter/nft_quota.c:68)
nft_do_chain
Freed by task 79:
nf_tables_commit (include/linux/skbuff.h:1332
net/netfilter/nf_tables_api.c:10759
net/netfilter/nf_tables_api.c:11185)
nfnetlink_rcv_batch (net/netfilter/nfnetlink.c:574)
netlink_unicast
netlink_sendmsg
The buggy address belongs to the cache skbuff_head_cache of size 232Queueing from the packet path is wrong even leaving the race aside:notify_list is only drained by nft_commit_notify() from nf_tables_commit() (:11185), so a notification enqueued outside a transaction is not sent until some later netlink batch commits, if one ever does.
The gfp argument that nft_obj_notify() still takes is a leftover of the pre-67cc570edaa0 behaviour, where this path called nfnetlink_send() directly. Restore that: split the message construction out into nft_obj_notify_alloc() and let each caller decide what to do with the skb. nft_obj_notify(), the exported one reached from the packet path, sends it straight away; nf_tables_obj_notify(), which runs under commit_mutex, keeps queueing it, so transaction notifications are still coalesced.
net/netfilter/nf_tables_api.c +23 -13
@@ -8715,18 +8715,17 @@ static int nf_tables_delobj(struct sk_buff *skb, const struct nfnl_info *info,return nft_delobj (&ctx, obj); } -static void -__nft_obj_notify (struct net *net,const struct nft_table *table, -struct nft_object *obj,u32 portid,u32 seq,int event, -u16 flags,int family,int report,gfp_t gfp) +static struct sk_buff * +nft_obj_notify_alloc (struct net *net,const struct nft_table *table, +struct nft_object *obj,u32 portid,u32 seq,int event, +u16 flags,int family,int report,gfp_t gfp) { -struct nftables_pernet *nft_net =nft_pernet (net);struct sk_buff *skb;int err;if (!report && !nfnetlink_has_listeners (net,NFNLGRP_NFTABLES )) -return ; +return NULL ; skb =nlmsg_new (NLMSG_GOODSIZE , gfp);if (skb ==NULL )
@@ -8740,10 +8739,10 @@ __nft_obj_notify(struct net *net, const struct nft_table *table, goto err; } -nft_notify_enqueue (skb, report, &nft_net->notify_list); -return ; +return skb; err:nfnetlink_set_err (net, portid,NFNLGRP_NFTABLES , -ENOBUFS ); +return NULL ; } void nft_obj_notify(struct net *net, const struct nft_table *table,
@@ -8752,6 +8751,7 @@ void nft_obj_notify(struct net *net, const struct nft_table *table, {char *buf =kasprintf (gfp,"%s:%u" , table->name,nft_base_seq (net)); +struct sk_buff *skb; audit_log_nfcfg(buf, family,
@@ -8762,17 +8762,27 @@ void nft_obj_notify(struct net *net, const struct nft_table *table, gfp);kfree (buf); -__nft_obj_notify (net, table, obj, portid, seq, event, - flags, family, report, gfp); +/* Called from the packet path, holding no mutex: notify_list is +* serialised by commit_mutex, so send this notification directly. +*/ + skb =nft_obj_notify_alloc (net, table, obj, portid, seq, event, + flags, family, report, gfp); +if (skb) +nfnetlink_send (skb, net, portid,NFNLGRP_NFTABLES , report, gfp); }EXPORT_SYMBOL_GPL (nft_obj_notify);static void nf_tables_obj_notify (const struct nft_ctx *ctx,struct nft_object *obj,int event) { -__nft_obj_notify (ctx->net, ctx->table, obj, ctx->portid, - ctx->seq, event, ctx->flags, ctx->family, - ctx->report,GFP_KERNEL ); +struct nftables_pernet *nft_net =nft_pernet (ctx->net); +struct sk_buff *skb; + + skb =nft_obj_notify_alloc (ctx->net, ctx->table, obj, ctx->portid, + ctx->seq, event, ctx->flags, ctx->family, + ctx->report,GFP_KERNEL ); +if (skb) +nft_notify_enqueue (skb, ctx->report, &nft_net->notify_list); }/*
Sasha Levin
maintainerPATCH 1/3 netfilter: nft_set_rbtree: Remove unused variable nft_net
From: Yang Li <[email protected]>
[ Upstream commit 67059b61597c004e23b074547b40604222bee3a0 ]
The code that uses nft_net has been removed, and the nft_pernet function is merely obtaining a reference to shared data through the net pointer. The content of the net pointer is not modified or changed, so both of them should be removed.
silence the warning:net/netfilter/nft_set_rbtree.c:627:26: warning: variable ‘nft_net’ set but not used
Stable-dep-of: 7904b94768e9 ("netfilter: nf_tables: don't queue packet path object notifications")
--- net/netfilter/nf_tables_api.c | 28 +++++++++++++++------------- net/netfilter/nft_set_rbtree.c | 2 -- 2 files changed, 15 insertions(+), 15 deletions(-)
net/netfilter/nf_tables_api.c +15 -13
@@ -922,11 +922,11 @@ nf_tables_chain_type_lookup(struct net *net, const struct nlattr *nla,return ERR_PTR (-ENOENT ); } -static __be16 nft_base_seq (const struct net *net) +static unsigned int nft_base_seq (const struct net *net) {struct nftables_pernet *nft_net =nft_pernet (net); -return htons (nft_net->base_seq &0xffff ); +return READ_ONCE (nft_net->base_seq); }static const struct nla_policy nft_table_policy[NFTA_TABLE_MAX +1 ] = {
@@ -946,7 +946,7 @@ static int nf_tables_fill_table_info(struct sk_buff *skb, struct net *net, event =nfnl_msg_type (NFNL_SUBSYS_NFTABLES , event); nlh =nfnl_msg_put (skb, portid, seq, event, flags, family, -NFNETLINK_V0 ,nft_base_seq (net)); +NFNETLINK_V0 ,htons (nft_base_seq (net) &0xffff ));if (!nlh) goto nla_put_failure;
@@ -1789,7 +1789,7 @@ static int nf_tables_fill_chain_info(struct sk_buff *skb, struct net *net, event =nfnl_msg_type (NFNL_SUBSYS_NFTABLES , event); nlh =nfnl_msg_put (skb, portid, seq, event, flags, family, -NFNETLINK_V0 ,nft_base_seq (net)); +NFNETLINK_V0 ,htons (nft_base_seq (net) &0xffff ));if (!nlh) goto nla_put_failure;
@@ -3398,7 +3398,7 @@ static int nf_tables_fill_rule_info(struct sk_buff *skb, struct net *net,u16 type =nfnl_msg_type (NFNL_SUBSYS_NFTABLES , event); nlh =nfnl_msg_put (skb, portid, seq, type, flags, family,NFNETLINK_V0 , -nft_base_seq (net)); +htons (nft_base_seq (net) &0xffff ));if (!nlh) goto nla_put_failure;
@@ -4593,7 +4593,8 @@ static int nf_tables_fill_set(struct sk_buff *skb, const struct nft_ctx *ctx, event =nfnl_msg_type (NFNL_SUBSYS_NFTABLES , event); nlh =nfnl_msg_put (skb, portid, seq, event, flags, ctx->family, -NFNETLINK_V0 ,nft_base_seq (ctx->net)); +NFNETLINK_V0 , +htons (nft_base_seq (ctx->net) &0xffff ));if (!nlh) goto nla_put_failure;
@@ -5938,7 +5939,8 @@ static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb) seq = cb->nlh->nlmsg_seq; nlh =nfnl_msg_put (skb, portid, seq, event,NLM_F_MULTI , - table->family,NFNETLINK_V0 ,nft_base_seq (net)); + table->family,NFNETLINK_V0 , +htons (nft_base_seq (net) &0xffff ));if (!nlh) goto nla_put_failure;
@@ -6018,7 +6020,8 @@ static int nf_tables_fill_setelem_info(struct sk_buff *skb, event =nfnl_msg_type (NFNL_SUBSYS_NFTABLES , event); nlh =nfnl_msg_put (skb, portid, seq, event, flags, ctx->family, -NFNETLINK_V0 ,nft_base_seq (ctx->net)); +NFNETLINK_V0 , +htons (nft_base_seq (ctx->net) &0xffff ));if (!nlh) goto nla_put_failure;
@@ -7838,7 +7841,7 @@ static int nf_tables_fill_obj_info(struct sk_buff *skb, struct net *net, event =nfnl_msg_type (NFNL_SUBSYS_NFTABLES , event); nlh =nfnl_msg_put (skb, portid, seq, event, flags, family, -NFNETLINK_V0 ,nft_base_seq (net)); +NFNETLINK_V0 ,htons (nft_base_seq (net) &0xffff ));if (!nlh) goto nla_put_failure;
@@ -8205,9 +8208,8 @@ void nft_obj_notify(struct net *net, const struct nft_table *table, struct nft_object *obj, u32 portid, u32 seq, int event, u16 flags, int family, int report, gfp_t gfp) { - struct nftables_pernet *nft_net = nft_pernet(net); char *buf = kasprintf(gfp, "%s:%u", - table->name, nft_net->base_seq); + table->name,nft_base_seq (net)); audit_log_nfcfg(buf, family,
@@ -8872,7 +8874,7 @@ static int nf_tables_fill_flowtable_info(struct sk_buff *skb, struct net *net, event =nfnl_msg_type (NFNL_SUBSYS_NFTABLES , event); nlh =nfnl_msg_put (skb, portid, seq, event, flags, family, -NFNETLINK_V0 ,nft_base_seq (net)); +NFNETLINK_V0 ,htons (nft_base_seq (net) &0xffff ));if (!nlh) goto nla_put_failure;
@@ -9119,7 +9121,7 @@ static int nf_tables_fill_gen_info(struct sk_buff *skb, struct net *net,int event =nfnl_msg_type (NFNL_SUBSYS_NFTABLES ,NFT_MSG_NEWGEN ); nlh =nfnl_msg_put (skb, portid, seq, event,0 ,AF_UNSPEC , -NFNETLINK_V0 ,nft_base_seq (net)); +NFNETLINK_V0 ,htons (nft_base_seq (net) &0xffff ));if (!nlh) goto nla_put_failure;
net/netfilter/nft_set_rbtree.c +0 -2
@@ -653,7 +653,6 @@ static void nft_rbtree_gc(struct nft_set *set) {struct nft_rbtree *priv =nft_set_priv (set);struct nft_rbtree_elem *rbe, *rbe_end =NULL ; -struct nftables_pernet *nft_net;struct net *net =read_pnet (&set->net);u64 tstamp =nft_net_tstamp (net);struct rb_node *node, *next;
@@ -661,7 +660,6 @@ static void nft_rbtree_gc(struct nft_set *set) set =nft_set_container_of (priv); net =read_pnet (&set->net); - nft_net =nft_pernet (net); gc =nft_trans_gc_alloc (set,0 ,GFP_KERNEL );if (!gc)
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/3 netfilter: nf_tables: place base_seq in struct net
From: Florian Westphal <[email protected]>
[ Upstream commit 64102d9bbc3d41dac5188b8fba75b1344c438970 ]
This will soon be read from packet path around same time as the gencursor.
Both gencursor and base_seq get incremented almost at the same time, so it makes sense to place them in the same structure.
This doesn't increase struct net size on 64bit due to padding.
Stable-dep-of: 7904b94768e9 ("netfilter: nf_tables: don't queue packet path object notifications")
--- include/net/netfilter/nf_tables.h | 1 - include/net/netns/nftables.h | 1 + net/netfilter/nf_tables_api.c | 40 ++++++++++++++----------------- 3 files changed, 19 insertions(+), 23 deletions(-)
include/net/netfilter/nf_tables.h +0 -1
@@ -1817,7 +1817,6 @@ struct nftables_pernet {struct mutex commit_mutex;u64 table_handle;u64 tstamp; -unsigned int base_seq;unsigned int gc_seq;u8 validate_state; };
include/net/netns/nftables.h +1 -0
@@ -3,6 +3,7 @@#define _NETNS_NFTABLES_H_struct netns_nftables { +unsigned int base_seq;u8 gencursor; };
net/netfilter/nf_tables_api.c +18 -22
@@ -924,9 +924,7 @@ nf_tables_chain_type_lookup(struct net *net, const struct nlattr *nla,static unsigned int nft_base_seq (const struct net *net) { -struct nftables_pernet *nft_net =nft_pernet (net); - -return READ_ONCE (nft_net->base_seq); +return READ_ONCE (net->nft.base_seq); }static const struct nla_policy nft_table_policy[NFTA_TABLE_MAX +1 ] = {
@@ -1033,7 +1031,7 @@ static int nf_tables_dump_tables(struct sk_buff *skb,rcu_read_lock (); nft_net =nft_pernet (net); - cb->seq =READ_ONCE (nft_net->base_seq); + cb->seq =nft_base_seq (net);list_for_each_entry_rcu (table, &nft_net->tables, list) {if (family !=NFPROTO_UNSPEC && family != table->family)
@@ -1885,7 +1883,7 @@ static int nf_tables_dump_chains(struct sk_buff *skb,rcu_read_lock (); nft_net =nft_pernet (net); - cb->seq =READ_ONCE (nft_net->base_seq); + cb->seq =nft_base_seq (net);list_for_each_entry_rcu (table, &nft_net->tables, list) {if (family !=NFPROTO_UNSPEC && family != table->family)
@@ -3569,7 +3567,7 @@ static int nf_tables_dump_rules(struct sk_buff *skb,rcu_read_lock (); nft_net =nft_pernet (net); - cb->seq =READ_ONCE (nft_net->base_seq); + cb->seq =nft_base_seq (net);list_for_each_entry_rcu (table, &nft_net->tables, list) {if (family !=NFPROTO_UNSPEC && family != table->family)
@@ -3708,7 +3706,6 @@ nf_tables_getrule_single(u32 portid, const struct nfnl_info *info,static int nf_tables_getrule (struct sk_buff *skb,const struct nfnl_info *info,const struct nlattr *const nla[]) { -struct nftables_pernet *nft_net =nft_pernet (info->net);u32 portid =NETLINK_CB (skb).portid;struct net *net = info->net;struct sk_buff *skb2;
@@ -3740,7 +3737,7 @@ static int nf_tables_getrule(struct sk_buff *skb, const struct nfnl_info *info, buf =kasprintf (GFP_ATOMIC ,"%.*s:%u" ,nla_len (nla[NFTA_RULE_TABLE ]), (char *)nla_data (nla[NFTA_RULE_TABLE ]), - nft_net->base_seq); +nft_base_seq (net));audit_log_nfcfg (buf, info->nfmsg->nfgen_family,1 ,AUDIT_NFT_OP_RULE_RESET ,GFP_ATOMIC );kfree (buf);
@@ -4730,7 +4727,7 @@ static int nf_tables_dump_sets(struct sk_buff *skb, struct netlink_callback *cb)rcu_read_lock (); nft_net =nft_pernet (net); - cb->seq =READ_ONCE (nft_net->base_seq); + cb->seq =nft_base_seq (net);list_for_each_entry_rcu (table, &nft_net->tables, list) { if (ctx->family != NFPROTO_UNSPEC &&
@@ -5910,7 +5907,7 @@ static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb)rcu_read_lock (); nft_net =nft_pernet (net); - cb->seq =READ_ONCE (nft_net->base_seq); + cb->seq =nft_base_seq (net);list_for_each_entry_rcu (table, &nft_net->tables, list) { if (dump_ctx->ctx.family != NFPROTO_UNSPEC &&
@@ -6257,7 +6254,7 @@ static int nf_tables_getsetelem(struct sk_buff *skb, }if (reset) -audit_log_nft_set_reset (table,nft_pernet (net)->base_seq, +audit_log_nft_set_reset (table,nft_base_seq (net), nelems);return err;
@@ -7900,7 +7897,7 @@ static int nf_tables_dump_obj(struct sk_buff *skb, struct netlink_callback *cb)rcu_read_lock (); nft_net =nft_pernet (net); - cb->seq =READ_ONCE (nft_net->base_seq); + cb->seq =nft_base_seq (net);list_for_each_entry_rcu (table, &nft_net->tables, list) {if (family !=NFPROTO_UNSPEC && family != table->family)
@@ -7934,7 +7931,7 @@ static int nf_tables_dump_obj(struct sk_buff *skb, struct netlink_callback *cb) idx++; }if (ctx->reset && entries) -audit_log_obj_reset (table, nft_net->base_seq, entries); +audit_log_obj_reset (table,nft_base_seq (net), entries);if (rc <0 )break ; }
@@ -8103,7 +8100,7 @@ static int nf_tables_getobj_reset(struct sk_buff *skb, buf =kasprintf (GFP_ATOMIC ,"%.*s:%u" ,nla_len (nla[NFTA_OBJ_TABLE ]), (char *)nla_data (nla[NFTA_OBJ_TABLE ]), - nft_net->base_seq); +nft_base_seq (net));audit_log_nfcfg (buf, info->nfmsg->nfgen_family,1 ,AUDIT_NFT_OP_OBJ_RESET ,GFP_ATOMIC );kfree (buf);
@@ -8935,7 +8932,7 @@ static int nf_tables_dump_flowtable(struct sk_buff *skb,rcu_read_lock (); nft_net =nft_pernet (net); - cb->seq =READ_ONCE (nft_net->base_seq); + cb->seq =nft_base_seq (net);list_for_each_entry_rcu (table, &nft_net->tables, list) {if (family !=NFPROTO_UNSPEC && family != table->family)
@@ -9115,7 +9112,6 @@ static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)static int nf_tables_fill_gen_info (struct sk_buff *skb,struct net *net,u32 portid,u32 seq) { -struct nftables_pernet *nft_net =nft_pernet (net);struct nlmsghdr *nlh;char buf[TASK_COMM_LEN ];int event =nfnl_msg_type (NFNL_SUBSYS_NFTABLES ,NFT_MSG_NEWGEN );
@@ -9125,7 +9121,7 @@ static int nf_tables_fill_gen_info(struct sk_buff *skb, struct net *net,if (!nlh) goto nla_put_failure; -if (nla_put_be32 (skb,NFTA_GEN_ID ,htonl (nft_net->base_seq)) || +if (nla_put_be32 (skb,NFTA_GEN_ID ,htonl (nft_base_seq (net))) ||nla_put_be32 (skb,NFTA_GEN_PROC_PID ,htonl (task_pid_nr (current))) ||nla_put_string (skb,NFTA_GEN_PROC_NAME ,get_task_comm (buf, current))) goto nla_put_failure;
@@ -10253,11 +10249,11 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)* Bump generation counter, invalidate any dump in progress. * Cannot fail after this point. */ - base_seq =READ_ONCE (nft_net->base_seq); + base_seq =nft_base_seq (net);while (++base_seq ==0 ) ; -WRITE_ONCE (nft_net->base_seq, base_seq); +WRITE_ONCE (net->nft.base_seq, base_seq); gc_seq =nft_gc_seq_begin (nft_net);
@@ -10474,7 +10470,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)nft_commit_notify (net,NETLINK_CB (skb).portid);nf_tables_gen_notify (net, skb,NFT_MSG_NEWGEN ); -nf_tables_commit_audit_log (&adl, nft_net->base_seq); +nf_tables_commit_audit_log (&adl,nft_base_seq (net));nft_gc_seq_end (nft_net, gc_seq); nft_net->validate_state =NFT_VALIDATE_SKIP ;
@@ -10794,7 +10790,7 @@ static bool nf_tables_valid_genid(struct net *net, u32 genid)mutex_lock (&nft_net->commit_mutex); nft_net->tstamp =get_jiffies_64 (); - genid_ok = genid ==0 || nft_net->base_seq == genid; + genid_ok = genid ==0 ||nft_base_seq (net) == genid;if (!genid_ok)mutex_unlock (&nft_net->commit_mutex);
@@ -11458,7 +11454,7 @@ static int __net_init nf_tables_init_net(struct net *net)INIT_LIST_HEAD (&nft_net->module_list);INIT_LIST_HEAD (&nft_net->notify_list);mutex_init (&nft_net->commit_mutex); - nft_net->base_seq =1 ; + net->nft.base_seq =1 ; nft_net->gc_seq =0 ; nft_net->validate_state =NFT_VALIDATE_SKIP ;
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/3 netfilter: nf_tables: don't queue packet path object notifications
In reply to Sasha LevinFrom: Fourie Zhang <[email protected]>
[ Upstream commit 7904b94768e983bcb2be34a8d6d1f3450f5b838b ]
All file:line references below are against v7.2-rc4 (ac5b0e5651b1). The trace was captured on 7.2.0-rc6-kasan72rc6 (075b74841bd0), where the same lines apply.
nft_obj_notify() is exported and reached from the packet path. Its only in-tree caller is nft_quota_obj_eval() (net/netfilter/nft_quota.c:68), which notifies with GFP_ATOMIC while evaluating a rule for a transiting packet, holding no mutex.
Since commit 67cc570edaa0 ("netfilter: nf_tables: coalesce multiple notifications into one skbuff") that notification is no longer sent immediately. __nft_obj_notify() queues it onto nft_net->notify_list via nft_notify_enqueue() (net/netfilter/nf_tables_api.c:1211), which is a bare list_add_tail(). notify_list has no lock of its own (include/net/netfilter/nf_tables.h:1951), it is serialised by commit_mutex:
the six other enqueue sites all run inside a netlink transaction, and the drain in nft_commit_notify() (net/netfilter/nf_tables_api.c:10746) does list_del() + kfree_skb() from nf_tables_commit() with commit_mutex held.
Sending packets through a chain that references a depleted quota object therefore races an unlocked list_add_tail() against list_del() + kfree_skb() on another CPU. The WRITE_ONCE(prev->next, new) in __list_add() then stores through an sk_buff that has already been freed:
BUG: KASAN: slab-use-after-free in __nft_obj_notify+0x2c5/0x2d0
Write of size 8 at addr ff110001047183c0 by task poc/76
CPU: 0 UID: 1000 PID: 76 Comm: poc Tainted: G W 7.2.0-rc6-kasan72rc6 #4
Call Trace:
<IRQ>
__nft_obj_notify (include/linux/list.h:164 include/linux/list.h:191
net/netfilter/nf_tables_api.c:1211
net/netfilter/nf_tables_api.c:8743)
nft_quota_obj_eval (net/netfilter/nft_quota.c:68)
nft_do_chain_inet
nf_hook_slow
__ip_local_out
ip_push_pending_frames
udp_send_skb
udp_sendmsg
__x64_sys_sendto
Allocated by task 77:
__alloc_skb (net/core/skbuff.c:704)
__nft_obj_notify (include/net/netlink.h:1055
net/netfilter/nf_tables_api.c:8731)
nft_quota_obj_eval (net/netfilter/nft_quota.c:68)
nft_do_chain
Freed by task 79:
nf_tables_commit (include/linux/skbuff.h:1332
net/netfilter/nf_tables_api.c:10759
net/netfilter/nf_tables_api.c:11185)
nfnetlink_rcv_batch (net/netfilter/nfnetlink.c:574)
netlink_unicast
netlink_sendmsg
The buggy address belongs to the cache skbuff_head_cache of size 232Queueing from the packet path is wrong even leaving the race aside:notify_list is only drained by nft_commit_notify() from nf_tables_commit() (:11185), so a notification enqueued outside a transaction is not sent until some later netlink batch commits, if one ever does.
The gfp argument that nft_obj_notify() still takes is a leftover of the pre-67cc570edaa0 behaviour, where this path called nfnetlink_send() directly. Restore that: split the message construction out into nft_obj_notify_alloc() and let each caller decide what to do with the skb. nft_obj_notify(), the exported one reached from the packet path, sends it straight away; nf_tables_obj_notify(), which runs under commit_mutex, keeps queueing it, so transaction notifications are still coalesced.
--- net/netfilter/nf_tables_api.c | 36 ++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-)
net/netfilter/nf_tables_api.c +23 -13
@@ -8170,18 +8170,17 @@ static int nf_tables_delobj(struct sk_buff *skb, const struct nfnl_info *info,return nft_delobj (&ctx, obj); } -static void -__nft_obj_notify (struct net *net,const struct nft_table *table, -struct nft_object *obj,u32 portid,u32 seq,int event, -u16 flags,int family,int report,gfp_t gfp) +static struct sk_buff * +nft_obj_notify_alloc (struct net *net,const struct nft_table *table, +struct nft_object *obj,u32 portid,u32 seq,int event, +u16 flags,int family,int report,gfp_t gfp) { -struct nftables_pernet *nft_net =nft_pernet (net);struct sk_buff *skb;int err;if (!report && !nfnetlink_has_listeners (net,NFNLGRP_NFTABLES )) -return ; +return NULL ; skb =nlmsg_new (NLMSG_GOODSIZE , gfp);if (skb ==NULL )
@@ -8195,10 +8194,10 @@ __nft_obj_notify(struct net *net, const struct nft_table *table, goto err; } -nft_notify_enqueue (skb, report, &nft_net->notify_list); -return ; +return skb; err:nfnetlink_set_err (net, portid,NFNLGRP_NFTABLES , -ENOBUFS ); +return NULL ; } void nft_obj_notify(struct net *net, const struct nft_table *table,
@@ -8207,6 +8206,7 @@ void nft_obj_notify(struct net *net, const struct nft_table *table, {char *buf =kasprintf (gfp,"%s:%u" , table->name,nft_base_seq (net)); +struct sk_buff *skb; audit_log_nfcfg(buf, family,
@@ -8217,17 +8217,27 @@ void nft_obj_notify(struct net *net, const struct nft_table *table, gfp);kfree (buf); -__nft_obj_notify (net, table, obj, portid, seq, event, - flags, family, report, gfp); +/* Called from the packet path, holding no mutex: notify_list is +* serialised by commit_mutex, so send this notification directly. +*/ + skb =nft_obj_notify_alloc (net, table, obj, portid, seq, event, + flags, family, report, gfp); +if (skb) +nfnetlink_send (skb, net, portid,NFNLGRP_NFTABLES , report, gfp); }EXPORT_SYMBOL_GPL (nft_obj_notify);static void nf_tables_obj_notify (const struct nft_ctx *ctx,struct nft_object *obj,int event) { -__nft_obj_notify (ctx->net, ctx->table, obj, ctx->portid, - ctx->seq, event, ctx->flags, ctx->family, - ctx->report,GFP_KERNEL ); +struct nftables_pernet *nft_net =nft_pernet (ctx->net); +struct sk_buff *skb; + + skb =nft_obj_notify_alloc (ctx->net, ctx->table, obj, ctx->portid, + ctx->seq, event, ctx->flags, ctx->family, + ctx->report,GFP_KERNEL ); +if (skb) +nft_notify_enqueue (skb, ctx->report, &nft_net->notify_list); }/*
Signature · 1 line
2.53.0