β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3385

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3385 Β· 9 files
FileTypeDescriptionSize
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

fixed
baseline reproduced→ patch + rebuild →patched clean

Rebuilt 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)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Sep 11 23:03:53 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

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).