(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