# DF-5282 — EHCI unvalidated register geometry (sc_offs / sc_noport)

## What

`ehci_init()` latches the device-reported register geometry with no
validation:

- `sys/bus/u4b/controller/ehci.c:308` — `sc->sc_offs = EHCI_CAPLENGTH(...)`
  (8-bit, device MMIO)
- `sys/bus/u4b/controller/ehci.c:327` — `sc->sc_noport = EHCI_HCS_N_PORTS(...)`
  (4-bit, device MMIO)

`sc->sc_io_size` (BAR size, `ehci_pci.c:324`) is never consulted. Every
subsequent `EOREAD4`/`EOWRITE4` (`ehci.h:372-383`) adds `sc_offs` to the
register offset raw. Furthest reach: `0xFF + EHCI_PORTSC(15) = 0x17F` bytes
past the BAR base. With an adversarially undersized BAR (< 0x180 — also
device-controlled) every port-register access is out-of-bounds MMIO
read/write (DF-5268 class, bounded variant). Same defect family as
DF-5268/DF-2336/DF-1106 (xHCI).

## Guest environment

- DragonFly 6.5-DEVELOPMENT x86_64 (QEMU/KVM, QEMU 7.2), ICH4-style
  `usb-ehci` device: BAR 0x1000 @ 0xfebd1000, CAPLENGTH=0x20, 6 ports.
- ehci is a KLD module (`/boot/kernel/ehci.ko`).

## Build

    # in guest (needs /usr/src + /usr/obj kernel build dir):
    mkdir -p /root/ehcipwn && cp poison.c Makefile /root/ehcipwn/
    cd /root/ehcipwn && make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC

## Run (A/B demonstration — read/write path geometry confusion)

    ./baseline.sh          # roothub GET_STATUS / SET+CLEAR SUSPEND roundtrip
    kldload /root/ehcipwn/ehcipwn.ko     # latches sc_offs=0xFF (== malicious CAPLENGTH)
    ./poisoned.sh          # same requests now hit io_hdl+0x147/0x15B
    kldunload ehcipwn

Expected: baseline port status 0x0504 (POWER|HIGH|SUSPEND) / 0x0500;
poisoned GET_STATUS returns 0x0400 (POWER bit lost — wrong register read);
poisoned SET_SUSPEND "succeeds" but the real PORTSC is untouched (0x0500
after restore).

## Run (hard live-lock — interrupt path)

    kldload /root/ehcipwn/ehcipwn.ko
    # QEMU monitor: device_add usb-kbd,id=k1,bus=ehci0.0,port=1

Expected (unpatched kernel): the first port-change interrupt is read via
the poisoned offset (returns 0), the handler exits without acking the real
USBSTS.PCD, the level-triggered INTx re-fires forever -> the guest
live-locks: ssh dies within ~20 s, serial console silent, qemu spins at
40-85% CPU, `vm.sh status` => down. Reproduced 3/3 from clean snapshots
(2 baseline + 1 patched-module kernel). Control run (identical hotplug,
no poison): ukbd0 attaches, guest healthy.

## Fix

`fix.diff` — init-time geometry validation in `ehci_init()` before any
sc_offs-relative access. Rejects hostile CAPLENGTH/N_PORTS combinations
against sc_io_size (closes the from-boot malicious-geometry latching and
the undersized-BAR OOB). No-regression verified on the patched module
(real EHCI passes, full USB function incl. hotplug). The reject branch
cannot be exercised on QEMU (its EHCI model hardcodes CAPLENGTH=0x20 /
BAR=0x1000); the post-init corruption vector is equivalent to a device
storming its own IRQ line — see VERDICT.md.
