FAILED: patch "[PATCH] netfilter: cttimeout: prevent UAF during module unload" failed to apply to 6.18-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 6.18-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.18.y
git checkout FETCH_HEAD
git cherry-pick -x fec9b1de0d02de8dafa3cc344bcb91cf28660643
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026091639-unmade-pouch-5365@gregkh' --subject-prefix 'PATCH 6.18.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From fec9b1de0d02de8dafa3cc344bcb91cf28660643 Mon Sep 17 00:00:00 2001
From: Chengfeng Ye <[email protected]>
Date: Mon, 24 Aug 2026 20:12:38 +0800
Subject: [PATCH] netfilter: cttimeout: prevent UAF during module unload
nf_ct_set_timeout() protects the timeout hook dereference and policy lookup with rcu_read_lock(). cttimeout_exit(), however, unregisters the per-net operations before it clears the hook.
This allows the following interleaving:
CPU 0 CPU 1
cttimeout_exit() nf_ct_set_timeout()
unregister_pernet_subsys() rcu_read_lock()
kfree(pernet) h = nf_ct_timeout_hook
h->timeout_find_get()
nfct_timeout_pernet()
The hook still points to ctnl_timeout_find_get() when CPU 1 looks up the already freed per-net timeout list. KASAN reported:
BUG: KASAN: slab-use-after-free in ctnl_timeout_find_get Read of size 8 by task poc/90 Call Trace: ctnl_timeout_find_get+0x271/0x2a0 [nfnetlink_cttimeout] nf_ct_set_timeout+0x7b/0x3c0 xt_ct_tg_check+0x724/0xb20 xt_check_target+0x234/0xa90 do_ipt_set_ctl+0x570/0x1270 Allocated by task 89: __kmalloc_noprof+0x16e/0x460 ops_init+0x6d/0x420 register_pernet_operations+0x2f6/0x670 Freed by task 91: kfree+0x131/0x390 ops_undo_list+0x3d4/0x730 unregister_pernet_operations+0x232/0x490 unregister_pernet_subsys+0x1c/0x30 cttimeout_exit+0x52/0x970 [nfnetlink_cttimeout]
Clear the hook and wait for existing readers before unregistering the per-net operations. This blocks new policy lookups and ensures readers that observed the hook finish before the per-net storage is freed.
net/netfilter/nfnetlink_cttimeout.c +1 -1
@@ -652,9 +652,9 @@ static void __exit cttimeout_exit(void) {nfnetlink_subsys_unregister (&cttimeout_subsys); -unregister_pernet_subsys (&cttimeout_ops);RCU_INIT_POINTER (nf_ct_timeout_hook,NULL );synchronize_net (); +unregister_pernet_subsys (&cttimeout_ops); }module_init (cttimeout_init);
Sasha Levin
maintainerPATCH 1/2 netfilter: cttimeout: detach dataplane timeout policy and repurpose refcount
From: Pablo Neira Ayuso <[email protected]>
[ Upstream commit 7d6a9cdb8d3a51d9cfe546a09a518ab3d2671549 ]
Add a refcount for struct nf_ct_timeout which is used by ct extension to set the custom ct timeout policy, this tells us that the ct timeout is being used by a conntrack entry. When the last conntrack entry drops the refcount on the ct timeout, the ct timeout is released.
Remove the refcount for control plane which controls if the ruleset refers to the timeout policy. After this update, it is possible to remove the ct timeout policy from nfnetlink_cttimeout immediately. This is for simplicity not to handle two refcounts on a single object.
Remove nf_queue_nf_hook_drop(): a packet sitting in nfqueue will just hold a reference to the nf_ct_timeout object until packet is reinjected, since this is part of the ct extension, this will be released by the time the conntrack is freed.
nf_ct_untimeout() is still called to clean up in a best effort basis:
the ct timeout on existing entries gets removed when the ct timeout goes away, but as long as the iptables ruleset still refers to the ct timeout through a template, new conntracks may keep attaching it and extend its lifetime until the rule is removed.
nf_ct_untimeout() is not called anymore from module removal path, this is unlikely to find timeouts give module refcount is bumped, and the new refcount already tracks the ct timeout policy use so it is released when unused.
Stable-dep-of: fec9b1de0d02 ("netfilter: cttimeout: prevent UAF during module unload")
--- include/net/netfilter/nf_conntrack_timeout.h | 27 ++++- net/netfilter/nf_conntrack_core.c | 6 +- net/netfilter/nf_conntrack_timeout.c | 27 ++++- net/netfilter/nfnetlink_cttimeout.c | 112 +++++++++---------- net/netfilter/nft_ct.c | 7 +- net/netfilter/xt_CT.c | 2 +- 6 files changed, 107 insertions(+), 74 deletions(-)
include/net/netfilter/nf_conntrack_timeout.h +25 -2
@@ -12,6 +12,7 @@#define CTNL_TIMEOUT_NAME_MAX 32struct nf_ct_timeout { +refcount_t refcnt;__u16 l3num;const struct nf_conntrack_l4proto *l4proto;struct rcu_head rcu;
@@ -22,6 +23,22 @@ struct nf_conn_timeout {struct nf_ct_timeout __rcu *timeout; }; +static inline void nf_ct_timeout_put (const struct nf_conn *ct) +{ +#ifdef CONFIG_NF_CONNTRACK_TIMEOUT +struct nf_conn_timeout *timeout_ext; +struct nf_ct_timeout *timeout; + + timeout_ext =nf_ct_ext_find (ct,NF_CT_EXT_TIMEOUT ); +if (!timeout_ext) +return ; + + timeout =rcu_dereference (timeout_ext->timeout); +if (timeout &&refcount_dec_and_test (&timeout->refcnt)) +kfree_rcu (timeout, rcu); +#endif +} +static inline unsigned int *nf_ct_timeout_data (const struct nf_conn_timeout *t) {
@@ -56,8 +73,14 @@ struct nf_conn_timeout *nf_ct_timeout_ext_add(struct nf_conn *ct,#ifdef CONFIG_NF_CONNTRACK_TIMEOUT struct nf_conn_timeout *timeout_ext; +if (!timeout) +return NULL ; + timeout_ext =nf_ct_ext_add (ct,NF_CT_EXT_TIMEOUT , gfp); -if (timeout_ext ==NULL ) +if (!timeout_ext || timeout_ext->timeout) +return NULL ; + +if (!refcount_inc_not_zero (&timeout->refcnt))return NULL ;rcu_assign_pointer (timeout_ext->timeout, timeout);
@@ -75,7 +98,7 @@ static inline unsigned int *nf_ct_timeout_lookup(const struct nf_conn *ct)struct nf_conn_timeout *timeout_ext; timeout_ext =nf_ct_timeout_find (ct); -if (timeout_ext) +if (timeout_ext &&rcu_access_pointer (timeout_ext->timeout)) timeouts =nf_ct_timeout_data (timeout_ext);#endif return timeouts;
net/netfilter/nf_conntrack_core.c +4 -2
@@ -1690,16 +1690,18 @@ void nf_conntrack_free(struct nf_conn *ct)*/ WARN_ON (refcount_read (&ct->ct_general.use) !=0 ); +rcu_read_lock ();if (ct->status &IPS_SRC_NAT_DONE ) {const struct nf_nat_hook *nat_hook; -rcu_read_lock (); nat_hook =rcu_dereference (nf_nat_hook);if (nat_hook) nat_hook->remove_nat_bysrc (ct); -rcu_read_unlock (); } +nf_ct_timeout_put (ct); +rcu_read_unlock (); +kfree (ct->ext);kmem_cache_free (nf_conntrack_cachep, ct); cnet =nf_ct_pernet (net);
net/netfilter/nf_conntrack_timeout.c +24 -3
@@ -25,17 +25,32 @@const struct nf_ct_timeout_hooks __rcu *nf_ct_timeout_hook__read_mostly ;EXPORT_SYMBOL_GPL (nf_ct_timeout_hook); +/* nf_ct_iterate_cleanup() holds the conntrack lock. */ static int untimeout (struct nf_conn *ct,void *timeout) {struct nf_conn_timeout *timeout_ext =nf_ct_timeout_find (ct);if (timeout_ext) { -const struct nf_ct_timeout *t; +struct nf_ct_timeout *t; - t =rcu_access_pointer (timeout_ext->timeout); +rcu_read_lock (); + t =rcu_dereference (timeout_ext->timeout); +if (!t) { +rcu_read_unlock (); +return 0 ; + } -if (!timeout || t == timeout) +if (!timeout || t == timeout) {RCU_INIT_POINTER (timeout_ext->timeout,NULL ); + +/* No race with nf_conntrack_free() which is called +* only after the conntrack has been removed from +* the hashes. +*/ +if (refcount_dec_and_test (&t->refcnt)) +kfree_rcu (t, rcu); + } +rcu_read_unlock (); }/* We are not intended to delete this conntrack. */
@@ -70,6 +85,8 @@ int nf_ct_set_timeout(struct net *net, struct nf_conn *ct,const char *errmsg =NULL ;int ret =0 ; +WARN_ON_ONCE (!nf_ct_is_template (ct)); +rcu_read_lock (); h =rcu_dereference (nf_ct_timeout_hook);if (!h) {
@@ -127,6 +144,8 @@ void nf_ct_destroy_timeout(struct nf_conn *ct)struct nf_conn_timeout *timeout_ext;const struct nf_ct_timeout_hooks *h; +WARN_ON_ONCE (!nf_ct_is_template (ct)); +rcu_read_lock (); h =rcu_dereference (nf_ct_timeout_hook);
@@ -139,6 +158,8 @@ void nf_ct_destroy_timeout(struct nf_conn *ct)if (t) h->timeout_put (t);RCU_INIT_POINTER (timeout_ext->timeout,NULL ); +if (t &&refcount_dec_and_test (&t->refcnt)) +kfree_rcu (t, rcu); } }rcu_read_unlock ();
net/netfilter/nfnetlink_cttimeout.c +50 -62
219 lines not on this page · raw message
net/netfilter/nft_ct.c +3 -4
@@ -898,8 +898,6 @@ static void nft_ct_timeout_obj_eval(struct nft_object *obj, } } -rcu_assign_pointer (timeout->timeout, priv->timeout); -/* adjust the timeout as per 'new' state. ct is unconfirmed, * so the current timestamp must not be added. */
@@ -950,6 +948,7 @@ static int nft_ct_timeout_obj_init(const struct nft_ctx *ctx, timeout->l3num = l3num; timeout->l4proto = l4proto; +refcount_set (&timeout->refcnt,1 ); ret =nf_ct_netns_get (ctx->net, ctx->family);if (ret <0 )
@@ -970,10 +969,10 @@ static void nft_ct_timeout_obj_destroy(const struct nft_ctx *ctx,struct nft_ct_timeout_obj *priv =nft_obj_data (obj);struct nf_ct_timeout *timeout = priv->timeout; -nf_queue_nf_hook_drop (ctx->net);nf_ct_untimeout (ctx->net, timeout);nf_ct_netns_put (ctx->net, ctx->family); -kfree_rcu (priv->timeout, rcu); +if (refcount_dec_and_test (&timeout->refcnt)) +kfree_rcu (priv->timeout, rcu); } static int nft_ct_timeout_obj_dump(struct sk_buff *skb,
net/netfilter/xt_CT.c +1 -1
@@ -284,7 +284,7 @@ static void xt_ct_tg_destroy(const struct xt_tgdtor_param *par,struct nf_conn_help *help;if (ct) { -if (info->helper[0 ] || info->timeout[0 ]) +if (info->helper[0 ])nf_queue_nf_hook_drop (par->net); help =nfct_help (ct);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/2 netfilter: cttimeout: prevent UAF during module unload
From: Chengfeng Ye <[email protected]>
[ Upstream commit fec9b1de0d02de8dafa3cc344bcb91cf28660643 ]
nf_ct_set_timeout() protects the timeout hook dereference and policy lookup with rcu_read_lock(). cttimeout_exit(), however, unregisters the per-net operations before it clears the hook.
This allows the following interleaving:
CPU 0 CPU 1
cttimeout_exit() nf_ct_set_timeout()
unregister_pernet_subsys() rcu_read_lock()
kfree(pernet) h = nf_ct_timeout_hook
h->timeout_find_get()
nfct_timeout_pernet()
The hook still points to ctnl_timeout_find_get() when CPU 1 looks up the already freed per-net timeout list. KASAN reported:
BUG: KASAN: slab-use-after-free in ctnl_timeout_find_get Read of size 8 by task poc/90 Call Trace: ctnl_timeout_find_get+0x271/0x2a0 [nfnetlink_cttimeout] nf_ct_set_timeout+0x7b/0x3c0 xt_ct_tg_check+0x724/0xb20 xt_check_target+0x234/0xa90 do_ipt_set_ctl+0x570/0x1270 Allocated by task 89: __kmalloc_noprof+0x16e/0x460 ops_init+0x6d/0x420 register_pernet_operations+0x2f6/0x670 Freed by task 91: kfree+0x131/0x390 ops_undo_list+0x3d4/0x730 unregister_pernet_operations+0x232/0x490 unregister_pernet_subsys+0x1c/0x30 cttimeout_exit+0x52/0x970 [nfnetlink_cttimeout]
Clear the hook and wait for existing readers before unregistering the per-net operations. This blocks new policy lookups and ensures readers that observed the hook finish before the per-net storage is freed.
--- net/netfilter/nfnetlink_cttimeout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
net/netfilter/nfnetlink_cttimeout.c +1 -1
@@ -654,9 +654,9 @@ static void __exit cttimeout_exit(void) {nfnetlink_subsys_unregister (&cttimeout_subsys); -unregister_pernet_subsys (&cttimeout_ops);RCU_INIT_POINTER (nf_ct_timeout_hook,NULL );synchronize_net (); +unregister_pernet_subsys (&cttimeout_ops); }module_init (cttimeout_init);
Signature · 1 line
2.53.0