# VERDICT — DF-5394 (drafted during fix build; finalized after validation)

## Status: reproduced (see verdict.json for the machine record)

## The defect (source-certain)

`sys/bus/u4b/controller/usb_controller.c`:

- `usb_attach_sub()` (:810-907) creates 4 USB kthreads and queues the real
  bus bring-up (`attach_msg`) to the explore kthread — **asynchronously**
  (:900-905).
- `usb_bus_attach()` (:700-795) is that async worker: allocates the root hub,
  probes/attaches it, and only at **:791** does `device_set_softc(dev, bus)`
  — the usbus device's softc is NULL for the entire duration; at **:794** it
  arms the 4-second `power_wdog` callout.
- `usb_detach()` (:188-243) treats `device_get_softc(dev) == NULL` (:191,
  :195) as "was never setup properly" and either
  (a) **returns having torn down nothing** — kthreads, usbpf ifp, intr
      config hook, queued attach_msg and the armed callout all outlive the
      controller softc that the caller (HCD detach) immediately frees, or
  (b) when the device object's softc field holds racing heap garbage
      (observed `0xfffffffffffffff8`), **passes the NULL guard** and
      executes `usb_callout_drain(&bus->power_wdog)` at :200 on the wild
      pointer.
- Nothing serialises usb_detach against the async attacher: the explore-proc
  FIFO only orders messages once you get past :210 (`usb_proc_msignal` of
  detach_msg), which the guard at :195 prevents from ever running in case
  (a); in case (b) the drain at :200 runs before any of that.

## Reproduction (5 kernel panics, 2 with crash dumps, identical signatures)

Guest: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (Jul 2 2026 build), QEMU/KVM
6 vCPU, `-device qemu-xhci,id=x0`, xhci.ko modular. Trigger as root:

    kldunload xhci                      # clean-unload boot-attached HCD
    kldload xhci; kldunload xhci        # zero-delay pair

- Runs 1-3 (file serial): first pair crashed each time (run1 DDB stop,
  run2+3 dump+reboot).
- Run 5 (unix-socket serial, snapshot kernel): first pair crashed with a
  clean panic header; crash dump #2 extracted (`/var/crash/vmcore.0`).
- A 40-cycle hammering with fast console survived with ZERO leaked usbus
  kthreads — i.e. when the explore kthread wins to :791, usb_detach takes
  the full-dance path and the explore-proc FIFO makes it correct. The panic
  needs the detacher's `:191` read to land inside the async window; a slow
  console (socket serial) throttles the attacher at its `:736` printf and
  makes the hit essentially deterministic (1st pair, 2/2 socket runs).

### Crash signature (identical in both decoded dumps)

    panic: page fault
    --- trap 0xc, rip = _callout_cancel_or_stop+0x1a ---
    _callout_cancel_or_stop() at _callout_cancel_or_stop+0x1a
    usb_detach() at usb_detach+0x3c
    device_detach / device_delete_child / device_delete_children / xhci_pci_detach

    trap frame: tf_rbx = 0xfffffffffffffff8  (usb_detach's `bus`)
                tf_rdi = 0x488               (&bus->power_wdog = -8 + 0x490)
                tf_addr = 0x498              fault VA = 0x488 + 0x10
                tf_err = 0x2                 SUPERVISOR WRITE
    instruction: lock andl $0xffffefff,0x10(%rdi)

The panic text char-interleaves on the console with `usbus0: 5.0Gbps Super
Speed USB v3.0` — usb_bus_attach's printf at **:736** — proving the explore
kthread was between :736 and :791 on another CPU when usb_detach faulted.
The dump additionally shows a usbus device object whose softc field equals
`bus`: the async `device_set_softc` at :791 landing in the very device
object the detach path is tearing down.

## Why the window is wide on DragonFly specifically

`usb_attach_sub()` calls runtime `config_intrhook_establish()` (:871);
DragonFly's `run_interrupt_driven_config_hooks()` (subr_autoconf.c) then
sleeps ~10 s ("conifhk", prints the "**WARNING** waiting for the following
device to finish configuring" + "Giving up" messages seen in every crash
log) because ran hooks stay on the global list until disestablished. The
attach_msg is therefore queued only ~10 s into kldload; when kldload(2)
returns, usb_bus_attach() has barely begun, and an immediately following
kldunload lands inside the window by construction.

## Impact

Deterministic kernel memory corruption (supervisor write through a wild
pointer derived from freed/reused device memory) → panic/DoS; the
teardown-skip variant is a straightforward UAF of the whole `struct usb_bus`
(including 4 kthread contexts and an armed callout) that fires whenever the
detacher wins the read race. Trigger: kldunload of an HCD module, PCI
hot-unplug, or HCD attach failure after child attach (xhci_pci.c:339) —
local, privileged (or physical). Not reachable by an unprivileged user
directly. Ceiling beyond DoS: the corrupted write is heap-layout dependent
(bus = -8 observed); treat as memory corruption, not a controlled-write
primitive.

## Fix (fix.diff, validated — see below)

1. `usb_attach_sub()`: `device_set_softc(dev, bus)` synchronously, in the
   all-procs-created success branch, BEFORE signalling attach_msg — a racing
   usb_detach then always takes the full-dance path, and the explore-proc
   FIFO (attach_msg queued before detach_msg) provides the ordering.
2. `usb_detach()`: move `usb_callout_drain(&bus->power_wdog)` to AFTER the
   detach-job wait — usb_bus_attach arms the callout as its last action
   (:794) and the detach job runs after the attach job, so the drain now
   always observes the armed callout instead of racing its arming.

Also closes the previously dead error paths deterministically
("Unsupported USB revision" return at :744 and root-hub failure :782-788 now
detach cleanly instead of leaking the procs/usbpf forever, since the softc
is set unconditionally in the success branch of attach_sub).

## Fix validation

- Baseline (stock snapshot kernel): panic on first zero-delay pair with the
  socket serial console; dump extracted (see serial-run5-panic.log,
  /var/crash/vmcore.0).
- Patched (fix.diff, `make nativekernel KERNCONF=X86_64_GENERIC &&
  make installkernel`): 12 zero-delay pairs + hammering with the same socket
  serial console — no panic, clean `usbus0: detached` each cycle, no leaked
  kthreads. (final matrix in run-fixed.log)

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