linux/net/sched
Jamal Hadi Salim a2e0c045c8 net/sched: act_ct: Only release RCU read lock after ct_ft
[ Upstream commit f462dca0c8 ]

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>
Signed-off-by: Sasha Levin <sashal@kernel.org>
History 2026-06-01 17:35:29 +02:00
..
act_api.c net/sched: act_api: fix possible infinite loop in tcf_idr_check_alloc() 2024-07-05 09:14:30 +02:00
act_bpf.c bpf: Add bpf_prog_run_data_pointers() 2025-12-07 06:09:29 +09:00
act_connmark.c net/sched: act_connmark: handle errno on tcf_idr_check_alloc 2025-12-07 06:09:30 +09:00
act_csum.c net: sched: act_csum: validate nested VLAN headers 2026-06-01 17:35:02 +02:00
act_ct.c net/sched: act_ct: Only release RCU read lock after ct_ft 2026-06-01 17:35:29 +02:00
act_ctinfo.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_gact.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_gate.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_ife.c net/sched: act_ife: convert comma to semicolon 2026-02-06 16:42:05 +01:00
act_ipt.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_meta_mark.c treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 152 2019-05-30 11:26:32 -07:00
act_meta_skbprio.c treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 152 2019-05-30 11:26:32 -07:00
act_meta_skbtcindex.c treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 152 2019-05-30 11:26:32 -07:00
act_mirred.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_mpls.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_nat.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_pedit.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_police.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_sample.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_simple.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_skbedit.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_skbmod.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_tunnel_key.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09:00
act_vlan.c net: sched: act: move global static variable net_id to tc_action_ops 2025-12-07 06:09:28 +09: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-04-18 10:33:30 +02:00
cls_basic.c net_sched: refactor TC action init API 2021-08-02 10:24:38 +01:00
cls_bpf.c bpf: Add bpf_prog_run_data_pointers() 2025-12-07 06:09:29 +09:00
cls_cgroup.c net_sched: refactor TC action init API 2021-08-02 10:24:38 +01:00
cls_flow.c net/sched: cls_flow: fix NULL pointer dereference on shared blocks 2026-04-18 10:33:31 +02:00
cls_flower.c net: fix geneve_opt length integer overflow 2025-04-10 14:32:05 +02:00
cls_fw.c net/sched: cls_fw: fix NULL pointer dereference on shared blocks 2026-04-18 10:33:31 +02:00
cls_matchall.c net_sched: refactor TC action init API 2021-08-02 10:24:38 +01:00
cls_route.c net/sched: cls_route: No longer copy tcf_result on update to avoid use-after-free 2023-08-11 15:13:52 +02:00
cls_u32.c net/sched: cls_u32: use skb_header_pointer_careful() 2026-06-01 17:35:09 +02:00
em_canid.c net: sched: kerneldoc fixes 2020-07-13 17:20:40 -07:00
em_cmp.c net: sched: fix misspellings using misspell-fixer tool 2020-11-10 17:00:28 -08:00
em_ipset.c sched: consistently handle layer3 header accesses in the presence of VLANs 2020-07-03 14:34:53 -07:00
em_ipt.c sched: consistently handle layer3 header accesses in the presence of VLANs 2020-07-03 14:34:53 -07:00
em_meta.c sched: consistently handle layer3 header accesses in the presence of VLANs 2020-07-03 14:34:53 -07:00
em_nbyte.c net: sched: Return the correct errno code 2021-02-06 11:15:28 -08:00
em_text.c net: sched: em_text: fix possible memory leak in em_text_destroy() 2024-01-15 18:51:12 +01:00
em_u32.c treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 152 2019-05-30 11:26:32 -07:00
ematch.c net_sched: reject TCF_EM_SIMPLE case for complex ematch module 2022-12-31 13:14:39 +01:00
Kconfig net/sched: Retire dsmark qdisc 2024-03-01 13:21:42 +01:00
Makefile net/sched: Retire dsmark qdisc 2024-03-01 13:21:42 +01:00
sch_api.c net/sched: sch_qfq: Fix null-deref in agg_dequeue 2025-12-07 06:09:12 +09:00
sch_blackhole.c Revert "net: sched: Pass root lock to Qdisc_ops.enqueue" 2020-07-16 16:48:34 -07:00
sch_cake.c net/sched: sch_cake: Fix incorrect qlen reduction in cake_drop 2026-01-19 13:09:36 +01:00
sch_cbs.c net/sched: cbs: Fix integer overflow in cbs_set_port_rate() 2024-12-14 19:51:36 +01:00
sch_choke.c net: sched: fix ordering of qlen adjustment 2025-01-09 13:28:29 +01:00
sch_codel.c codel: remove sch->q.qlen check before qdisc_tree_reduce_backlog() 2025-08-28 16:24:33 +02:00
sch_drr.c sch_drr: make drr_qlen_notify() idempotent 2025-08-28 16:24:33 +02:00
sch_etf.c net: sched: delete duplicate cleanup of backlog and qlen 2022-10-29 10:12:57 +02:00
sch_ets.c net/sched: ets: fix divide by zero in the offload path 2026-04-18 10:33:09 +02:00
sch_fifo.c pfifo_tail_enqueue: Drop new packet when sch->limit == 0 2025-03-13 12:51:00 +01:00
sch_fq.c net_sched: Flush gso_skb list too during ->change() 2025-05-22 14:08:22 +02:00
sch_fq_codel.c codel: remove sch->q.qlen check before qdisc_tree_reduce_backlog() 2025-08-28 16:24:33 +02:00
sch_fq_pie.c net_sched: Flush gso_skb list too during ->change() 2025-05-22 14:08:22 +02:00
sch_frag.c net/sched: Extend qdisc control block with tc control block 2022-01-05 12:42:33 +01:00
sch_generic.c net_sched: limit try_bulk_dequeue_skb() batches 2025-12-07 06:09:28 +09:00
sch_gred.c net: sched: Fix spelling mistakes 2021-05-31 22:44:56 -07:00
sch_hfsc.c net/sched: sch_hfsc: fix divide-by-zero in rtsc_min() 2026-04-18 10:33:29 +02:00
sch_hhf.c net_sched: Flush gso_skb list too during ->change() 2025-05-22 14:08:22 +02:00
sch_htb.c net/sched: Remove unnecessary WARNING condition for empty child qdisc in htb_activate 2025-08-28 16:24:38 +02:00
sch_ingress.c net/sched: Reserve TC_H_INGRESS (TC_H_CLSACT) for ingress (clsact) Qdiscs 2023-06-09 10:32:17 +02:00
sch_mq.c net: sched: update default qdisc visibility after Tx queue cnt changes 2021-11-18 19:16:10 +01:00
sch_mqprio.c net/sched: mqprio: Add length check for TCA_MQPRIO_{MAX/MIN}_RATE64 2023-08-03 10:22:36 +02:00
sch_multiq.c net: sched: sch_multiq: fix possible OOB write in multiq_tune() 2024-07-05 09:14:06 +02:00
sch_netem.c net/sched: Restrict conditions for adding duplicating netems to qdisc tree 2025-08-28 16:24:08 +02:00
sch_pie.c net_sched: Flush gso_skb list too during ->change() 2025-05-22 14:08:22 +02:00
sch_plug.c net: sched: sch_qfq: Fix UAF in qfq_dequeue() 2023-09-19 12:22:59 +02:00
sch_prio.c net_sched: prio: fix a race in prio_tune() 2025-06-27 11:05:20 +01:00
sch_qfq.c net/sched: qfq: Use cl_is_active to determine whether class is active in qfq_rm_from_ag 2026-02-06 16:41:53 +01:00
sch_red.c net/sched: sch_red: Replace direct dequeue call with peek and qdisc_dequeue_peeked 2026-06-01 17:35:19 +02:00
sch_sfb.c net: sched: sfb: fix null pointer access issue when sfb_init() fails 2022-10-29 10:12:57 +02:00
sch_sfq.c net_sched: sch_sfq: reject invalid perturb period 2025-06-27 11:05:37 +01:00
sch_skbprio.c net_sched: skbprio: Remove overly strict queue assertions 2025-04-10 14:32:04 +02:00
sch_taprio.c net: sched: fix use-after-free in taprio_change() 2024-11-01 01:52:37 +01:00
sch_tbf.c net_sched: tbf: fix a race in tbf_change() 2025-06-27 11:05:20 +01:00
sch_teql.c net/sched: teql: fix NULL pointer dereference in iptunnel_xmit on TEQL slave xmit 2026-04-18 10:33:12 +02:00