# DF-5545 VERDICT — reproduced (panic), fix validated

## Bottom line

`xhci_pci_detach()` at `sys/bus/u4b/controller/xhci_pci.c:355` ignores
the return of `device_delete_children(self)`. Demonstrated live on the
QEMU guest with a real xHCI controller (first `qemu-xhci`, identical
mechanics later re-validated on `nec-usb-xhci`): with one child vetoing
detach, `device_detach(xhci0)` reported complete success (`xhci0:
detached`), left the vetoing child ATTACHED to the now-driverless xhci0
whose softc had been kfree'd/NULLed by `device_set_driver(dev, NULL)`
(subr_bus.c:2156) — the next touch of the parent context through the
orphan was a `Fatal trap 12` page fault (NULL softc + 0x20 field offset,
`df5545_thread+0x91 movq 0x20(%rax),%rdx`), guest dead in DDB.

This is the xHCI instance of the family idiom: DF-5544 (uhci_pci.c:374,
proven the same way this pass), DF-5528 (fwohci_pci.c), DF-4558
(atapi-cam.c). FreeBSD heritage: the same swallow exists in
 FreeBSD's xhci_pci.c.

## Root-cause chain (path:line)

1. `xhci_pci.c:355` — `device_delete_children(self);` return discarded.
2. `subr_bus.c:1292` — child veto (`device_detach(child) != 0`) makes it
   return EBUSY; the still-attached child stays in xhci0's children list.
3. `xhci_pci.c:357-378` — teardown proceeds anyway: callout drain,
   controller halt, busmaster off, intr teardown, IRQ released, MSI
   released, **MMIO BAR released** (:372-376), **`xhci_uninit(sc)`**
   (:378) frees all DMA (event/command ring page, DCBAA page,
   scratchpads; xhci.c:675-686 `usb_bus_mem_free_all`) and destroys
   sc_cmd_cv/sc_cmd_lock.
4. `xhci_pci.c:380` — `return (0)`: detach lies to the caller.
5. `subr_bus.c:2151-2157` — success path: `devclass_delete_device`,
   `dev->state = DS_NOTPRESENT`, `device_set_driver(dev, NULL)` ⇒
   softc kfree'd, `device_get_softc(xhci0)` ⇒ NULL.
6. Orphaned attached child + freed softc + unmapped BAR + freed DMA ⇒
   NULL-deref (observed) or UAF on freed DMA pages (cmd 3 path).

## Runs

- Baseline (kernel #0 Thu Jul 2 06:02:54 2026, pristine): `run.sh`
  transcript in run.log; decisive serial output in panic.txt /
  serial_console_full.log. Guest panicked at `hw.df5545=2`; the ssh
  session hung (DDB), forcing the documented kill.
- Fixed kernel: both family fixes (DF-5545 + DF-5546) applied to the
  guest's /usr/src in one rebuild (fix_build.log shows
  `-c .../xhci_pci.c` recompiled and "Kernel install ... completed";
  /boot/kernel/kernel md5 29a0c51c...). Identical trigger:
  `returned 16 -- honest failure`, `xhci0: child device failed to
  detach; refusing controller teardown`, `driver=xhci state=3`
  (DS_ATTACHED), ring/DCBAA buffers byte-identical to the pre-detach
  snapshot, **no panic** on the exact cmd2 that killed the baseline
  (run.fix.log). After `hw.df5545=5` (child cooperates), retry detaches
  cleanly: `returned 0`, `STILL has 0 child device(s)`.

## Exploitability

Trigger context is privileged (kldunload / PCI hot-unplug equivalent —
the harness drives `device_detach(xhci0)` exactly as
`devclass_delete_driver` does, subr_bus.c:885-897). No unprivileged
reach through this file: no in-tree u4b child detach returns nonzero
(`usb_detach` is unconditional-0 at usb_controller.c:242), and a DS_BUSY
child propagates EBUSY upward before this code can swallow it. So:
latent-API-hazard + proven memory-safety consequence ⇒ Low severity,
mechanics certain. No route from unprivileged input was found (checked:
ugen open state does not veto newbus detach; the u4b device tree is not
newbus-children of usbus).

## Pass-2 negatives recorded for this file

- Attach unwind (xhci_pci.c:220-225) releasing only the MMIO BAR is
  correct: `usb_bus_mem_alloc_all` self-cleans on partial failure
  (usb_controller.c:986-988); no xhci_uninit needed (cv/lock are
  initialized only after alloc_all succeeds, xhci.c:657-663).
- No DF-5509 analog: the handler is armed at :279 only AFTER xhci_init
  set the oper/runt offsets (xhci.c:625-630), and `xhci_interrupt_locked`
  ACKs unconditionally (xhci.c:1654-1660: any nonzero USBSTS is written
  back, IMAN pending cleared) — no mute-handler IRQ storm window.
- MSI count math: DF branch uses `pci_alloc_1intr` with fixed count=1
  (pci.c:4348-4375); no MSI-X count math exists in this file; no
  DF-5464-style KASSERT-only validation on this path (that class was
  icu_abi-internal).
- Suspend/resume delegated to usbus children (`usb_suspend/usb_resume`
  exist, usb_controller.c:105-108) which drive
  `xhci_set_hw_power_sleep` via bus_methods (xhci.c:4489); glue wiring
  complete, nothing size/count-bearing.
- Quirk tables (devid→usedma32 at :209-218, Intel route/imod at
  :296-308) are compile-time constant switches; `sc_vendor` ksprintf
  "0x%04x" into char[16] is bounded (xhci.h).
- `sc_io_size` (BAR size, :206) is never used as a bound by xhci (only
  uhci/ohci use it for barriers) — nothing config-space-supplied feeds
  sizes/counts in this file beyond the already-filed DF-1106/DF-2336
  extended-capability walk (not re-reported here).
- BIOS handoff wait (:411-423) is bounded (500 × 10 ms) and requires a
  BIOS-owned semaphore; not attacker-reachable.

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