(CVE-2025-37890) Linux Kernel net_sched netem Double Enqueue Leading to Use-After-Free and Local Privilege Escalation
CVE: CVE-2025-37890
Affected Versions: Linux kernel 5.0.1 through 6.15-rc4
CVSS3.1: 7.8 (High) — CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Summary
| Product | Linux Kernel (net_sched) |
|---|---|
| Vendor | Linux Kernel |
| Severity | High — a local unprivileged attacker may exploit this to achieve local privilege escalation |
| Affected Versions | Linux kernel 5.0.1 through 6.15-rc4 |
| CVE Identifier | CVE-2025-37890 |
| CVE Description | A use-after-free in the Linux kernel net scheduler HFSC module via netem’s re-entrant enqueue behaviour leads to local privilege escalation |
| CWE Classification(s) | CWE-416: Use After Free |
CVSS4.0 Scoring System
Base Score: 8.5
Vector String: CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
| Metric | Value |
|---|---|
| Attack Vector (AV) | Local |
| Attack Complexity (AC) | Low |
| Attack Requirements (AT) | None |
| Privileges Required (PR) | Low |
| User Interaction (UI) | None |
| Vulnerable System Confidentiality (VC) | High |
| Vulnerable System Integrity (VI) | High |
| Vulnerable System Availability (VA) | High |
| Subsequent System Confidentiality (SC) | None |
| Subsequent System Integrity (SI) | None |
| Subsequent System Availability (SA) | None |
Technical Details
A use-after-free vulnerability in the Linux Kernel net scheduler subsystem can be exploited to achieve local privilege escalation. In the hfsc_enqueue routine, if the qdisc has a netem child, it is possible for the netem’s re-entrant behaviour to doubly activate a child class. This corrupts internal tracking, leading to a use-after-free vulnerability. We recommend upgrading past commit 37d9cf1a3ce35de3df6f7d209bfb1f50cf188cea.
The vulnerability lies in the netem_enqueue() function.
if (skb2) {
struct Qdisc *rootq = qdisc_root_bh(sch);
u32 dupsave = q->duplicate; /* prevent duplicating a dup... */
q->duplicate = 0;
rootq->enqueue(skb2, rootq, to_free); // [1]
q->duplicate = dupsave;
skb2 = NULL;
}
qdisc_qstats_backlog_inc(sch, skb);
cb = netem_skb_cb(skb);
if (q->gap == 0 || /* not doing reordering */
q->counter < q->gap - 1 || /* inside last reordering gap */
q->reorder < get_crandom(&q->reorder_cor, &q->prng)) {
// [...]
tfifo_enqueue(skb, sch); // [2]
When the netem qdisc tries to duplicate a packet, it enqueues the packet into the root qdisc ([1]). Subsequently, tfifo_enqueue() is called at [2], which increases the qdisc’s qlen.
A vulnerability exists when the netem qdisc is a child of a classful parent. For example, in drr_enqueue(), there is first a check ([3]) if the child qdisc is empty. Then, it enqueues the packet into the child qdisc ([4]). After the enqueue succeeds, it activates the newly active child ([5]).
first = !cl->qdisc->q.qlen; // [3]
err = qdisc_enqueue(skb, cl->qdisc, to_free); // [4]
if (unlikely(err != NET_XMIT_SUCCESS)) {
if (net_xmit_drop_count(err)) {
cl->qstats.drops++;
qdisc_qstats_drop(sch);
}
return err;
}
if (first) { // [5]
list_add_tail(&cl->alist, &q->active);
cl->deficit = cl->quantum;
}
When the parent (drr) receives a packet to enqueue in an empty netem qdisc, first = true at [3] and the packet is enqueued in netem. In netem, the packet duplication enqueues the packet in the root qdisc, the parent drr, again before it calls tfifo_enqueue() ([2]). So, the netem still has qlen = 0 when the drr_enqueue() logic runs for the second time. This causes first = true for the duplicate packet as well. Subsequently, both calls succeed and the new child activation occurs twice at [5].
This ’re-entrant’ behaviour is present in other classful qdiscs as well.
This bug can be used to manipulate classful qdisc’s internal tracking and escalated into a LPE.
Proof of Concept
unshare -rn
ip link set dev lo up
tc qdisc add dev lo handle 1:0 root drr
tc class add dev lo classid 1:1 drr
tc qdisc add dev lo parent 1:1 handle 2:0 netem duplicate 100%
echo "" | socat -u STDIN UDP4-DATAGRAM:127.0.0.1:8888,priority=$((0x10001))
Fix
Upgrade past commit 37d9cf1a. The fix introduces a check of the n_active class variable to prevent duplicate insertions.
Credit
Gerrard Tai of STAR Labs SG Pte. Ltd.
Timeline
- 2025-03-28 — Reported to Linux Kernel Security Team
- 2025-05-16 — CVE-2025-37890 published