# DF-5282 — VERDICT

**Status: reproduced (impact: dos — system-wide hard live-lock);
memory-safety facet (OOB MMIO) is code-certain but bounded and could not
be manifested on this guest (hard blocker: QEMU's EHCI model hardcodes a
4 KB BAR). Confidence: certain.**

## Root cause

`ehci_init()` (`sys/bus/u4b/controller/ehci.c:308,327`) latches two
device-controlled MMIO values with **no validation against the mapped
resource size**:

```c
sc->sc_offs   = EHCI_CAPLENGTH(EREAD4(sc, EHCI_CAPLEN_HCIVERSION)); /* 8 bits */
sc->sc_noport = EHCI_HCS_N_PORTS(sparams);                          /* 4 bits */
```

Every later register access is `bus_space_{read,write}_4(io_hdl,
sc->sc_offs + reg)` (`ehci.h:372-383`). `sc->sc_io_size` (stored by the
glue at `ehci_pci.c:324`) is never consulted anywhere in the driver. The
furthest register touched is `EHCI_PORTSC(sc_noport) = 0x44 + 4*noport`
(`ehci.c:2068` root-intr scan; `ehci.c:3251,3345,3395` roothub requests;
`ehcireg.h:139`), so the driver's whole access window is
`[sc_offs, sc_offs + 0x44 + 4*sc_noport]` ⊆ `[0, 0x17F]`. A malicious or
broken controller that reports `CAPLENGTH=0xFF` (and, for the OOB to
materialize, a BAR smaller than 0x180 — both fully device-controlled)
turns every port-register access into an **out-of-bounds MMIO read/write
of up to (0x17F − bar_size) bytes past the mapping** — the EHCI member of
the xHCI register-geometry family (DF-5268 CAPLENGTH/DBOFF/RTSOFF,
DF-2336/DF-1106 XECP walk). Because CAPLENGTH is 8 bits here, the EHCI
variant's reach is bounded to 383 bytes, unlike xHCI's unbounded 32-bit
offsets.

## Verification methodology (DF-5268 pattern)

QEMU's EHCI hardcodes sane register values, so the malicious latched
value was injected via a KLD (`poison.c`) that walks devclass "ehci" →
softc and stores 0xFF into the already-latched `sc->sc_offs` — bit
identical to what `ehci_init()` would have stored from a CAPLENGTH
register reading 0xFF. Everything downstream is the driver's unmodified
code. Guest: DragonFly 6.5-DEVELOPMENT x86_64, QEMU 7.2 ICH4-style
`usb-ehci` (8086:24cd), BAR 0xfebd1000–0xfebd1fff (0x1000), CAPLENGTH
0x20, 6 ports, ehci as `/boot/kernel/ehci.ko`.

## Observed results

1. **Read path consumes the unvalidated offset** — roothub
   `GET_STATUS(port)` (`ehci.c:3345`) returns real status `0x0500`
   (POWER|HIGH_SPEED) with sane geometry; with `sc_offs=0xFF` the same
   request returns `0x0400` (the POWER bit from `EHCI_PS_PP` is gone)
   because `EOREAD4` now reads io_hdl+0x147/0x15B (unimplemented in-bar).
2. **Write path consumes the unvalidated offset** — baseline
   `SET_FEATURE(UHF_PORT_SUSPEND)` (`ehci.c:3402`, `EOWRITE4`) makes the
   SUSPEND bit visible (0x0504); the identical request with the poison
   loaded returns OK but, after restoring `sc_offs`, the **real** PORTSC
   shows 0x0500 — no SUSPEND — proving the kernel MMIO write landed at
   the wrong offset (io_hdl+0x147) and never reached the port register.
3. **Interrupt path → hard system live-lock** — with `sc_offs=0xFF`
   loaded and the PCD re-enabled by `ehci_pcd_enable` (`ehci.c:1429-1441`,
   itself writing USBINTR/USBSTS via the poisoned offset), a single USB
   device hotplug (QEMU monitor `device_add usb-kbd`) wedges the whole
   machine: `ehci_interrupt()` (`ehci.c:1504-1513`) reads USBSTS via the
   poisoned offset → 0 → exits without acking the real USBSTS.PCD → the
   level-triggered INTx re-fires forever. ssh dies within ~20 s, the
   serial console prints nothing further (no panic — pure live-lock), the
   qemu process spins at 40–85% CPU, `vm.sh status` ⇒ down.
   **Reproduced 3/3 from clean `with-src` snapshots** (2× unpatched,
   1× with the fix module loaded). **Control run (identical hotplug, no
   poison): `ukbd0` attaches and the guest stays healthy.**

## The OOB facet — why it did not manifest here (hard blocker)

Max poisoned offset = 0xFF + 0x44 + 4*15 = **0x17F** (383). QEMU's EHCI
MMIO region is **0x1000** bytes (device model constant; verified via the
monitor BAR listing and `sc_io_size=0x1000` printed by the KLD), so every
poisoned access stays inside the mapping — no page fault, no adjacent
MMIO corruption is *possible* on this guest. Manifesting the OOB requires
an adversarially undersized BAR (< 0x180), which QEMU's `usb-ehci` device
model cannot be configured to expose. On real malicious hardware the same
driver code performs the accesses past the mapping (bounded 0x17F window):
driver-controlled dword writes with device-influenced values at
15 selectable 4-byte slots (`EHCI_PORTSC(1..15)`), plus reads. Adjacent
PCI device registers mapped within 383 bytes after such a BAR are
corruptible.

## Attribution of the live-lock

The wedge requires the geometry to become adversarial **after** the
controller was initialized and real interrupts enabled (the KLD
simulates mid-life corruption / re-negotiation, or a from-boot malicious
device would have real USBINTR never written). A device that can flip its
CAPLENGTH mid-life could also simply assert INTx forever — an IRQ storm
no driver fix can prevent. The **driver-controllable defect** is the
unvalidated latch itself plus the resulting blind/mute interrupt handling
and geometry confusion (items 1–3 above); the fix below closes the
latch-time vector.

## Fix (fix.diff, authored post-verification; never applied to sys/)

Init-time validation in `ehci_init()` immediately after both values are
latched and **before** the first `sc_offs`-relative access
(`ehci_hcreset` at ehci.c:335): reject `sc_offs < 0x08` or
`sc_offs + 0x44 + 4*sc_noport > sc_io_size` with USB_ERR_IOERROR.

Validation performed:
- Patch applied cleanly to the guest's /usr/src; `quickkernel` +
  `installkernel` rebuilt `ehci.ko` containing the guard (string verified
  in the installed module; auto-loaded at boot).
- **No regression (pass-branch)**: patched module boots the real QEMU
  EHCI (offs=0x20, noport=6, io_size=0x1000 → passes), full USB function
  including device enumeration.
- **Reject-branch**: not exercisable on this guest (QEMU EHCI hardcodes
  CAPLENGTH=0x20 / BAR=0x1000) — fix_status **not_testable** for that
  branch.
- **Wedge re-run on the patched kernel**: the post-init corruption
  trigger still wedges the guest (expected — init-time validation cannot
  cover an offset that flips after init; documented above). This vector
  is attributed to generic malicious-device IRQ storming.

## Threat model

Malicious / compromised EHCI silicon or emulated controller (AV:P), or a
config-space/caps-corrupted device. Not reachable by an unprivileged
local user through this file (no ioctls here; /dev/usb nodes are
operator/root). Consequences: (a) deterministic USB-subsystem
misbehavior/hard machine live-lock from a single device plug, (b)
bounded OOB MMIO read/write on adversarially-undersized-BAR hardware.

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