# DF-5767 — OHCI `sc_noport` latched from unvalidated device-reported NDP (0–255) — OOB MMIO R/W past the 0x100-byte OHCI register file File: `sys/bus/u4b/controller/ohci.c` (pass 2, GLM 5.3) Family: register-geometry trust — OHCI variant of DF-5268 (xhci), DF-5282 (ehci), DF-5296 (uhci). Severity: Low. Bucket: kernleak (hardware-trust class). Confidence: certain (code facts). ## What the code does `ohci_controller_init()` reads the number of downstream ports directly from device MMIO and latches it into `sc->sc_noport` with **no upper-bound check** (sys/bus/u4b/controller/ohci.c:282-287): ```c sc->sc_noport = 0; for (i = 0; (i < 10) && (sc->sc_noport == 0); i++) { usb_pause_mtx(NULL, USB_MS_TO_TICKS(OHCI_READ_DESC_DELAY)); sc->sc_noport = OHCI_GET_NDP(OREAD4(sc, OHCI_RH_DESCRIPTOR_A)); } ``` * The retry loop only guards against NDP == 0 (the documented AMD756 erratum, comment at ohci.c:279-281). A flaky or hostile controller returning 0xFF (or any value > 15) is latched verbatim. * `OHCI_GET_NDP(s) = (s) & 0xff` (ohcireg.h:97) — the full 8-bit register field, while the OHCI 1.0a spec caps NDP at 15 and defines the operational register file as 0x00-0xFF with per-port registers at 0x54 + 4*(n-1) (ohcireg.h:113). * `sc->sc_noport` is **re-latched on every resume**: `ohci_resume()` (ohci.c:461-476) → `ohci_controller_init(sc, 0)` → ohci.c:286. A transient bogus read at resume time replaces the boot-time value. ## Propagation (all in ohci.c) 1. `ohci_root_intr()` (ohci.c:1590-1620), runs on every RHSC interrupt / roothub status poll: * `m = sc->sc_noport + 1` clamped to `8*sizeof(sc_hub_idata)` = 256 — the clamp is **vacuous** (noport ≤ 255 as uint8_t, ohci.h:248,251). * Loop (ohci.c:1610-1616) does `OREAD4(sc, OHCI_RH_PORT_STATUS(i))` for i = 1..noport → MMIO reads at 0x54..0x50+4*noport = **0x474 max**, i.e. up to 0x374 bytes past the 0x100-byte register file. 2. `ohci_roothub_exec()` port requests (index bounded only by `sc_noport`): * GET port status — bounds check ohci.c:2306-2310, read at 2311. * SET port feature — bounds check ohci.c:2324-2328, writes at 2332-2358 (incl. `UPS_RESET` at 2339) → **OOB MMIO writes** of fixed UPS_* bit patterns at 0x54+4*(noport-1). * CLEAR port feature — bounds check ohci.c:2221-2225, writes at 2229-2252, re-read at 2264. 3. Hub descriptor (ohci.c:2276-2296): `bNbrPorts = sc_noport` (2279) and `bDescLength = 8 + ((noport+7)/8)` (2294). The in-kernel uhub driver then enumerates noport ports and itself issues the GET/SET status requests of (2) for every bogus port — the OOB accesses are **kernel-driven**, no userspace request needed beyond plugging/unplugging a device (or any roothub change event) once noport is poisoned. (The `DeviceRemovable[l/8]` writes at 2290 stay in-bounds: array is [32] and l/8 ≤ 31.) ## Why impact is Low * Requires a hostile or misbehaving controller (PCI device / emulation): the register read the driver performs is hardware-controlled. Not triggerable by unprivileged local input on healthy hardware. * The OOB MMIO writes carry only fixed feature-bitmask constants (UPS_PORT_POWER, UPS_RESET, change-acknowledge bits <<16), not attacker data; reads contribute at most one change-bit per bogus port into the hub interrupt bitmap (weak info side channel into `uhub` state). * On implementations whose OHCI BAR is 4 KiB (common), ports 16..255 map *inside* the BAR and nothing happens; on minimal (0x100) BARs the accesses land on adjacent PCI MMIO regions (cross-device register corruption, same ceiling as DF-5296/uhci). ## Why not live-verified on the audit guest (status: untested) * The guest's QEMU caps the OHCI port count at `OHCI_MAX_PORTS` = 15 at device init (`hw/usb/hcd-ohci.c:1953-1955` in QEMU master, fetched 2026-10-11) and models HcRhDescriptorA as fully read-only (`OHCI_RHA_RW_MASK = 0`, qemu hcd-ohci.c:189, 1791-1792) — so no value > 15 can ever be observed by the driver on this guest, not even with a kernel module rewriting the register. * The default audit-guest QEMU config does not instantiate any USB controller at all (`pciconf -l` shows none), which additionally forecloses device-driven triggering. * Same verification posture as the accepted family sibling DF-5296 (uhci BAR-size trust), filed `untested` for the identical reason. ## Recommended fix Clamp the latched port count to the spec maximum at both latch sites: ```diff --- a/sys/bus/u4b/controller/ohci.c +++ b/sys/bus/u4b/controller/ohci.c @@ -283,6 +283,12 @@ sc->sc_noport = 0; for (i = 0; (i < 10) && (sc->sc_noport == 0); i++) { usb_pause_mtx(NULL, USB_MS_TO_TICKS(OHCI_READ_DESC_DELAY)); sc->sc_noport = OHCI_GET_NDP(OREAD4(sc, OHCI_RH_DESCRIPTOR_A)); + if (sc->sc_noport > 15) { + device_printf(sc->sc_bus.bdev, + "invalid NDP %d, clipping to 15\n", + sc->sc_noport); + sc->sc_noport = 15; + } } ``` (Defense-in-depth: additionally clamp in `ohci_root_intr` / `ohci_roothub_exec` index checks, `index > 15`.)