linux/net/sched
Jamal Hadi Salim f462dca0c8 net/sched: act_ct: Only release RCU read lock after ct_ft
When looking up a flow table in act_ct in tcf_ct_flow_table_get(),
rhashtable_lookup_fast() internally opens and closes an RCU read critical
section before returning ct_ft.
The tcf_ct_flow_table_cleanup_work() can complete before refcount_inc_not_zero()
is invoked on the returned ct_ft resulting in a UAF on the already freed ct_ft
object. This vulnerability can lead to privilege escalation.

Analysis from zdi-disclosures@trendmicro.com:
When initializing act_ct, tcf_ct_init() is called, which internally triggers
tcf_ct_flow_table_get().

static int tcf_ct_flow_table_get(struct net *net, struct tcf_ct_params *params)

{
                struct zones_ht_key key = { .net = net, .zone = params->zone };
                struct tcf_ct_flow_table *ct_ft;
                int err = -ENOMEM;

                mutex_lock(&zones_mutex);
                ct_ft = rhashtable_lookup_fast(&zones_ht, &key, zones_params); // [1]
                if (ct_ft && refcount_inc_not_zero(&ct_ft->ref)) // [2]
                                goto out_unlock;
                ...
}

static __always_inline void *rhashtable_lookup_fast(
                struct rhashtable *ht, const void *key,
                const struct rhashtable_params params)
{
                void *obj;

                rcu_read_lock();
                obj = rhashtable_lookup(ht, key, params);
                rcu_read_unlock();

                return obj;
}

At [1], rhashtable_lookup_fast() looks up and returns the corresponding ct_ft
from zones_ht . The lookup is performed within an RCU read critical section
through rcu_read_lock() / rcu_read_unlock(), which prevents the object from
being freed. However, at the point of function return, rcu_read_unlock() has
already been called, and there is nothing preventing ct_ft from being freed
before reaching refcount_inc_not_zero(&ct_ft->ref) at [2]. This interval becomes
the race window, during which ct_ft can be freed.

Free Process:

tcf_ct_flow_table_put() is executed through the path tcf_ct_cleanup() call_rcu()
tcf_ct_params_free_rcu() tcf_ct_params_free() tcf_ct_flow_table_put().

static void tcf_ct_flow_table_put(struct tcf_ct_flow_table *ct_ft)
{
                if (refcount_dec_and_test(&ct_ft->ref)) {
                                rhashtable_remove_fast(&zones_ht, &ct_ft->node, zones_params);
                                INIT_RCU_WORK(&ct_ft->rwork, tcf_ct_flow_table_cleanup_work); // [3]
                                queue_rcu_work(act_ct_wq, &ct_ft->rwork);
                }
}

At [3], tcf_ct_flow_table_cleanup_work() is scheduled as RCU work

static void tcf_ct_flow_table_cleanup_work(struct work_struct *work)

{
                struct tcf_ct_flow_table *ct_ft;
                struct flow_block *block;

                ct_ft = container_of(to_rcu_work(work), struct tcf_ct_flow_table,
                                                                rwork);
                nf_flow_table_free(&ct_ft->nf_ft);
                block = &ct_ft->nf_ft.flow_block;
                down_write(&ct_ft->nf_ft.flow_block_lock);
                WARN_ON(!list_empty(&block->cb_list));
                up_write(&ct_ft->nf_ft.flow_block_lock);
                kfree(ct_ft); // [4]

                module_put(THIS_MODULE);
}

tcf_ct_flow_table_cleanup_work() frees ct_ft at [4]. When this function executes
between [1] and [2], UAF occurs.

This race condition has a very short race window, making it generally
difficult to trigger. Therefore, to trigger the vulnerability an msleep(100) was
inserted after[1]

Fixes: 138470a9b2 ("net/sched: act_ct: fix lockdep splat in tcf_ct_flow_table_get")
Reported-by: zdi-disclosures@trendmicro.com
Tested-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
Link: https://patch.msgid.link/20260410111627.46611-1-jhs@mojatatu.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2026-04-12 09:26:15 -07:00
..
act_api.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
act_bpf.c bpf: Add bpf_prog_run_data_pointers() 2025-11-14 08:56:49 -08:00
act_connmark.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
act_csum.c net: sched: act_csum: validate nested VLAN headers 2026-04-03 14:34:56 -07:00
act_ct.c net/sched: act_ct: Only release RCU read lock after ct_ft 2026-04-12 09:26:15 -07:00
act_ctinfo.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
act_gact.c net/sched: Add module aliases for cls_,sch_,act_ modules 2024-02-02 10:57:55 -08:00
act_gate.c net/sched: act_gate: snapshot parameters with RCU on replace 2026-02-27 16:10:36 -08:00
act_ife.c net/sched: act_ife: Fix metalist update behavior 2026-03-05 07:54:08 -08:00
act_meta_mark.c
act_meta_skbprio.c
act_meta_skbtcindex.c
act_mirred.c net/sched: act_mirred: Fix leak when redirecting to self on egress 2026-01-05 16:23:42 -08:00
act_mpls.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
act_nat.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
act_pedit.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
act_police.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
act_sample.c net: sched: act_sample: add action cookie to sample 2024-07-05 17:45:47 -07:00
act_simple.c net/sched: Remove redundant memset(0) call in reset_policy() 2025-08-12 17:13:29 -07:00
act_skbedit.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
act_skbmod.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
act_tunnel_key.c net_sched: add back BH safety to tcf_lock 2025-09-02 15:51:45 -07:00
act_vlan.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
bpf_qdisc.c bpf: net_sched: Use the correct destructor kfunc type 2026-01-12 18:53:57 -08:00
cls_api.c net: sched: cls_api: fix tc_chain_fill_node to initialize tcm_info to zero to prevent an info-leak 2026-03-30 17:56:40 -07:00
cls_basic.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
cls_bpf.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
cls_cgroup.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
cls_flow.c net/sched: cls_flow: fix NULL pointer dereference on shared blocks 2026-04-02 15:08:42 +02:00
cls_flower.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
cls_fw.c net/sched: cls_fw: fix NULL dereference of "old" filters before change() 2026-04-12 08:49:13 -07:00
cls_matchall.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
cls_route.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
cls_u32.c Convert more 'alloc_obj' cases to default GFP_KERNEL arguments 2026-02-21 20:03:00 -08:00
em_canid.c net/sched: em_canid: fix uninit-value in em_canid_match 2025-11-26 16:28:10 +01:00
em_cmp.c net: sched: fix TCF_LAYER_TRANSPORT handling in tcf_get_base_ptr() 2025-11-24 18:53:14 -08:00
em_ipset.c
em_ipt.c
em_meta.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
em_nbyte.c net: sched: fix TCF_LAYER_TRANSPORT handling in tcf_get_base_ptr() 2025-11-24 18:53:14 -08:00
em_text.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
em_u32.c net: fill in MODULE_DESCRIPTION()s for net/sched 2024-02-09 14:12:02 -08:00
ematch.c net_sched: reject TCF_EM_SIMPLE case for complex ematch module 2022-12-19 09:43:18 +00:00
Kconfig sched: Add enqueue/dequeue of dualpi2 qdisc 2025-07-23 17:52:07 -07:00
Makefile sched: Add enqueue/dequeue of dualpi2 qdisc 2025-07-23 17:52:07 -07:00
sch_api.c Convert 'alloc_flex' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
sch_blackhole.c
sch_cake.c net/sched: sch_cake: fixup cake_mq rate adjustment for diffserv config 2026-02-27 18:35:40 -08:00
sch_cbs.c net/sched: cbs: Fix integer overflow in cbs_set_port_rate() 2024-10-15 18:25:47 -07:00
sch_choke.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
sch_codel.c net_sched: use qdisc_dequeue_drop() in cake, codel, fq_codel 2025-11-25 16:10:32 +01:00
sch_drr.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
sch_dualpi2.c net_sched: use qdisc_skb_cb(skb)->pkt_segs in bstats_update() 2025-11-25 16:10:32 +01:00
sch_etf.c net_sched: sch_tfs: implement lockless etf_dump() 2024-04-19 11:34:07 +01:00
sch_ets.c net/sched: ets: fix divide by zero in the offload path 2026-02-26 18:28:47 -08:00
sch_fifo.c pfifo_tail_enqueue: Drop new packet when sch->limit == 0 2025-02-05 18:13:58 -08:00
sch_fq_codel.c Convert more 'alloc_obj' cases to default GFP_KERNEL arguments 2026-02-21 20:03:00 -08:00
sch_fq_pie.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
sch_fq.c net_sched: sch_fq: clear q->band_pkt_count[] in fq_reset() 2026-03-04 17:54:22 -08:00
sch_frag.c net/sched: Use nested-BH locking for sch_frag_data_storage 2025-05-15 15:23:31 +02:00
sch_generic.c net/sched: teql: Fix double-free in teql_master_xmit 2026-03-16 19:40:32 -07:00
sch_gred.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
sch_hfsc.c net/sched: sch_hfsc: fix divide-by-zero in rtsc_min() 2026-03-27 20:41:11 -07:00
sch_hhf.c Convert more 'alloc_obj' cases to default GFP_KERNEL arguments 2026-02-21 20:03:00 -08:00
sch_htb.c Convert remaining multi-line kmalloc_obj/flex GFP_KERNEL uses 2026-02-22 08:26:33 -08:00
sch_ingress.c clsact: Fix use-after-free in init/destroy rollback asymmetry 2026-03-17 12:09:16 +01:00
sch_mq.c Convert more 'alloc_obj' cases to default GFP_KERNEL arguments 2026-02-21 20:03:00 -08:00
sch_mqprio_lib.c net: sched: Fill in missing MODULE_DESCRIPTION for qdiscs 2023-11-01 21:49:09 -07:00
sch_mqprio_lib.h net/sched: mqprio: allow per-TC user input of FP adminStatus 2023-04-13 22:22:10 -07:00
sch_mqprio.c Convert more 'alloc_obj' cases to default GFP_KERNEL arguments 2026-02-21 20:03:00 -08:00
sch_multiq.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
sch_netem.c net/sched: sch_netem: fix out-of-bounds access in packet corruption 2026-04-01 19:24:20 -07:00
sch_pie.c net/sched: Fix backlog accounting in qdisc_dequeue_internal 2025-08-14 17:52:29 -07:00
sch_plug.c net/sched: Add module aliases for cls_,sch_,act_ modules 2024-02-02 10:57:55 -08:00
sch_prio.c net_sched: prio: fix a race in prio_tune() 2025-06-12 08:05:49 -07:00
sch_qfq.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
sch_red.c Including fixes from bluetooth and wireless. 2025-06-12 09:50:36 -07:00
sch_sfb.c net/sched: Add drop reasons for AQM-based qdiscs 2024-12-17 13:27:29 +01:00
sch_sfq.c Convert 'alloc_obj' family to use the new default GFP_KERNEL argument 2026-02-21 17:09:51 -08:00
sch_skbprio.c net_sched: skbprio: Remove overly strict queue assertions 2025-04-02 16:03:32 -07:00
sch_taprio.c Convert more 'alloc_obj' cases to default GFP_KERNEL arguments 2026-02-21 20:03:00 -08:00
sch_tbf.c net_sched: use qdisc_skb_cb(skb)->pkt_segs in bstats_update() 2025-11-25 16:10:32 +01:00
sch_teql.c net/sched: teql: Fix double-free in teql_master_xmit 2026-03-16 19:40:32 -07:00