HFSC init_vf() NULL-pointer dereference: curve-less intermediate class panics kernel on first packet to any descendant
Summary
hfsc_class_create() only allocates cl_fsc for a non-zero link-sharing curve and nothing stops such a curve-less class from being an intermediate node. When the first packet is queued to any descendant leaf, the activation walk init_vf() calls rtsc_min(&cl->cl_virtual, cl->cl_fsc, ...) with cl_fsc==NULL; rtsc_min's first read isc->sm1 faults at VA 0. The passive-side twin update_vf() has guarded exactly this case forever ('if (cl->cl_fsc == NULL || cl->cl_nactive == 0) continue;') and init_vf() itself NULL-checks cl_usc - line 1040 is the sole unguarded curve deref. REPRODUCED twice on stock X86_64_GENERIC guest: Fatal trap 12, VA 0x0, 'Stopped at rtsc_min+0x11: movq (%rsi),%r12', guest dead in DDB. ALTQ_HFSC is compiled into GENERIC; pf arrives as KLD. Config requires root (/dev/pf ioctls), trigger is any packet - privileged local DoS, same bucket as DF-0441. Fix validated (guard go_active && cl->cl_fsc != NULL, mirroring update_vf): panic gone, guest survives the packet.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3385 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| hfsc_initvf_null.c | poc-source | raw /dev/pf ioctl driver + one ping | 6.5 KB | view raw |
| panic.txt | panic | serial console trap capture (both baseline runs) | 1.8 KB | view raw |
| run.log | run-log | baseline run narrative | 1.3 KB | view raw |
| run.fixed.log | run-log | full patched-kernel output: NO PANIC + rollback | 587 B | view raw |
| fix.diff | fix | guard init_vf go_active block with cl_fsc != NULL | 491 B | view raw |
| env.txt | env | unames, config, securelevel | 1.1 KB | view raw |
| build.sh | build | compile command | 106 B | view raw |
| run.sh | run | run script with expected outputs | 116 B | view raw |
| build.log | build-log | build session record incl. nativekernel rc=0 | 1.0 KB | view raw |
Fix verification
fixedRebuilt kernel with the one-hunk guard (make nativekernel rc=0, installkernel rc=0, rebooted into #1). Baseline: Fatal trap 12 VA 0x0 in rtsc_min+0x11, guest dead. Patched: same PoC run survives the packet, prints NO PANIC, rolls the altq config back, network recovers, guest stays healthy. The panic is gone; the panic does NOT reproduce on the patched kernel.
findings/poc/DF-3385/run.fixed.log (full patched run output), panic.txt (baseline trap), build.log (nativekernel/installkernel rc=0 record)
Confirmed kernel references
Detail
Evidence (decisive lines)
panic.txt (serial trap capture, both runs); run.fixed.log (patched kernel: same PoC completes, NO PANIC, rollback restores network, guest healthy); VERDICT.md; fix.diff validated by nativekernel rebuild
PoC changes
Seed sketch was written fresh for this finding; the only iteration was adding an empty altq-transaction rollback after the ping so the fixed-kernel run restores the network and exits cleanly (the misconfigured hierarchy blackholes traffic while attached).
Verified recommended fix
Guard init_vf()'s activation block with cl->cl_fsc != NULL, mirroring update_vf()'s existing guard
Verdict
init_vf() passes cl->cl_fsc (NULL for a class configured with a zero link-sharing curve, which may legally be an intermediate node) into rtsc_min(), whose first instruction reads isc->sm1 -> read of virtual address 0 -> Fatal trap 12 on the FIRST packet queued to any descendant. Baseline stock kernel reproduced twice (panic.txt: trap 12, VA 0x0, Stopped at rtsc_min+0x11 movq (%rsi),%r12). update_vf() has guarded exactly this case at altq_hfsc.c:1092 since the KAME import; init_vf() missed it. Privileged local DoS (config requires root via /dev/pf ioctls; any unprivileged traffic then triggers).
No comments yet.