DF-5509 / verdict.json
{ "finding_id": "DF-5509", "status": "reproduced", "reproduced": true, "impact": "dos", "confidence": "certain", "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).", "exploit_chain": "", "evidence": "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)", "kernel_refs": [ "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" ], "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 QEMUs 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: QEMUs 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.", "attempts": 6, "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", "runtime_sec": 3900, "guest_dirty": 1, "build_cmd": "cd /root/ehciarm && make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC", "run_cmd": "./run.sh (phased; see README.md - core sequence: kldunload ehci; pciconf -w -b pci0:3:0 0x6a 1; kenv ehciarm.mode=2; kldload ehciarm.ko; kldload ehci &; wait for waiting for BIOS on serial; QEMU monitor device_add usb-kbd; vmstat -i | grep ehci before/after)", "code_hash": "9e2b0db0be4bf97e206ef56a9b82d48d830a28e01f635302e7783f0f4f2e5f48", "notes": "Pass-2 audit of ehci_pci.c; known findings DF-1092/DF-2335 (unbounded EECP chain) NOT re-reported. Accidental re-confirmation of their class: QEMUs LEGSUP NEXT byte drifted to 0x10 mid-session, which makes the walk cycle forever on the BAR0 dword - noted for the triage team. Guest was reset clean (vm.sh reset with-src) after the run; guest_dirty=1 reflects that the run patched /usr/src and installed a test kernel before the reset.", "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).", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.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_evidence": "fix_validation_3pt.log; fix_build_guest.log" } |