ehci_pci_attach arms ehci_interrupt before controller takeover/init: the pre-init handler is mute (reads USBSTS from the HCSPARAMS capability register, sc_eintrs==0 forces exit WITHOUT acknowledging) β a level-INTx IRQ storm runs for the entire window, including the up-to-5-second BIOS-handoff park, with no code touching USBINTR/USBSTS until ehci_init's hcreset
Summary
bus_setup_intr runs BEFORE take_controller (whose LEGSUP park sleeps up to 5s) and before ehci_init latches sc_offs/sc_eintrs - through the whole window the M_ZERO'd softc makes the armed handler read 'USBSTS' at the HCSPARAMS register and goto done WITHOUT acknowledging: any BIOS-residue or malicious (USBINTR & USBSTS)!=0 state re-fires level INTx until hcreset. Guest-proven on stock code: 311,162 interrupts through one parked attach vs 55 control (5657x), and a PERMANENT variant (~33K intr/s sustained, no self-heal) when the storm starves the attaching thread. Malicious EHCI silicon/emulator or BIOS handoff residue; boot-time needs no privilege. Quiesce-USBINTR/USBSTS-before-setup_intr fix validated (park storm -> delta 0, full enumeration). Distinct root cause from DF-1092/2335 (arming order vs walk bounds).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5509 Β· 18 files| File | Type | Description | Size | |
|---|---|---|---|---|
| mutearm.c | β | 8.1 KB | view raw | |
| Makefile | β | 54 B | β download | |
| build.sh | β | 204 B | view raw | |
| run.sh | β | 198 B | view raw | |
| trigger_endtoend.sh | β | 1.5 KB | view raw | |
| trigger_mute.sh | β | 675 B | view raw | |
| mon.py | β | 537 B | view raw | |
| build.log | β | 1015 B | view raw | |
| run.log | β | 3.1 KB | view raw | |
| serial_console_full.log | β | 20.8 KB | view raw | |
| env.txt | β | 928 B | view raw | |
| fix.diff | β | 1.4 KB | view raw | |
| fix_build_guest.log | β | 1.4 KB | view raw | |
| fix_validation_3pt.log | β | 245 B | view raw | |
| VERDICT.md | β | 7.5 KB | β raw | |
| README.md | β | 3.3 KB | β raw | |
| manifest.json | β | 2.0 KB | view raw | |
| verdict.json | β | 5.3 KB | view raw |
DF-5509 β ehci_pci.c arms ehci_interrupt before controller init: pre-init interrupts are mute (no acknowledge) β level-INTx IRQ storm
What (root cause, one paragraph)
ehci_pci_attach() registers the interrupt handler at
sys/bus/u4b/controller/ehci_pci.c:390-391 β before
ehci_pci_take_controller() (:398, whose BIOS-handoff wait can park the
attach for up to 5 s at :532-545) and before ehci_init() (:451) runs. In
that whole window the fresh M_ZERO softc still holds the pre-init values
sc_offs == 0 and sc_eintrs == 0 (set only at ehci.c:308/353; softc
zeroing confirmed at sys/kern/subr_bus.c:1953-1954). The armed handler
ehci_interrupt() then:
- reads "USBSTS" at
io_hdl + sc_offs(0) + 0x04β the HCSPARAMS capability register, never the real status register (measured: reads 6), - and because
status & sc->sc_eintrs == 0, unconditionally exits viagoto doneat ehci.c:1509-1511 without writing USBSTS to acknowledge.
If the controller arrives with (USBINTR & USBSTS) != 0 β classic
BIOS-ownership-handoff residue, or a malicious/compromised controller β the
level-triggered INTx re-fires for every instant of the window. Nothing
clears USBINTR/USBSTS until ehci_hcreset() at ehci.c:215 (first reached
via ehci.c:335, after the 5 s handoff park). On this 6-vCPU guest the
storm is bounded (~5 s, 311,162 interrupts measured in the window vs 55 in
the control); on fewer CPUs β or whenever the storming IRQ thread starves
the attaching thread β ehci_hcreset is never reached and the storm is
permanent (demonstrated: ~33K interrupts/s sustained indefinitely until the
softc was un-muted).
Guest environment (env.txt)
DragonFly 6.5-DEVELOPMENT x86_64, QEMU 7.2.22 i440fx, -device
usb-ehci,id=ehci0 cold-boot β ehci0@pci0:0:3:0 (8086:24cd), BAR
0xfebd1000/0x1000, IRQ 10, CAPLENGTH 0x20, 6 ports, HCCPARAMS=0x6880
(EECP=0x68 β QEMU-implemented writable LEGSUP semaphores at config 0x68).
ehci as /boot/kernel/ehci.ko.
Build
# in guest (needs /usr/src + /usr/obj/usr/src/sys/X86_64_GENERIC): mkdir -p /root/ehciarm && cp mutearm.c Makefile /root/ehciarm/ cd /root/ehciarm && make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC
Run
See run.sh (exact sequence) and VERDICT.md (measured results).
Expected decisive numbers (unpatched kernel):
- Phase 2 (park + one real port event, stock driver): ehci0 interrupts 393,106 β 704,268 = 311,162 in the attach window.
- Phase 3 control (park, no pending event): 55.
- Phase 4 (pre-init
sc_eintrs=0on live driver + one port event): sustained ~33K/s, load 3.6 and climbing, never recovers until the softc is restored (then stops dead: 1,603,736 β 1,605,667 β tail only).
Fix (fix.diff, validated)
Quiesce the controller's interrupt state before bus_setup_intr():
read CAPLENGTH (absolute, valid pre-init), write USBINTR=0 and
acknowledge-all (USBSTS=0xFFFFFFFF). Patched kernel: same Phase-2 trigger
produces a control-sized delta and USB still enumerates normally.
Threat model
Malicious/compromised EHCI silicon or emulated controller, or BIOS/SMM residue at ownership handoff. Not reachable by an unprivileged local user through this file (boot/kldload-time state; /dev/usb nodes are operator/root). Impact class matches the file's known findings DF-1092 / DF-2335 (Low, hardware-preconditioned kernel DoS).
DF-5509 β VERDICT
Status: reproduced (impact: dos). Confidence: certain. Fix authored after verification and validated on the guest (same trigger: unpatched 311,162 interrupts through the armed window; patched: 0 during the window, USB fully functional).
Root cause
ehci_pci_attach() arms the interrupt handler at
sys/bus/u4b/controller/ehci_pci.c:390-391 β before
ehci_pci_take_controller() (:398, whose LEGSUP BIOS-handoff wait
:517-546 parks the attach for up to 5 s) and before ehci_init() (:451)
latches the geometry. newbus allocates the softc with
M_INTWAIT | M_ZERO (sys/kern/subr_bus.c:1953-1954), so through the whole
window sc_offs == 0 (set only at ehci.c:308) and sc_eintrs == 0
(set only at ehci.c:353). The armed ehci_interrupt() (ehci.c:1490) then:
- reads "USBSTS" at
io_hdl + sc_offs(0) + EHCI_USBSTS(0x04)β i.e. the HCSPARAMS capability register (measured: 6, N_PORTS β never the real status register); - computes
status & sc->sc_eintrs == 0and exits viagoto done(ehci.c:1509-1511) without writing USBSTS to acknowledge.
With (USBINTR & USBSTS) != 0 at handler-arm time β exactly what a BIOS
mid-handoff (or a malicious controller) leaves behind β the level-triggered
INTx re-fires for every instant of the window. The first code that
touches USBINTR/USBSTS after arming is ehci_hcreset() (ehci.c:215,
EOWRITE4 USBCMD=0 + HCRESET) reached only via ehci_init() at ehci.c:335 β
after the up-to-5 s handoff park. This ordering defect is distinct from
the file's known findings (DF-1092/DF-2335: the walk itself can be
unbounded) and from DF-5282 (ehci.c latching unvalidated geometry): the
handler is mute here regardless of geometry correctness, because it is
armed before the softc fields it depends on exist.
Verification methodology
QEMU 7.2.22 i440fx, DragonFly 6.5-DEVELOPMENT, -device usb-ehci,id=ehci0
cold-boot (ehci0@pci0:0:3:0, 8086:24cd, BAR 0xfebd1000/0x1000, IRQ 10,
6 ports). QEMU's usb-ehci implements the LEGSUP extended capability at
EECP=0x68 with writable BIOS_SEM/OS_SEM, so the entire BIOS-handoff
dance is exercisable from the guest (root pciconf -w). The device-side
preconditions (USBINTR=0x3F enabled, USBCMD.RS=1, BIOS_SEM=1 β the
"BIOS residue") were set by a root KLD (ehciarm.ko, DF-5282's poison
methodology) and a QEMU-monitor device_add usb-kbd fired a real
port-change event at a chosen instant. 100% stock driver code in every
measured run; softc re-zeroing on each kldload verified from
subr_bus.c:1943-1954.
Observed results
- Dry run β BIOS_SEM=1, clean controller state: serial console shows
usbus0: waiting for BIOS to give up controlβtimed out waiting for BIOS(~5 s park inside ehci_pci_take_controller), attach then completes normally. The park is deterministic and opens exactly between bus_setup_intr (:391) and ehci_init (:451). - End-to-end bounded storm (stock driver) β armed residue + park + one real port event mid-park: ehci0 interrupt counter 393,106 β 704,268 = 311,162 interrupts through a single attach; attach nonetheless completes (ehci_hcreset clears the state at ehci.c:335) and the keyboard enumerates. On this 6-vCPU guest the storm is bounded; the attaching CPU is saturated for the whole park.
- Control β identical park, no pending event: Ξ = 55 interrupts. A single port event in the armed pre-init window is a 5,657Γ interrupt amplification on stock driver code.
- Permanent variant β pre-init handler state injected on the live,
otherwise-healthy driver (
sc_eintrs = 0β the exact M_ZERO value;sc_offsleft CORRECT at 0x20): one real port event β sustained ~33,000 interrupts/s, ~900 K interrupts over 14 s, load average 3.6 and climbing, no self-heal (nothing will ever acknowledge; no hcreset is coming). Restoringsc_eintrs=0x37stops the storm dead within one delivery (1,603,736 β 1,605,667 β tail only, load 0.00). This is the boot-window outcome on machines where the storming IRQ thread starves the attaching thread (e.g. 1-vCPU):ehci_hcresetis never reached β permanent boot-time machine live-lock.
Why this is the driver's defect (attribution)
A controller that can assert INTx forever can DoS the machine regardless of
any driver β that generic case is out of scope (same attribution DF-5282
made). The driver-controllable defect here is the ordering: the handler
is registered while it provably cannot acknowledge anything (sc_offs=0 β
reads the wrong register; sc_eintrs=0 β masks every bit), and the window
deliberately includes a 5-second sleep. A 2-instruction quiesce before
bus_setup_intr (the validated fix) removes the entire window.
QEMU device-model notes (repro fidelity)
- QEMU's
ehci_opreg_writefor USBINTR does not re-evaluate the INTx line, butehci_port_attachraises PCD and re-evaluates immediately (hw/usb/hcd-ehci.c v7.2: ehci_raise_irq/ehci_update_irq) β so arming before the port event reproduces the handoff-residue semantics exactly. - Real EHCI silicon latches USBSTS.PCD on connect changes regardless of run state; QEMU only does so via paths that need the frame timer, hence the arm also sets USBCMD.RS (the state a BIOS actively servicing ports is in at handoff).
- Amusing side-observation: after the runs, QEMU's LEGSUP dword had NEXT=0x10; the walk then reads the BAR0 dword (0xfebd1000 β NEXT=0x10) at config 0x10 forever β an accidental re-confirmation of the KNOWN DF-1092/DF-2335 unbounded-chain class (not re-reported).
Fix (fix.diff β validated)
In ehci_pci_attach(), immediately after the MMIO resource is mapped and
before bus_setup_intr(): read CAPLENGTH (low byte of capability
register 0 β an absolute offset, valid pre-init), then write
USBINTR = 0 and USBSTS = 0xFFFFFFFF (acknowledge-all). A pending
interrupt can then never be level-asserted at handler-arm time; interrupts
are re-enabled later by ehci_init_sub() (ehci.c:264) once sc_offs and
sc_eintrs are valid.
Validation (see fix_build_guest.log, fix_validation_3pt.log):
- Patch applied to guest /usr/src,
make quickkernel(ehci_pci.c recompiled, log:5530) +make installkernel, rebooted. - Pass-branch: real QEMU EHCI boots and enumerates normally (roothub + 6 keyboards; 72 boot interrupts β normal).
- Same trigger as the baseline storm: T1 park-start 322,795 β T2 mid-park 322,795 (Ξ=0) β T3 post-attach 322,900 (Ξ=105 total) with the port event fired inside the park β vs 311,162 unpatched. Storm eliminated; USB functional.
Threat model / severity
Malicious or compromised EHCI silicon/emulator, or BIOS/SMM residue at ownership handoff; kldload-time re-attach is root-gated, boot-time attach needs no privilege at all beyond the device's own state. Not reachable by an unprivileged local user through this file. Consequence: bounded ~5 s full-machine IRQ storm (measured 311 K interrupts) or permanent boot live-lock when the attaching CPU is starved (demonstrated variant). Severity Low β calibrated with the file's known findings DF-1092 / DF-2335 (same hardware-preconditioned DoS class, same file, Low each).
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
fixedfix.diff applied to guest /usr/src, quickkernel+installkernel, rebooted. Pass-branch: real QEMU EHCI attaches and enumerates normally (72 boot interrupts). Same trigger that produced 311,162 interrupts unpatched now yields delta=0 during the BIOS-handoff park (3-point sampling: 322795 -> 322795 -> 322900) with the port event fired inside the park; USB fully functional afterwards.
fix_validation_3pt.log; fix_build_guest.log
Confirmed kernel references
- sys/bus/u4b/controller/ehci_pci.c:390-391
- sys/bus/u4b/controller/ehci_pci.c:398
- sys/bus/u4b/controller/ehci_pci.c:451
- sys/bus/u4b/controller/ehci_pci.c:517-546
- sys/bus/u4b/controller/ehci.c:308
- sys/bus/u4b/controller/ehci.c:215
- sys/bus/u4b/controller/ehci.c:335
- sys/bus/u4b/controller/ehci.c:353
- sys/bus/u4b/controller/ehci.c:1504-1513
- sys/kern/subr_bus.c:1953-1954
Detail
Evidence (decisive lines)
run.log (all counter measurements: 393106->704268=311162 storm run; 55 control; 1131077->1231156->1603736 permanent variant stopping dead at 1605667 after sc_eintrs restore); serial_console_full.log (ehciarm probe/arm lines, 'usbus0: waiting for BIOS to give up control' / 'timed out waiting for BIOS' per run, ukbd enumeration after each storm); fix_validation_3pt.log (patched: T1 322795, T2 mid-park 322795 delta=0, T3 322900 delta=105 with the port event fired inside the park); fix_build_guest.log (ehci_pci.c recompiled, ehci.ko installed 16:08, pass-branch 72 boot interrupts); VERDICT.md (full narrative incl. QEMU hcd-ehci.c semantics that make the repro faithful)
PoC changes
Wrote the trigger from scratch (no seed): ehciarm.ko implements 3 modes (probe / mute sc_eintrs=0 / arm USBINTR=0x3F+RS on the driver-less controller) via kenv; the 5s BIOS-handoff park is forced with root pciconf writes to QEMU's writable LEGSUP BIOS_SEM (config 0x6a); the port event comes from the QEMU monitor (device_add usb-kbd). First end-to-end attempt did not storm: QEMU's ehci only latches port-change status via paths that need USBCMD.RS, so the arm was corrected to leave the controller RUNNING (the state a BIOS actively servicing ports is in). Learned from QEMU v7.2 hcd-ehci.c that USBINTR writes never re-evaluate the INTx line while ehci_port_attach does - so arming must precede the port event, exactly the handoff-residue order.
Verified recommended fix
In ehci_pci_attach(), immediately after mapping the MMIO BAR and BEFORE bus_setup_intr(): read CAPLENGTH (absolute, low byte of cap reg 0) and write USBINTR=0 plus USBSTS=0xFFFFFFFF (ack-all) so no interrupt can be pending/level-asserted while the pre-init mute handler is armed (fix.diff, validated).
Verdict
ehci_pci_attach() registers ehci_interrupt() at ehci_pci.c:390-391 BEFORE ehci_pci_take_controller() (:398, up-to-5s LEGSUP BIOS-handoff park at :517-546) and BEFORE ehci_init() (:451) sets sc_offs/sc_eintrs (ehci.c:308/353). In that window the M_ZERO softc (subr_bus.c:1953) holds sc_offs=0 and sc_eintrs=0, so the armed handler reads 'USBSTS' from MMIO+0x04 = the HCSPARAMS capability register (measured: 6) and unconditionally exits via goto done WITHOUT acknowledging (ehci.c:1504-1513). Any (USBINTR & USBSTS)!=0 state at handler-arm time - BIOS handoff residue or malicious controller - makes the level INTx re-fire until ehci_hcreset (ehci.c:215/335), the first code after the window. Reproduced end-to-end on stock driver code: one real port event (QEMU monitor device_add) during the BIOS-handoff park of a kldload attach produced 311,162 ehci0 interrupts vs 55 in the no-event control (5,657x); the permanent variant (pre-init sc_eintrs=0 injected on the live driver, sc_offs left correct) sustains ~33,000 intr/s indefinitely with no self-heal until the softc is restored. Bounded ~5s full-machine IRQ storm at boot/attach; permanent boot live-lock when the attaching CPU is starved. Hardware/BIOS-preconditioned, root-gated to re-trigger in a running system: Low, same class as DF-1092/DF-2335 but a distinct root cause (handler-arming order, not the walk bounds).
No comments yet.