In the Linux kernel, the following vulnerability has been resolved:
net/sched: actct: Only release RCU read lock after ctft
When looking up a flow table in actct in tcfctflowtableget(), rhashtablelookupfast() internally opens and closes an RCU read critical section before returning ctft. The tcfctflowtablecleanupwork() can complete before refcountincnotzero() is invoked on the returned ctft resulting in a UAF on the already freed ctft object. This vulnerability can lead to privilege escalation.
Analysis from zdi-disclosures@trendmicro.com: When initializing actct, tcfctinit() is called, which internally triggers tcfctflowtable_get().
static int tcfctflowtableget(struct net *net, struct tcfctparams *params)
{ struct zoneshtkey key = { .net = net, .zone = params->zone }; struct tcfctflowtable *ctft; 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 _alwaysinline void *rhashtablelookupfast( 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], rhashtablelookupfast() looks up and returns the corresponding ctft from zonesht . The lookup is performed within an RCU read critical section through rcureadlock() / rcureadunlock(), which prevents the object from being freed. However, at the point of function return, rcureadunlock() has already been called, and there is nothing preventing ctft from being freed before reaching refcountincnotzero(&ctft->ref) at [2]. This interval becomes the race window, during which ctft can be freed.
Free Process:
tcfctflowtableput() is executed through the path tcfctcleanup() callrcu() tcfctparamsfreercu() tcfctparamsfree() tcfctflowtableput().
static void tcfctflowtableput(struct tcfctflowtable *ctft) { if (refcountdecandtest(&ctft->ref)) { rhashtableremovefast(&zonesht, &ctft->node, zonesparams); INITRCUWORK(&ctft->rwork, tcfctflowtablecleanupwork); // [3] queuercuwork(actctwq, &ctft->rwork); } }
At [3], tcfctflowtablecleanup_work() is scheduled as RCU work
static void tcfctflowtablecleanupwork(struct workstruct *work)
{ struct tcfctflowtable *ctft; 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);
}
tcfctflowtablecleanupwork() frees ctft 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]
{
"osv_generated_from": "https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/46xxx/CVE-2026-46319.json",
"cna_assigner": "Linux"
}