⬢ DragonFlyBSD Kernel Audit
DF-5533 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5533",
  "status": "reproduced",
  "reproduced": true,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "usb_proc_drain()s cold branch (usb_process.c:433-439) calls suspend_kproc() which tsleeps forever (timo=0, kern_kthread.c:188-204) until the usb_process thread acknowledges TDF_MP_STOPREQ \u2014 but the dispatchers suspend check is #if 0d out (usb_process.c:86-89) and the macro it would use calls kthread_suspend_check(), a function that does not exist anywhere in the DragonFly kernel; additionally suspend_kproc() is invoked while drain still holds the USB bus lock and its tsleep variant releases nothing, so the signalled usb_process thread can never re-acquire the lock to exit either. Reproduced on the guest: sysctl firing the cold-path drain went to D1/suskp and never returned (11s+ observation, still there at reset); the WARNING: A USB process has been left suspended kprintf after the call never printed; dfhz blocked in B-state on the held lock. Boot-time DoS: reachable when a USB controller attach failure during cold autoconfiguration reaches usb_proc_free()/usb_proc_drain() (usb_controller.c:210-236, 879-900) \u2014 hardware/hypervisor-triggered, not attacker-triggerable in default config. Fix validated on a patched kernel (fix.diff v2): the fire sysctl returns immediately, the WARNING prints, no D-state drain thread remains, and the usb_process thread is left intentionally parked (kpsusp) exactly as the WARNING documents.",
  "exploit_chain": "faulty USB controller (or QEMU config presenting broken xhci/ehci) -> attach failure during cold autoconf -> usb_bus teardown error path -> usb_proc_free() -> usb_proc_drain() cold branch -> suspend_kproc() tsleeps forever holding the bus lock -> boot wedged (drain thread + usb_process thread + every later taker of that bus lock frozen)",
  "evidence": "run.log (sysctl PID in D1/suskp forever, fire.log empty, dfhz B3, WARNING absent), run.patched.log (fire returns RC0, WARNING printed, no D-state thread; includes the fix-v1 lock-parking deadlock capture that motivated v2), VERDICT.md, fix.diff (validated v2)",
  "kernel_refs": [
    "sys/bus/u4b/usb_process.c:86-89",
    "sys/bus/u4b/usb_process.c:60",
    "sys/bus/u4b/usb_process.c:433-439",
    "sys/kern/kern_kthread.c:188-204",
    "sys/bus/u4b/controller/usb_controller.c:210-236"
  ],
  "poc_changes": "same KLD harness as DF-5532 plus a kern.dfcold.fire sysctl that sets cold=1 and calls usb_proc_drain() on the live usb_process (mirrors the controller-attach-failure path); fired via nohup background shell so the observing session survives the hang",
  "attempts": 2,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline); patched validation on #1 Oct 7 01:28:37 build",
  "runtime_sec": 420,
  "guest_dirty": 0,
  "build_cmd": "cd /root/dfharness && make  (bsd.kmod.mk KLD; SRCS+= bus_if.h device_if.h; : > opt_usb.h)",
  "run_cmd": "kldload ./dfharness.ko; sleep 1; sysctl kern.dfstarv.stop=1; sleep 2; nohup sh -c sysctl kern.dfcold.fire=1; echo rc=$? > /root/fire.log 2>&1 & sleep 3; cat /root/fire.log; ps -axH -o pid,stat,wchan,comm | grep -E dfhz|sysctl",
  "code_hash": "f6f8e58790fbe32a183b90dd965221c7c977fd60a7b5ccd6d63ac7a17dcdcc06",
  "notes": "fix v1 parked the dispatcher in kproc_suspend_loop() while it still held the bus lock -> drains reacquire deadlocked (captured in run.patched.log); v2 drops the lock around the park (TDF_MP_STOPREQ pre-check keeps the per-message fast path lock-held). Post-cold-drain, a second drain on the same struct waits forever for the intentionally-parked thread \u2014 inherent to the documented left suspended semantics, irrelevant at the boot-failure site. Guest reset (with-src) after both baseline and patched validation.",
  "recommended_fix": "honour suspend requests in the dispatcher loop top with the bus lock dropped (kproc_suspend_loop idiom) and release up->up_lock around suspend_kproc() in drains cold branch \u2014 see validated fix.diff",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "baseline: fire sysctl in D1/suskp forever, never returns, WARNING never prints, bus lock held by wedged drain; patched kernel (full nativekernel build + reboot, fix.diff v2): sysctl kern.dfcold.fire=1 RETURNS immediately (FIRE-RETURNED-RC0), WARNING: A USB process has been left suspended prints, no D-state drain thread remains, usb_process thread intentionally parked in kpsusp. Hang GONE.",
  "fix_evidence": "findings/poc/DF-5533/run.patched.log (fire returns, WARNING printed, ps states; v1 deadlock capture), findings/poc/DF-5533/fix.diff, guest kernel #1 Oct 7 01:28:37"
}