DF-5532 / verdict.json
{ "finding_id": "DF-5532", "status": "reproduced", "reproduced": true, "impact": "dos", "confidence": "certain", "verdict": "usb_proc_mwait()s only wakeups are the dispatchers empty-queue transition (usb_process.c:163-167) and drain teardown (:445-447), so a waiter sleeps uninterruptibly (cv_wait, no PCATCH) until EVERY message source on the usb_process quiesces \u2014 not until its own messages complete. Reproduced with a production-code KLD harness on the real usb_proc_* symbols: victim message completed at T+0.53s while the waiter was still asleep at T+20s under a queue whose every message cost one 10ms tick (churn_count == elapsed ticks, no slow callback anywhere); waiter woke 20ms after the churn stopped (starve_ms=19481; second run 19520). Extreme variant (instant self-requeue): 8.9e9 callbacks monopolized the usb_process thread AND its bus lock for 275s and starved guest userland to death. Unprivileged reachability via the family-established consumers (ucom tcsetattr/close -> ucom_queue_command -> mwait, usb_serial.c:655-672; usbd_transfer_unsetup -> mwait, usb_transfer.c:1286). Fix validated on a patched kernel: starve_ms 19481/19520 -> 0 (waiter wakes in the same millisecond its message completes, same churn load).", "exploit_chain": "local unprivileged user -> consumer syscall path that mwaits (ucom tcsetattr(2)/close(2) on /dev/ttyU*, or transfer unsetup during traffic) -> cv_wait(&up_drain) with no PCATCH -> any unrelated continuously-refilled message source on the same usb_process (driver poll loop, sustained RX/TX transfer-done churn) keeps the queue non-empty -> empty-queue wakeup never fires -> syscall thread parked in unkillable D-state for unbounded time; spin-variant monopolizes the usb_process thread + bus lock, stalling all other consumers incl. detach/hub-event teardown", "evidence": "run.log (T+3s/T+9s/asleep + post-stop wake, starve_ms=19481), run.2.log (starve_ms=19520), run.spin.log (8.9e9 callbacks/275s, sshd starved), run.patched.log (starve_ms=0 on fix v1+v2 kernels), VERDICT.md narrative, fix.diff (validated)", "kernel_refs": [ "sys/bus/u4b/usb_process.c:163-167", "sys/bus/u4b/usb_process.c:363-394", "sys/bus/u4b/usb_process.c:445-447", "sys/kern/kern_condvar.c:20-44", "sys/bus/u4b/serial/usb_serial.c:655-672", "sys/bus/u4b/usb_transfer.c:1286" ], "poc_changes": "seed sketch rewritten as a phase-driven KLD harness: churn pair whose callback requeues itself with a 1-tick lock drop (production-faithful; pure-spin mode kept as sysctl-selectable extreme variant), victim pair msignal+mwait from a dedicated kernel thread standing in for the unprivileged-syscall waiters; added report/stop sysctls; needed opt_usb.h, bus_if.h/device_if.h in SRCS and <sys/condvar.h>/<sys/bus.h> includes for the kmod build", "attempts": 3, "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:12:42 / #1 Oct 7 01:28:37 builds", "runtime_sec": 540, "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 3; sysctl kern.dfstarv.report; sleep 6; sysctl kern.dfstarv.report; sysctl kern.dfstarv.stop=1; sleep 1; sysctl kern.dfstarv.report; kldunload dfharness", "code_hash": "f6f8e58790fbe32a183b90dd965221c7c977fd60a7b5ccd6d63ac7a17dcdcc06", "notes": "guest reset (with-src) after the spin-variant starved sshd, after the cold-test (DF-5533) and at the end of the run; baseline starve_ms bounded only by when the operator stopped the churn - in production unbounded; the DF-5367..5504 family measured per-driver instances of this same uninterruptible occupancy", "recommended_fix": "broadcast up_drain after every completed-message dequeue in the dispatcher (guarded by up_dsleep so it is free when nobody waits) \u2014 see validated fix.diff", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "baseline starve_ms=19481/19520 (waiter asleep 19.5s+ after its messages completed, under a queue of 10ms messages); patched kernel (full nativekernel build + reboot, fix.diff hunks 1-3): starve_ms=0 \u2014 waiter wakes in the same millisecond its victim message completes with the identical churn load (churn_count=151 at T+3s both baseline and patched). Bad behaviour GONE.", "fix_evidence": "findings/poc/DF-5532/run.patched.log (v1 T+3s/T+10s and v2 T+3s samples, both starve_ms=0); findings/poc/DF-5532/fix.diff; guest kernels #1 Oct 7 01:12:42 (v1) and #1 Oct 7 01:28:37 (v2)" } |