# DF-5546 VERDICT — reproduced (resource leak), fix validated

## Bottom line

In forced-polling mode (`hw.usb.xhci.use_polling=1`), the xHCI PCI glue
allocates classic-MSI vectors for the controller
(`pci_alloc_1intr`, xhci_pci.c:234, unconditional) but never creates the
IRQ resource (:253-268), and its detach releases MSI only when an IRQ
resource exists (:366-371). Proven live on the guest with a classic-MSI
NEC uPD720200 (`-device nec-usb-xhci,msi=on,msix=off`): polling-mode
attach leaves `sc_irq_rid=1, sc_irq_res=NULL, sc_intr_hdl=NULL`; after a
fully clean `device_detach(xhci0)` (usbus detaches fine — no veto
involved), `pci_release_msi(xhci0)` still had work to do — it returned 0
and released the vectors the driver leaked. On the fixed kernel (fix.diff
applied in the guest, same rebuild as DF-5545) the identical run prints
`probe returned 19 (clean: nothing left to release)`.

## Root-cause chain (path:line)

1. `xhci_pci.c:232-235` — `pci_alloc_1intr(self, xhci_use_msi /*=1*/,
   &rid, &irq_flags)`: on MSI-capable hardware this runs
   `pci_alloc_msi(dev, &rid, 1, cpu)` (sys/bus/pci/pci.c:4365) →
   rid=1, `msi_alloc=1`, vector + resource-list entries created.
2. `xhci_pci.c:253` — polling tunable set → the whole IRQ-resource block
   is skipped; attach proceeds with the callout poller (:320-324).
3. `xhci_pci.c:362-371` — detach: `sc->sc_irq_res == NULL` → the block
   containing `pci_release_msi(self)` is skipped entirely.
4. Nothing else in the driver or the PCI layer releases driver-allocated
   MSI on driver detach → leaked `msi_alloc` message + rid entries per
   attach/detach cycle; the pool is finite (MSI vector space shared by
   all devices; exhaustion makes later `pci_alloc_msi` fail — devices
   silently fall back to INTx or fail to arm interrupts).

## Hardware precondition (documented hunt)

First attempt used `-device qemu-xhci` (1b36:000d): that device
implements **MSI-X only** (pciconf: `cap 11[90] = MSI-X`, no cap 05)
while DF's `pci_alloc_1intr`/`pci_alloc_msi` path allocates classic MSI
only — nothing was allocated, probe correctly returned ENODEV
(run.hw-precondition.log). Switching to
`-device nec-usb-xhci,msi=on,msix=off` (classic MSI present: `cap
05[70] = MSI supports 16 messages, 64 bit`; chip 0x01941033 is in this
driver's own probe/quirk tables at xhci_pci.c:94 and :210) satisfied the
precondition and the leak reproduced immediately. Real xHCI silicon
commonly exposes classic MSI (the NEC part in the driver's own table
does), so the precondition is ordinary hardware + the diagnostic
tunable.

## Runs

- Baseline (pristine kernel, polling boot, NEC controller):
  `sc_irq_rid=1 sc_irq_res=0 sc_intr_hdl=0` → detach returns 0 →
  `pci_release_msi(xhci0) probe returned 0 (LEAK: driver left MSI
  vectors allocated)` (run.log). Harness releases the leaked vectors
  itself so the guest stays usable.
- Fixed kernel (combined DF-5545+DF-5546 rebuild, same controller +
  tunable, fresh boot): identical pre-state `sc_irq_rid=1 sc_irq_res=0`,
  detach returns 0, `probe returned 19 (clean: nothing left to release)`
  (run.fix.log).

## Exploitability

Privileged-only: boot tunable (root, loader.conf) + module-unload /
hot-unplug context. Impact = kernel resource leak (vector pool + PCI
resource-list entries), a slow local DoS on device bring-up — not an
info leak, not memory corruption. Low severity, mechanics certain.

## Related

DF-5464 (icu MSI/MSI-X count math, different subsystem/class) was
checked against this file: the DF branch here uses
`pci_alloc_1intr` with a fixed count of 1 — no count arithmetic, no
KASSERT-only validation on this path. Not a recurrence.

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