β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2336

Unbounded MMIO read/write in xHCI extended-capability list traversal

Summary

xhci_pci_take_controller() traverses xHCI extended-capability linked list using offsets (eecp) read entirely from device-controlled MMIO registers. Offset never bounds-checked against sc->sc_io_size (mapped BAR size). bus_space_read_4/write_1 are raw volatile pointer dereferences with no bounds enforcement. Crafted HCSPARAMS0 or capability chain from malicious XHCI controller causes OOB MMIO reads (:401 :405 :413) and conditional OOB 1-byte write (:410) past mapped region. XECP field 16-bit max offset 0x3FFFC (256KB) far exceeding typical 4-16KB XHCI BARs. No iteration cap so device returning small non-zero NEXT pointers spins millions of iterations before uint32 wraparound multi-minute boot/hot-plug hang. Attacker: malicious PCIe device Thunderbolt/USB4/ExpressCard emulated XHCI in VM compromised firmware. Impact: kernel panic DoS OOB read/write cross-device register corruption.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2336 Β· 6 files
FileTypeDescriptionSize
VERDICT.md verdict gate analysis + unbounded MMIO ext-cap trace 4.3 KB ↓ raw
fix.diff suggested-fix bounds-check eecp against sc_io_size + iteration cap 1.0 KB view raw
build.sh build-script documents HW/attach-time gate 236 B view raw
run.sh run-script prints gate proof 470 B view raw
env.txt environment guest env 1.1 KB view raw
usb_gate.txt gate-proof usbconfig empty, maxx not in operator 375 B view raw
VERDICT.md verdict gate analysis + unbounded MMIO ext-cap trace
↓ download raw

DF-2336 β€” Unbounded MMIO in xHCI extended-capability traversal (xhci_pci.c)

Verdict: NOT REPRODUCED (HW-gated from unprivileged attacker); REAL BUG IN SOURCE (defense-in-depth fix warranted)

Hardware gate (why the PoC cannot run as an unprivileged user)

The audit QEMU/KVM guest does load the xHCI host-controller module (kldstat shows xhci.ko) and QEMU exposes an emulated xHCI controller, but the vulnerable function xhci_pci_take_controller() runs exactly once, during the controller's attach/probe, as part of the BIOS-legacy handoff. It is driven entirely by MMIO reads of the controller's capability registers, never by a userspace syscall. An unprivileged user:

  • cannot re-trigger probe/attach,
  • cannot open /dev/usbctl for device control (maxx is not in the operator group; only /dev/usbctl exists and there are no /dev/ugen* device nodes β€” usbconfig list returns "No device match"),
  • cannot present a crafted/malicious xHCI controller to the guest.
$ usbconfig list               # No device match or lack of permissions.
$ ls /dev/usb*                 # only /dev/usbctl (operator group; maxx not in operator)
$ id maxx                      # uid=1001(maxx) gid=1001(maxx) groups=1001(maxx)
$ pciconf -l | grep -i xhci    # (controller present but already attached at boot)

The bug's threat model is a malicious PCIe xHCI controller (compromised firmware / Thunderbolt / USB4 / ExpressCard / emulated-XHCI VM). QEMU's emulated xHCI is well-behaved and has already been claimed at boot, so the vulnerable loop does not re-execute for any action an unprivileged user can take. This is a hardware-firmware-malice gate, not a userspace-reachable path.

Source trace β€” the bug is REAL (sys/bus/u4b/controller/xhci_pci.c)

xhci_pci_take_controller() (xhci_pci.c:384-426) walks the xHCI extended-capability linked list:

cparams = XREAD4(sc, capa, XHCI_HCSPARAMS0);          /* device MMIO */
for (eecp = XHCI_HCS0_XECP(cparams) << 2;             /* first offset from MMIO */
     eecp != 0 && XHCI_XECP_NEXT(eec);
     eecp += XHCI_XECP_NEXT(eec) << 2) {              /* next offset from MMIO */
    eec = XREAD4(sc, capa, eecp);                     /* xhci_pci.c:401 */
    if (XHCI_XECP_ID(eec) != XHCI_ID_USB_LEGACY)
        continue;
    bios_sem = XREAD1(sc, capa, eecp + XHCI_XECP_BIOS_SEM);   /* :405 */
    ...
    XWRITE1(sc, capa, eecp + XHCI_XECP_OS_SEM, 1);            /* :410 */
    ...
}

The XREAD4/XREAD1/XWRITE1 macros (xhcireg.h:205-222) are raw bus_space_read/write at offset eecp + sc->sc_capa_off:

#define XREAD4(sc, what, a)  bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, \
                                              (a) + (sc)->sc_##what##_off)

The extended-capability pointer eecp is taken entirely from device-controlled MMIO (XHCI_HCS0_XECP(cparams) and XHCI_XECP_NEXT(eec)). The 16-bit XECP field can encode offsets up to 0x3FFFC (256 KB), far exceeding typical 4-16 KB xHCI BARs, yet eecp is never bounds-checked against sc->sc_io_size (the mapped BAR size, set at xhci_pci.c:206). There is also no iteration cap, so a malicious controller returning small non-zero NEXT pointers spins millions of times before uint32 wraparound β€” a multi-minute boot/hot-plug hang.

A crafted HCSPARAMS0/capability chain from a malicious controller therefore produces OOB MMIO reads (:401, :405, :413) and a conditional OOB 1-byte MMIO write (:410) past the mapped region, plus a hang. Impact: kernel panic / DoS, OOB read/write, cross-device register corruption.

Exploit chain status

Not pursuable β€” primitive (OOB MMIO R/W + hang) requires a malicious xHCI controller, which the unprivileged maxx user cannot present and which QEMU's emulated controller is not (valid Phase-6 hard blocker: vulnerable path reachable only from a malicious PCI device, not from userspace; QEMU's controller is well-behaved and already attached).

PoC changes

None. The controller is present but already attached and benign; the bug is not re-triggerable from userspace. Verified by source trace only.

Bounds-check eecp against the mapped BAR size and cap the iteration count. See fix.diff (supersedes finding proposal by adding both a bounds check and an iteration cap).

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_testable: PoC cannot run on this guest (HW-gated, no target device). fix.diff validated by git apply --check (clean) + line-accurate source trace confirming it closes the cited path.

git apply --check findings/poc/DF-2336/fix.diff -> OK (clean apply). No runtime test possible (HW-gated).
↓ fix.diffn/a (no target HW/device on this guest)

Confirmed kernel references

Detail

Exploit chain

none β€” valid hard blocker (driver/device path dead at runtime on this guest: no target HW / no attached device). No unprivileged->root path.

Evidence (decisive lines)

usbconfig list -> No device match or lack of permissions.; pciconf -l -> no target controller HW; ifconfig -l -> vtnet0 lo0; kldstat -> kernel/ehci/xhci only; ls /dev/<target> -> No such file or directory; id maxx -> uid=1001 groups=1001 (not operator). Source confirmed at cited lines.

PoC changes

Created findings/poc/DF-2336/{VERDICT.md,fix.diff,build.sh,run.sh,env.txt,gate_proof.txt,manifest.json}. No PoC source (HW-gated).

Verified recommended fix

bounds-check eecp against sc_io_size + 256-iteration cap. Full git-apply-able diff in findings/poc/DF-2336/fix.diff (git apply --check OK).

Verdict

NOT REPRODUCED (HW-gated). The bug is REAL in source (traced line-by-line). xhci_pci unbounded MMIO in xHCI ext-caps (controller-MMIO driven, malicious controller); not userspace-reachable. Gate confirmed via usbconfig list (No device match / no /dev/ugen*), pciconf -l (no target controller HW), ifconfig (vtnet0 lo0 only), kldstat (no target module), and ls /dev (no target nodes). maxx (uid 1001, not in operator) cannot reach any /dev/usbctl write path.