# DF-5562 — VERDICT

**Status: reproduced (panic, deterministic under the harness trigger) —
fix validated (fixed). Confidence: certain.**

## The finding

`ohci_pci_detach()` (`sys/bus/u4b/controller/ohci_pci.c:323-360`) calls
`device_delete_children(self)` at **:329 and discards the return value**.
When a child device vetoes detach — `device_delete_children` →
`device_delete_child` → `device_detach(child)` returning `EBUSY`
(`sys/kern/subr_bus.c:1284-1292`, `1326-1340`) — the function
nevertheless:

- disables PCI bus mastering (:331),
- resets and halts the OHCI controller (`ohci_detach(sc)`, :337 →
  ohci.c:430-446: `OHCI_INTERRUPT_DISABLE = OHCI_ALL_INTRS`,
  `OHCI_CONTROL = OHCI_HCFS_RESET`),
- tears down the interrupt (:339-345) and releases the IRQ (:348-351),
- releases the MMIO BAR (:352-356),
- frees all USB DMA memory — HCCA, ED trees, TD pools
  (`usb_bus_mem_free_all`, :357 → usb_controller.c:1011-1021),
- returns **0** (:359).

`device_detach()` in `sys/kern/subr_bus.c:2129-2152` then completes the
"successful" detach: prints `ohci0: detached`, sets `DS_NOTPRESENT`, and
`device_set_driver(dev, NULL)` **kfrees `dev->softc`** and NULLs it
(subr_bus.c:1935-1945). The vetoing child is left attached to a
driverless parent with freed IRQ/MMIO/DMA and freed softc — every
subsequent interaction is a NULL-deref or use-after-free, and the caller
(`devclass_delete_driver`, subr_bus.c:889-897 — kldunload, PCI
hot-unplug) is told the unload succeeded.

## Verification (guest, real QEMU OHCI)

`-device pci-ohci` → `ohci0: <Apple KeyLargo/Intrepid USB controller>
mem 0xfebd1000-0xfebd10ff irq 11 at device 3.0 on pci0`, healthy
`usbus0`/`uhub0`/`ugen0.1` at boot (ohci compiled into X86_64_GENERIC,
sys/config/X86_64_GENERIC:305).

Harness `veto.ko` (this directory, DF-5544 v2 pattern): attaches a child
device `veto0` to `ohci0` whose detach method returns `EBUSY` while the
ohci driver is bound — a stand-in for the newbus detach contract — plus
a worker thread that calls `device_detach(ohci0)` (exactly what
`devclass_delete_driver()` at subr_bus.c:897 does) on
`sysctl hw.df5562=1`. The detach is issued from a kernel thread, not the
sysctl handler, because the sysctl path holds the per-CPU
`gd_sysctllock` across handler dispatch while the usb teardown calls
`device_sysctl_fini` (DF-5544 v1 lesson — harness artifact, not the
finding).

Observed (run.log, panic.txt — raw serial capture):

```
usbus0: detached                                <- child 1 deletes cleanly
veto0: DETACH VETO: returning EBUSY             <- child 2 vetoes
ohci0: detached                                 <- swallowed; full teardown
DF5562: device_detach(ohci0) returned 0         <- the lie
DF5562: post: ohci0 driver=(none) state=0
DF5562: ohci0 STILL has 1 child device(s): veto0(attached=1)
[sysctl hw.df5562=2 -> touch parent ctx via orphan]
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x58                    <- NULL softc + field offset
Stopped at df5562_thread+0x91: movq 0x58(%rax),%rcx
db>                                              <- guest dead
```

The panic address 0x58 is `device_get_softc(ohci0)` (NULL, kfree'd by
`device_set_driver`) + `offsetof(struct ohci_softc,
sc_hw.hcca_pc.buffer)` — `sc_hw` is the first member of `ohci_softc`
(ohci.h:223-224) — the orphan's only path to its parent context is
freed memory. The frozen ssh session (kernel died mid-command) is
captured in the run transcript.

## Fix validation (fix.diff)

One-line semantic fix: check and propagate. Applied in guest `/usr/src`,
`make quickkernel` + `make installkernel` (completed Wed Oct 7 18:32:42
UTC 2026, kernel #1: Wed Oct 7 18:31:30 UTC 2026), reboot with the same
`-device pci-ohci`, same harness rebuilt against the new kernel, same
trigger:

- **R1 (veto active)**: `ohci0: child device failed to detach (16);
  refusing controller teardown`; `device_detach returned 16`; post-state
  `driver=ohci state=3` (DS_ATTACHED);
  `hcca_pc.buffer=0xfffff8008df7b000 (was 0xfffff8008df7b000)` and
  ctrl-ED unchanged (**DMA intact**); the exact `hw.df5562=2` touch that
  killed the baseline prints the state safely — **guest alive**.
- **R2 (retry after child cooperates)**: `sysctl hw.df5562=5` then `=1`
  → clean complete detach, `ohci0 STILL has 0 child device(s)`, return
  0 — honest success, guest alive. (During this legitimate teardown the
  resetting controller logs `ohci_interrupt: unrecoverable error,
  controller halted` — expected reset noise, guest survives.)

fix_status: **fixed** (baseline reproduced / patched behavior correct).

## Honest scope / severity calibration

The veto child is synthetic: **no in-tree u4b child detach returns
nonzero today** (`usb_detach` returns 0 unconditionally,
usb_controller.c:189-241), and `device_busy()` propagates `DS_BUSY` up
the tree so a busy child makes `device_detach(ohci0)` fail before the
glue runs. The defect is the violated newbus detach contract plus its
proven consequence (deterministic teardown under a live child → freed
softc/IRQ/MMIO/DMA with reported success). Trigger requires the
privileged detach paths (kldunload / PCI hot-unplug / rescan) plus a
vetoing child — hence **Low** severity, matching the family precedents
DF-5544 (uhci, proven identically this pass), DF-5545 (xhci, proven
identically this pass), DF-5528 (fwohci) and DF-4558 (atapi-cam). Code
inherited from FreeBSD (ohci_pci_detach ignores the return there too).

## Pass-2 negatives recorded during this audit (not reported)

- **DF-5546 MSI-analog (checked per the family-sweep lead): no analog.**
  ohci_pci.c never calls `pci_alloc_1intr`/`pci_alloc_msi`; the IRQ is a
  plain shareable legacy INTx (`bus_alloc_resource_any(SYS_RES_IRQ,
  rid=0, RF_SHAREABLE|RF_ACTIVE)`, :240-242) released with the matching
  rid 0 (:348-351). No polling-mode allocation exists to leak.
- **Attach error unwinding (:318-320)**: sound. `usb_bus_mem_alloc_all`
  failure returns ENOMEM before any resource exists (:214-217; the
  allocator self-cleans). The only reachable unwind entry after
  `bus_setup_intr` is `ohci_init` failure — and unlike `uhci_init`
  (which returns 0 unconditionally), `ohci_init` CAN fail
  (ohci.c:424-425 returns USB_ERR_INVAL when `ohci_controller_init`
  fails); the gate at :333 (`sc_irq_res && sc_intr_hdl`) then reaches
  `ohci_detach`, which is safe in exactly that state: the `sc_tmo_rhsc`
  callout it stops/drains is `usb_callout_init_mtx`'d at ohci.c:401
  BEFORE `ohci_controller_init` at ohci.c:424, and MMIO is still
  mapped. `usb_attach` returns 0 unconditionally
  (usb_controller.c:163-182) so `device_probe_and_attach(bdev)` cannot
  fail. No DF-1105-style NULL-bdev window in this glue (no
  device_printf through bdev on the error path).
- **Suspend/resume wiring (:367-368)**: `bus_generic_suspend/resume`
  pass-through only — no PCI-glue state is touched and no memory-safety
  consequence exists (functional power-management gap at most: this glue
  never parks the controller in D3; not filed).
- **Config-space-supplied sizes**: `sc_io_size = rman_get_size(...)`
  (:238) is the BAR size, consumed by ohci.c (that file's own row);
  nothing in ohci_pci.c allocates by a config-supplied size.
  `ksprintf(sc->sc_vendor, ...)` (:259-297) writes at most 8+NUL bytes
  ("CMDTECH"/"(0x%04x)") into `char sc_vendor[16]` (ohci.h:253) — safe.
- **`ohci_pci_take_controller` SMM/BIOS handoff (:92-109)**: saves
  BAR+INTLINE, sets D0, restores both — boot-time console takeover
  path, no attacker-reachable window.
- **Sun PCIO-2 intpin quirk (:226-227)**: constant write of 4 when
  devid matches and intpin==0 — benign.
- **`return (0)` also lies when `bus_teardown_intr` fails (:339-345,
  "XXX or should we panic?")**: folded into this finding's root cause —
  the fix returns the child error first; a teardown failure would
  additionally warrant propagating `err`, covered by the same
  check-and-propagate discipline.

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