DF-5282 / verdict.json
{ "finding_id": "DF-5282", "status": "reproduced", "verdict": "ehci_init() latches the device-reported register geometry with no validation: sc_offs = CAPLENGTH (ehci.c:308, 8-bit device MMIO) and sc_noport = HCS_N_PORTS (ehci.c:327, 4-bit); sc_io_size (stored by the glue at ehci_pci.c:324) is never consulted, and every EOREAD4/EOWRITE4 (ehci.h:372-383) adds sc_offs raw. DF-5268 class, bounded variant: the drivers furthest access is sc_offs + EHCI_PORTSC(noport) <= 0x17F, so with an adversarially undersized BAR (< 0x180, fully device-controlled) every port-register access is OOB MMIO read/write of up to (0x17F - bar_size) bytes past the mapping (15 selectable dword write slots at PORTSC(1..15)). Verified on QEMU/KVM DragonFly 6.5-DEVELOPMENT (ICH4-style usb-ehci, BAR 0x1000, CAPLENGTH 0x20, 6 ports): latch-simulation KLD storing 0xFF into sc_offs (bit-identical to init latching a malicious CAPLENGTH) + the drivers unmodified roothub requests show (a) read path consumes the poison (GET_STATUS 0x0500 -> 0x0400, PP bit lost), (b) write path consumes the poison (SET_PORT_SUSPEND returns OK but the real PORTSC is provably untouched), (c) interrupt path: first device hotplug after the poison live-locks the whole machine (handler reads USBSTS via the poisoned offset, exits without acking real PCD, level INTx re-fires forever) \u2014 reproduced 3/3 from clean snapshots with a clean 1/1 control; ssh dead, console silent, no panic text, qemu spinning 40-85% CPU. The OOB itself cannot manifest on this guest (hard blocker: QEMUs EHCI model hardcodes a 4KB BAR > 0x17F max reach) and is documented as conditional on adversarial hardware geometry. Threat actor: malicious/compromised EHCI hardware (AV:P), same class family as DF-5268/DF-2336/DF-1106.", "reproduced": true, "impact": "dos", "confidence": "certain", "exploit_chain": "malicious/compromised EHCI controller reports CAPLENGTH=0xFF (and an undersized BAR for the corruption facet) -> ehci_init latches it unchecked (ehci.c:308) -> all EOREAD4/EOWRITE4 land at io_hdl+0xFF+reg -> roothub/interrupt paths read and write wrong (or out-of-mapping) registers -> deterministic USB geometry confusion; on the first port-change event the unackable PCD interrupt live-locks the entire system (proven on guest); on undersized-BAR hardware the same writes are bounded OOB MMIO corruption of adjacent PCI MMIO space (not demonstrable under QEMUs fixed 4KB bar)", "evidence": "run.log (A/B port-status deltas 0x0500/0x0504 vs 0x0400; poisoned SET_SUSPEND missing the real register; Run A wedge x2 + Run B control); VERDICT.md (full mechanism + OOB blocker); build.log / fix_build_guest.log; fix.diff", "kernel_refs": [ "sys/bus/u4b/controller/ehci.c:308", "sys/bus/u4b/controller/ehci.c:327", "sys/bus/u4b/controller/ehci.h:351", "sys/bus/u4b/controller/ehci.h:372-383", "sys/bus/u4b/controller/ehci_pci.c:316-324", "sys/bus/u4b/controller/ehci.c:1504-1513", "sys/bus/u4b/controller/ehci.c:1429-1441", "sys/bus/u4b/controller/ehci.c:3345", "sys/bus/u4b/controller/ehci.c:3402", "sys/bus/u4b/controller/ehcireg.h:139" ], "poc_changes": "Authored from scratch (no seed). KLD walks devclass ehci -> softc, prints geometry, latches sc_offs=0xFF (uint8_t, bit-identical to init latching CAPLENGTH=0xFF); triggers are genuine roothub control requests via usbconfig do_request and a real QEMU device hotplug. Initial builds needed ehci.cs full include set for ehci_softc_t.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (QEMU/KVM, usb-ehci 8086:24cd, BAR 0x1000)", "runtime_sec": 2340, "guest_dirty": 1, "build_cmd": "cd /root/ehcipwn && make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC", "run_cmd": "kldload /root/ehcipwn/ehcipwn.ko ; QEMU monitor: device_add usb-kbd,id=k1,bus=ehci0.0,port=1", "code_hash": "be3c70ce1de865be4528b1e222abf71bc831b0b8dd78a907ae68f1d0d21fd413", "notes": "Guest reset to clean-source snapshot after validation. The 3/3 wedge is the mid-life-corruption simulation vector (offset flips after real interrupts were enabled); a from-boot malicious CAPLENGTH yields USB-dead without the storm (real USBINTR never written) plus the OOB on undersized-BAR hardware. The prompts EECP/debug-port checklist item lives in ehci_pci.c:517-546 (device-controlled 8-bit PCI-config offset walk, unbounded next-chasing -> flagged for that files audit unit, out of scope here).", "recommended_fix": "Validate sc_offs and sc_noport against sc_io_size in ehci_init() before any operational-register access (fix.diff); reject hostile geometry with USB_ERR_IOERROR", "fix_status": "not_testable", "fix_baseline_reproduced": true, "fix_patched_reproduced": true, "fix_verdict": "fix.diff (init-time geometry guard) applied to the guests /usr/src copy, quickkernel+installkernel rebuilt, patched ehci.ko verified loaded (guard string present; kldstat). No-regression validated: real EHCI passes the guard, full USB function incl. device hotplug. The guards reject branch cannot be exercised on this guest because QEMUs EHCI device model hardcodes CAPLENGTH=0x20 and BAR=0x1000 (no property exposes either), hence not_testable for the from-boot malicious-geometry vector the fix closes. The reproduced wedge (post-init offset corruption + hotplug) still occurs on the patched kernel by construction \u2014 init-time validation cannot cover an offset that changes after init; that vector is equivalent to a malicious device hard-storming its own INTx line and requires platform-level IRQ mitigation, not a driver fix. fix_patched_reproduced=true records exactly that observation (same trigger re-wedged the patched guest), not a failure of the guard against the vector it targets.", "fix_evidence": "fix_build_guest.log (clean hunk apply at line 327, kernel+module rebuild/install, guard string in installed ehci.ko, kldstat shows module loaded, no-regression enumeration); run.log PHASE 5 (patched-kernel trigger re-run: down at t=18s, documented as post-init vector); final vm.sh reset with-src" } |