# DF-5533 VERDICT — REPRODUCED (dos, boot-time)

## Bottom line

`usb_proc_drain()`'s cold-boot branch (sys/bus/u4b/usb_process.c:433-439)
calls `USB_THREAD_SUSPEND(up->up_ptr)` = `suspend_kproc(td, 0)`
(sys/kern/kern_kthread.c:188-204). `suspend_kproc()` sets
TDF_MP_STOPREQ on the target thread and `tsleep(td, 0, "suspkp", 0)`
— sleeping **forever** (timo=0) until the target acknowledges the stop
request. But the `usb_process()` dispatcher thread never acknowledges
anything:

- the only suspend check is `#if 0`'d out at usb_process.c:86-89, and
- even if it were live, the macro it expands to —
  `kthread_suspend_check(curproc)` (usb_process.c:60) — **does not
  exist in the DragonFly kernel** (grep: zero definitions; the real
  primitive is `kproc_suspend_loop()`, kern_kthread.c:209).

Additionally, `suspend_kproc()` is called while the drain thread still
holds `up->up_lock` (acquired at usb_process.c:417) and its
`tsleep(td, ...)` variant releases no lock — so the USB process
thread, which `usb_proc_drain()` just `cv_signal()`ed at
usb_process.c:428-431, cannot re-acquire the lock to run its
`up_gone` exit either. Both threads freeze permanently; every later
taker of that USB bus lock joins the wedge.

## Reproduction (QEMU guest, unpatched kernel)

Harness sysctl `kern.dfcold.fire=1` (dfharness.c) sets `cold=1` and
calls `usb_proc_drain()` on a live usb_process — the exact code path a
USB controller attach-failure takes during autoconfiguration via
`usb_proc_free()`/`usb_proc_drain()`
(sys/bus/u4b/controller/usb_controller.c:210-236, 879-900).

Observed (run.log):

- the `sysctl kern.dfcold.fire=1` process went to **D1** state, wait
  channel **suspkp**, and stayed there for the whole observation
  window (11+ s; it is still there at guest reset);
- `/root/fire.log` — which captures the sysctl's return and the
  `echo rc=$?` — stayed **empty**: `usb_proc_drain()` never returned
  and the `kprintf("WARNING: A USB process has been left suspended")`
  at usb_process.c:437-438 never printed (it is after the
  never-returning call);
- the `dfhz` usb_process thread sat in B-state (blocked re-acquiring
  the bus lock the wedged drain thread holds);
- the rest of the guest kept running: the wedge is bounded to the
  drain thread + the usb_process + future takers of that bus lock —
  which, at real boot time, includes the whole USB subsystem
  teardown path on that controller.

## Impact

Boot-time denial of service, hardware/hypervisor-triggered (a USB
controller whose attach fails during cold autoconfiguration — faulty
hardware, or a QEMU/KVM configuration presenting a broken xhci/ehci).
Not remotely or locally attacker-triggerable in the default
configuration → **Low** severity, `dos` impact.

## Fix validation

`fix.diff` (in this pack; shared with DF-5532's, validated in the same
patched-kernel build) does two things:

1. loop-top `USB_THREAD_SUSPEND_CHECK();` in the dispatcher, with the
   macro fixed to the real primitive `kproc_suspend_loop()` — the same
   idiom every DragonFly kernel daemon uses (vfs_bio.c:2251,
   vfs_sync.c:379, subr_prf.c:931, vfs_mount.c:529);
2. release `up->up_lock` around `suspend_kproc()` so the target thread
   can actually run its suspend check instead of deadlocking on the
   lock the suspender holds while sleeping.

On the patched kernel, `sysctl kern.dfcold.fire=1` **returns
immediately**, the WARNING prints, and no D-state thread is left on
the sysctl path (the usb_process thread is intentionally left parked
suspended — exactly what the WARNING text promises).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
