ohci_controller_init latches unvalidated device-reported NDP (0-255) into sc_noport: kernel-driven OOB MMIO reads AND writes past the 0x100-byte OHCI register file on every roothub event (DF-5268/5282/5296 register-geometry family, OHCI variant)
Summary
The 8-bit NDP register field is latched with only a retry-on-zero guard - no clip to the OHCI 1.0a max of 15 - and re-latched on every resume: with NDP in [16,255] the roothub status loop OREAD4s ports 1..noport (offsets to 0x474), roothub_exec OREAD4/OWRITE4s 0x50+4*index including UPS_RESET fixed patterns, and bNbrPorts makes the in-kernel uhub thread itself walk all bogus ports - kernel-driven OOB MMIO R/W up to 0x374 past the register file on minimal-BAR implementations. QEMU hard-caps at 15 and models the register read-only - honestly untested (same posture as DF-5296). The isoc 3-page ITD mis-mapping found en route is stream-data corruption only (writes stay inside the transfer's own DMA allocation) - documented not filed. clip-at-latch fix git-apply-clean.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5767 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 5.0 KB | β raw | |
| VERDICT.md | β | 6.8 KB | β raw | |
| fix.diff | β | 743 B | view raw | |
| itd_offset_sim.py | β | 2.1 KB | view raw | |
| qemu-hcd-ohci-reference.c | β | 60.1 KB | view raw | |
| verdict.json | β | 4.0 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| env.txt | β | 294 B | view raw |
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):
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_noportis 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)
ohci_root_intr()(ohci.c:1590-1620), runs on every RHSC interrupt / roothub status poll: *m = sc->sc_noport + 1clamped to8*sizeof(sc_hub_idata)= 256 β the clamp is vacuous (noport β€ 255 as uint8_t, ohci.h:248,251). * Loop (ohci.c:1610-1616) doesOREAD4(sc, OHCI_RH_PORT_STATUS(i))for i = 1..noport β MMIO reads at 0x54..0x50+4noport = 0x474 max*, i.e. up to 0x374 bytes past the 0x100-byte register file.ohci_roothub_exec()port requests (index bounded only bysc_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_RESETat 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.- Hub descriptor (ohci.c:2276-2296):
bNbrPorts = sc_noport(2279) andbDescLength = 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. (TheDeviceRemovable[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
uhubstate). - 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-1955in 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 value15 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 -lshows none), which additionally forecloses device-driven triggering. - Same verification posture as the accepted family sibling DF-5296
(uhci BAR-size trust), filed
untestedfor the identical reason.
Recommended fix
Clamp the latched port count to the spec maximum at both latch sites:
--- 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.)
DF-5767 β VERDICT
Status: untested (hardware-precondition finding β not reproducible on the audit guest; code path proven by trace). Reproduced: no. Impact: none demonstrated (device-trust class). Confidence in the defect: certain.
Narrative
sys/bus/u4b/controller/ohci.c:286 latches the number of root-hub ports
straight from device MMIO (OHCI_GET_NDP(OREAD4(sc, OHCI_RH_DESCRIPTOR_A)),
mask 0xff at ohcireg.h:97) with only a zero-retry guard
(ohci.c:283, the AMD756 "reports 0 ports" erratum workaround). The OHCI 1.0a
spec limits NDP to 15 and defines the operational register file as
0x00-0xFF with port registers at 0x50 + 4*n (ohcireg.h:113); the driver
never clips, and re-latches the value on every resume
(ohci_resume ohci.c:461-476 β ohci_controller_init β ohci.c:282-287).
With sc->sc_noport in [16,255]:
ohci_root_intr()(ohci.c:1610-1616) reads port-status registers 1..noport on every RHSC interrupt β MMIO reads at 0x54..0x474, past the 0x100-byte register file for any noport β₯ 44. The idata clamp at ohci.c:1607-1609 is vacuous (noport+1 β€ 256 = 8*sizeof(sc_hub_idata), ohci.h:251).ohci_roothub_exec()bounds port indices only by sc_noport (ohci.c:2221-2225 CLEAR, 2306-2310 GET, 2324-2328 SET) and then OREAD4/OWRITE4 atOHCI_RH_PORT_STATUS(index)β OOB MMIO reads and writes (fixed UPS_* bit patterns, incl. UPS_RESET) up to offset 0x474.- The hub descriptor reports
bNbrPorts = sc_noport(ohci.c:2279), so the in-kernel uhub thread walks all bogus ports and performs those accesses itself during normal exploration β no crafted userspace request needed once the register lies.
Sibling family findings: DF-5268 (xhci CAPLENGTH/DBOFF/RTSOFF, Medium), DF-5282 (ehci CAPLENGTH/N_PORTS, Medium), DF-5296 (uhci BAR size vs 0x14 register file, Low) β this is the OHCI instance of the same idiom.
Why it is not reproduced on the guest
- QEMU initializes OHCI NDP from the
num-portsproperty and hard-errors aboveOHCI_MAX_PORTS= 15 (QEMUhw/usb/hcd-ohci.c:1953-1955). - QEMU models HcRhDescriptorA as fully read-only
(
OHCI_RHA_RW_MASK = 0x00000000, qemu hcd-ohci.c:189; write handler 1791-1792 applies a zero mask), so a guest KLD cannot poison the register either. - The default audit-guest QEMU command line has no USB controller at all
(
pciconf -lon guest shows none), and NDP > 15 could not occur even with-device usb-ohciadded.
Precondition class: malicious/buggy PCI hardware or emulation β identical to
DF-5296, which was likewise filed untested. A patched QEMU with
num-ports > 15 (or a hardware OHCI whose NDP reads > 15) would exhibit the
OOB accesses immediately on the first roothub status change; on QEMU's
0x1000-byte OHCI MMIO region the reads return 0 (no fault), so even a patched
QEMU shows no crash β the class is cross-device MMIO corruption, visible only
with a minimal-BAR implementation.
Fix validation
Not performable on this guest (bug cannot be manifested β see above);
fix.diff provided, fix_status: not_testable. The patch is a two-line clip
at the latch site plus optional clamp at the consumers; build-tested
mentally against the file (no other consumer of sc_noport permits > 15:
sc_devices[] is OHCI_MAX_DEVICES=128 β unrelated; hubd.bNbrPorts is uByte β
255 fits; DeviceRemovable[32] β l/8 β€ 31 in-bounds, so no secondary overflow
exists to fix).
Negative results worth recording (pass-2 classes hunted and killed)
- Isoc ITD page arithmetic (ohci.c:1900-1965): the flush condition
(length & 0xF000)allows ITDs spanning 3 pages (start page offset + accumulated length up to ~0x23FC). OHCI PSW encodes only {page(bp0), page(be)} (bit 12 page-select; confirmed in QEMUohci_service_iso_td, qemu hcd-ohci.c:694-713), so middle-page frames are mis-mapped into page(be) β verified numerically (sim in pack: frames intended for the middle page DMA to be's page, up to ~3 KB pastbe). All such writes stay inside the transfer's own DMA allocation (page(be) is the buffer's last page; usbd_transfer_setup_sub validates Ξ£frlengths β€ max_data_length via usb_generic.c:1166-1170, sobenever leaves the map; isoc data buffers β₯ 8 KB are page-granular bus_dmamem allocations, so past-beis the allocation's own slack). Net: silent frame-data corruption (integrity), NOT kernel memory corruption. Upstream FreeBSD carries identical code (diffed 2026-10-11); not filed as a security finding. - ITD field encodings correct:
OHCI_ITD_SET_FC(x)=((x)-1)<<243-bit count-1 (ohci.h:152),SET_SF16-bit (ohci.h:147). ntd/nitdallocation formulas (ohci.c:2404-2435) cover worst-case TD consumption (per frame: floor(len/4096)+1 TDs +1 ZLP β€ 2*nframes + max_data/4096); panic safety nets at ohci.c:1297/1901.ohci_non_isoc_done_subcbp/be arithmetic (ohci.c:824-838): underflow and overrun guarded bytemp > td->len β cc=STALL.ohci_isoc_doneplen walk (ohci.c:712-764):nframes>8clamp at 736,len > *plen β 0clamp at 748, walk bounded by the ITD chain built at enter time.- isoc
nframes==0NULL-deref inohci_device_isoc_enter(td_last NULL, ohci.c:1968): unreachable βusbd_transfer_submitrejects nframes==0 (usb_transfer.c:1748) and the stall_pipe special case completes viausbd_pipe_startwithout touching HCD methods (usb_transfer.c:2779-2785);usbd_transfer_setup_subrejects ZERO_NFRAMES (usb_transfer.c:547). ohci_device_donedouble-invocation: guarded by prev==0 no-op in_ohci_remove_qh(ohci.c:691) + shared-stack did_close/did_transfer serialization; ED (qh_start) non-NULL whenever methods assigned (setup success is a precondition for pipe open).- Done-list reaping: this driver does not walk hcca_done_head at all
(completion detected by ED head/tail polling, ohci.c:1037-1038);
intr_q TAILQ mutation during polling handled by
goto repeat(ohci.c:1092-1095). HW-drain protocol = two DMA sets + 1125 us dma delay (ohci.c:2586). - Roothub descriptor writes:
DeviceRemovable[32]β₯ max index 31 β no overflow even at NDP=255 (ohci.c:2288-2293, usb.h:610). - Port reset poll bounded (ohci.c:2340-2352); SET_ADDRESS bounded to OHCI_MAX_DEVICES (ohci.c:2190); SET_CONFIG β {0,1} (ohci.c:2197).
- Toggle-carry latch (ohci.c:1050-1055) reads HC-maintained ED headp β trusted-by-design, matches upstream.
- Detach-veto family: lives in ohci_pci.c (DF-5562, already filed); this
file's
ohci_detach(ohci.c:429-445) is post-children teardown only.
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.
Fix verification
not_testablefix.diff (git-apply clean) clips sc_noport to 15 inside the re-read loop, covering boot and resume latch paths; validation kernel build/boot not performed because the bug cannot be manifested on this guest (QEMU NDP hard cap 15 + read-only HcRhDescriptorA), so baseline==patched==no-observable-change by construction.
findings/poc/DF-5767/fix.diff
Confirmed kernel references
- sys/bus/u4b/controller/ohci.c:286
- sys/bus/u4b/controller/ohci.c:283
- sys/bus/u4b/controller/ohci.c:462
- sys/bus/u4b/controller/ohci.c:1606
- sys/bus/u4b/controller/ohci.c:1612
- sys/bus/u4b/controller/ohci.c:2221
- sys/bus/u4b/controller/ohci.c:2306
- sys/bus/u4b/controller/ohci.c:2324
- sys/bus/u4b/controller/ohci.c:2279
- sys/bus/u4b/controller/ohcireg.h:97
- sys/bus/u4b/controller/ohcireg.h:113
- sys/bus/u4b/controller/ohci.h:251
Detail
Evidence (decisive lines)
findings/poc/DF-5767/README.md (full trace), VERDICT.md (narrative incl. killed pass-2 candidates), fix.diff (applies cleanly, NDP clip to 15), qemu-hcd-ohci-reference.c:189/1791/1953 (QEMU caps+RO register proving untestability), env.txt (guest has no USB controller), itd_offset_sim.py (negative-result proof for the isoc ITD page-math class)
PoC changes
no PoC seed existed; no trigger written because the precondition (controller NDP>15) cannot be instantiated on the audit guest's QEMU (num-ports capped at 15, HcRhDescriptorA read-only, no USB controller on default cmdline)
Verified recommended fix
Clip latched NDP to the OHCI 1.0a maximum of 15 at the latch site (ohci.c:286, both boot and resume paths) with a device_printf warning; defense-in-depth: clamp port indices to 15 in ohci_root_intr/ohci_roothub_exec checks.
Verdict
ohci_controller_init (sys/bus/u4b/controller/ohci.c:282-287) latches the device-reported root-hub port count OHCI_GET_NDP(OREAD4(OHCI_RH_DESCRIPTOR_A)) with an 8-bit mask (ohcireg.h:97) and no upper-bound check β the retry loop only retries on zero (AMD756 erratum) β and re-latches it on every resume (ohci.c:462-476). The OHCI 1.0a spec caps NDP at 15 and the operational register file at 0x100 bytes (port regs at 0x50+4n, ohcireg.h:113). With NDP in [16,255]: ohci_root_intr (ohci.c:1610-1616) OREAD4s port status 1..noport (up to 0x474) on every RHSC interrupt; ohci_roothub_exec bounds port indices only by sc_noport (ohci.c:2221/2306/2324) and issues OOB MMIO reads AND writes (fixed UPS_* bit patterns incl. UPS_RESET) up to 0x474; the hub descriptor propagates bNbrPorts=sc_noport (ohci.c:2279) so the kernel uhub thread drives these OOB accesses itself during port exploration. Same register-geometry-trust family as DF-5268/DF-5282/DF-5296. Not live-testable on the audit guest: QEMU hard-caps OHCI num-ports at 15 (hw/usb/hcd-ohci.c:1953-1955) and models HcRhDescriptorA fully read-only (OHCI_RHA_RW_MASK=0, hcd-ohci.c:189/1791-1792), and the default guest QEMU config has no USB controller at all β identical posture to family sibling DF-5296 (filed untested).
No comments yet.