# DF-5532 VERDICT — REPRODUCED (dos)

## Bottom line

`usb_proc_mwait()`'s documented contract ("This function will return
when the USB process message pointed to by "pm" is no longer on a
queue", sys/bus/u4b/usb_process.c:357-362) is not what the
implementation provides: the `up_drain` condvar is **only** signalled
when the dispatcher finds the queue **completely empty**
(usb_process.c:163-167) or when `usb_proc_drain()` tears the process
down (usb_process.c:445-447). A waiter therefore sleeps until *every*
message source on that usb_process quiesces — not until *its* messages
complete — and it sleeps in `cv_wait()` with no PCATCH
(sys/kern/kern_condvar.c:20-44, `wakesig=0`), i.e. uninterruptibly.

## Reproduction (QEMU guest, DragonFly 6.5-DEVELOPMENT, unpatched kernel)

KLD harness `dfharness.c` (family evidence class — production-code rig
on the real `usb_proc_*` symbols, no kernel modification):

- run 1 (run.log): victim message completed at T+0.53 s
  (`victim_ran=1, victim_ran_ms=...959401`); at T+20 s the waiter was
  still asleep (`waiter_done=0`) with only 10 ms of trivial work per
  queue message (churn_count=1000 over 20 s — no stall anywhere);
  20 ms after `stop=1` emptied the queue, the waiter woke:
  `starve_ms=19481`.
- run 2 (run.2.log): identical shape, `starve_ms=19520`.
- spin variant (run.spin.log): with `churn_ticks=0` (callback never
  sleeps — pure self-requeue) the usb_process thread executed
  8.9e9 callbacks over 275 s, monopolizing the thread *and* its lock
  (the waiter starved already inside `lockmgr`), and guest userland
  was starved to the point that sshd stopped answering → guest reset.
  46141778 callbacks were counted in the first second alone.

`starve_ms` is bounded only by the churn's lifetime. In production the
churn is sustained USB traffic (transfer-done queues) or driver poll
loops; there is no bound at all.

## Why the family findings inherit from this

DF-5367/5421/5424/5425/5449/5456/5476/5487/5496/5504 measured
per-driver uninterruptible occupancy of this machinery (slow cfg
callbacks holding the queue). This finding is the independent,
sharper root defect in usb_process.c itself: **even with zero slow
callbacks**, any continuously refilled queue starves mwait waiters
whose messages completed long ago — the wakeup simply never fires.
The unprivileged reachability established by the family applies
verbatim: ucom tcsetattr(2)/close(2) → `ucom_queue_command()` →
`usb_proc_mwait()` (sys/bus/u4b/serial/usb_serial.c:655-672);
`usbd_transfer_unsetup()` → mwait (sys/bus/u4b/usb_transfer.c:1286).

## Exploit chain (what an attacker gets)

Local DoS: an unprivileged user with access to a ucom tty (or any
consumer whose queue is kept alive by traffic) parks syscall threads
in uninterruptible D-state for unbounded time (SIGKILL-immune); with
the spin variant the whole usb_process (and its bus lock) is
monopolized, stalling every other consumer of that queue — including
kernel teardown paths (`usb_proc_drain()` waits for the thread, which
never sleeps → detach hangs → the hub event thread stalls, exactly the
"stalls the USB hub event thread on unplug" the family measured).
No memory-corruption primitive is offered by this defect; the impact
ceiling is unbounded uninterruptible denial of service.

## Fix validation

`fix.diff` adds, after every completed-message dequeue in the
dispatcher, `if (up->up_dsleep) cv_broadcast(&up->up_drain);` (zero
cost when nobody waits — `up_dsleep` is only set by a live mwait
waiter). On the patched kernel (full buildkernel + reboot):

- baseline starve_ms = 19481/19520 (unpatched, above);
- patched: waiter wakes within one queue tick of its victim message
  completing while the identical churn runs on
  (`starve_ms≈10-20`, run.patched.log) — a ~3-orders-of-magnitude
  reduction, bounded by real work, not by unrelated queue activity.

The diff also carries the DF-5533 fix (loop-top suspend check + lock
release around suspend_kproc); both were validated in the same build.

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