[PATCH] net/sched: hfsc: clear eligible node on reset

From: shaos

Date: Tue Nov 03 2026 - 12:45:30 EST


Hello,

hfsc_reset_class() clears the realtime class counters but leaves its
el_node marked as linked. hfsc_reset_qdisc() then discards the
scheduler-wide eligible tree by assigning RB_ROOT. After a link
reset with an RSC class backlogged, the next packet sees the stale
non-empty el_node, skips init_ed(), and leaves the class absent from the
new eligible tree. HFSC then retains the packet in its child qdisc
without a dequeue event or watchdog recovery.

Two fresh QEMU runs reproduced a permanently queued packet after
link down/up. An FSC-only class, a drained-queue reset, and a no-reset
control transmitted normally. A kernel with the patch below reported
RESULT=NOT_STUCK and transmitted the packet. This requires control of
the HFSC qdisc and is a scheduler liveness/correctness defect; no memory
corruption or privilege escalation was observed.

HEAD commit: c03c1a14507dfaf8b856c2608f9ac216021fc222
Git tree: 30fd2ef82912c26cf3a58ad0ba6afc375fa851ac
Source version: Linux 7.3
Compiler: gcc 11.4.0, GNU ld 2.38

The console logs, full kernel config, packet sender, and QEMU/initramfs
harness are available from one fixed-revision Gist:

Console output:
https://gist.githubusercontent.com/shaos26/d7ec57d3a631368bd50ad0182ce9c268/raw/6344f32527074498da61f02b58068539859b388e/console-output.txt
Kernel config:
https://gist.githubusercontent.com/shaos26/d7ec57d3a631368bd50ad0182ce9c268/raw/6344f32527074498da61f02b58068539859b388e/kernel-config.txt
PoC source:
https://gist.githubusercontent.com/shaos26/d7ec57d3a631368bd50ad0182ce9c268/raw/6344f32527074498da61f02b58068539859b388e/poc.c
Harness: hfsc_reset_harness.sh
https://gist.githubusercontent.com/shaos26/d7ec57d3a631368bd50ad0182ce9c268/raw/6344f32527074498da61f02b58068539859b388e/hfsc_reset_harness.sh
Harness: init.sh
https://gist.githubusercontent.com/shaos26/d7ec57d3a631368bd50ad0182ce9c268/raw/6344f32527074498da61f02b58068539859b388e/init.sh
Harness: mkinitramfs.sh
https://gist.githubusercontent.com/shaos26/d7ec57d3a631368bd50ad0182ce9c268/raw/6344f32527074498da61f02b58068539859b388e/mkinitramfs.sh
Harness: run-qemu.sh
https://gist.githubusercontent.com/shaos26/d7ec57d3a631368bd50ad0182ce9c268/raw/6344f32527074498da61f02b58068539859b388e/run-qemu.sh

Clone the Gist and build the vulnerable kernel and initramfs. The
kernel source is the HEAD commit above; use the attached full config.
The host needs gcc, fakeroot, cpio, gzip, BusyBox, iproute2, and QEMU:

git clone \
https://gist.github.com/shaos26/d7ec57d3a631368bd50ad0182ce9c268.git \
hfsc-repro
cd hfsc-repro
TMP=/tmp/hfsc-repro-build
mkdir -p "$TMP/rootfs"
git clone \
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git \
linux
git -C linux checkout c03c1a14507dfaf8b856c2608f9ac216021fc222
cp kernel-config.txt linux/.config
make -C linux olddefconfig
make -C linux -j"$(nproc)" bzImage
mkdir -p poc
mv hfsc_reset_harness.sh init.sh mkinitramfs.sh run-qemu.sh poc/
cc -O2 -static -Wall -Wextra -Werror -o poc/hfsc_probe poc.c
fakeroot -- sh poc/mkinitramfs.sh "$TMP/rootfs" "$TMP/initramfs.cpio.gz"

Run the positive and close controls on fresh QEMU boots:

KERNEL="$PWD/linux/arch/x86/boot/bzImage" \
INITRD="$TMP/initramfs.cpio.gz" OUT="$TMP/rt-reset.log" \
sh poc/run-qemu.sh rt-reset
KERNEL="$PWD/linux/arch/x86/boot/bzImage" \
INITRD="$TMP/initramfs.cpio.gz" OUT="$TMP/ls-reset.log" \
sh poc/run-qemu.sh ls-reset
KERNEL="$PWD/linux/arch/x86/boot/bzImage" \
INITRD="$TMP/initramfs.cpio.gz" OUT="$TMP/rt-drained-reset.log" \
sh poc/run-qemu.sh rt-drained-reset
KERNEL="$PWD/linux/arch/x86/boot/bzImage" \
INITRD="$TMP/initramfs.cpio.gz" OUT="$TMP/rt-noreset.log" \
sh poc/run-qemu.sh rt-noreset

The positive prints RESULT=STUCK. Controls print RESULT=CONTROL_PASS.
For the fixed-kernel mechanism control, rebuild from the patched source;
rt-reset then prints RESULT=NOT_STUCK. The guest configures a qdisc and
sends AF_PACKET frames, so it runs as root in QEMU. A direct trigger
requires CAP_NET_ADMIN and CAP_NET_RAW.

The eligible-node marker must be cleared when the class scheduler state
is reset:

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: shaos <shawnq1@xxxxxxx>
---
net/sched/sch_hfsc.c | 1 +
1 file changed, 1 insertion(+)

diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c
--- a/net/sched/sch_hfsc.c
+++ b/net/sched/sch_hfsc.c
@@ -1488,6 +1488,7 @@ static void hfsc_reset_class(struct hfsc_class *cl)
cl->cl_myf = 0;
cl->cl_cfmin = 0;
cl->cl_nactive = 0;
+ RB_CLEAR_NODE(&cl->el_node);

cl->vt_tree = RB_ROOT;
cl->cf_tree = RB_ROOT;
--
2.34.1