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

cbb_pci_attach() CardBus secondary-bus allocator is a driver-static counter seeded at 2: collision-blind against firmware-assigned bus numbers (config-space aliasing of unrelated devices) and unbounded into the u_int8_t secbus/subbus (256 -> 0 'unconfigured' sentinel wrap)

Summary

When firmware leaves a socket unprogrammed, the driver assigns secbus from a static counter=2 without consulting the tree - the socket can be renumbered onto a live bus and both bridges claim type-1 config cycles, so a CardBus card's config accesses can reprogram an unrelated device's BARs/DMA. No >255 bound: socket #127 wraps to the 'unconfigured' sentinel. The in-tree comment says 'XXX EVILE BAD'; FreeBSD rewrote the allocator after 2005. Firmware/hardware-gated; no CardBus on guest. Bound-now + centralized-allocator fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4738 Β· 12 files
FileTypeDescriptionSize
README.md β€” 1.3 KB ↓ raw
VERDICT.md β€” 4.6 KB ↓ raw
cbb_busnum_alloc_demo.c β€” 3.9 KB view raw
build.sh β€” 147 B view raw
run.sh β€” 92 B view raw
build.log β€” 160 B view raw
run.log β€” 827 B view raw
guest_hw_evidence.txt β€” 1004 B view raw
env.txt β€” 399 B view raw
fix.diff β€” 537 B view raw
manifest.json β€” 961 B view raw
verdict.json β€” 3.2 KB view raw

DF-4738 β€” cbb_pci_attach(): collision-blind static CardBus bus-number allocator

File: sys/dev/pccard/pccbb/pccbb_pci.c (pass-2 GLM 5.3 re-audit)

Build (on the audit QEMU guest)

cc -Wall -Wextra -o /tmp/df4738 /tmp/df4738.c

Run

/tmp/df4738

Expected output (decisive lines)

=> COLLISION: two bridges now own bus 2; type-1 config
   cycles for the CardBus card are also claimed by pcie0 ...
socket #127: secbus_reg=256 -> u_int8_t secbus=0, ...
bugs: exit 0

Case 1 shows a firmware-configured PCIe root port keeping bus 2 while a cbb socket left unprogrammed (SECBUS=0) by firmware is renumbered onto the same bus 2 β€” the allocator never checks bus numbers already owned anywhere in the tree. Case 2 shows the shared static counter has no >255 bound: socket #127 draws secbus_reg=256, truncated into the u_int8_t softc field as 0 β€” the exact sentinel the driver itself treats as "unconfigured".

Why a userspace replica

The QEMU audit guest has no CardBus bridge (class 0x0607) PCI device at all β€” see guest_hw_evidence.txt β€” so cbb(4) never attaches and the in-kernel path cannot run. The renumbering arithmetic is deterministic, so a verbatim replica of pccbb_pci.c:299,344-359 with fixture bridges proves both defect axes.

VERDICT.md
↓ download raw

DF-4738 VERDICT β€” collision-blind, unbounded static bus-number allocator in cbb_pci_attach()

Status: not_reproduced at runtime (hardware-gated β€” no CardBus bridge on the audit guest); allocator logic defect certain, demonstrated by a guest-run verbatim replica of the renumbering block; real-world manifestation requires a specific (but historically real) firmware state.

Root cause (path:line)

sys/dev/pccard/pccbb/pccbb_pci.c:299:

static int curr_bus_number = 2; /* XXX EVILE BAD (see below) */

and the renumbering block pccbb_pci.c:344-359:

pribus = pci_read_config(brdev, PCIR_PRIBUS_2, 1);
if (sc->secbus == 0 || sc->pribus != pribus) {
        if (curr_bus_number <= sc->pribus)
                curr_bus_number = sc->pribus + 1;
        ...
        sc->secbus = curr_bus_number++;
        sc->subbus = curr_bus_number++;
        pci_write_config(brdev, PCIR_SECBUS_2, sc->secbus, 1);
        pci_write_config(brdev, PCIR_SUBBUS_2, sc->subbus, 1);
}

Defect axes:

  1. Collision-blind. When firmware leaves a CardBus socket unprogrammed (SECBUS == 0) β€” the state this block exists to handle β€” the socket is given curr_bus_number, which starts at 2 and is adjusted only for this bridge's primary bus (:346-347). Bus numbers that firmware already assigned to other bridges elsewhere in the tree (AGP/PCIe root ports routinely own bus 2..N) are never consulted. Two bridges then own the same bus number: type-1 config cycles intended for the CardBus card's bus are also claimed by the other bridge, so config reads/writes for a CardBus card can hit an unrelated real device β€” a card driver can program its BARs into, say, the display controller, or read the VGA controller's ID as the card's. Config-space corruption of unrelated devices (writable command register/BARs -> device DMA anywhere) is the worst-case fallout. (FreeBSD later rewrote this allocator; DragonFly still carries the 2005 code, as its own comment admits: "XXX EVILE BAD".)
  2. Unbounded 8-bit space. curr_bus_number is an int with no <=255 check; sc->secbus/sc->subbus are u_int8_t (pccbbvar.h:68-69) and pci_write_config(..., 1) truncates to a byte. Socket #127 draws secbus_reg = 256 -> softc secbus = 0 β€” the very "unconfigured" sentinel :345 treats as invalid: re-renumbering on any re-probe/resume while cardbus children still reference bus 0 (= host bus). Subsequent sockets silently alias earlier bus numbers (mod 256).
  3. Each socket unconditionally consumes two bus numbers (:353-354) even when one would do, accelerating exhaustion; and subbus is written before the guard-free increments, so a partial failure leaves config registers inconsistent.

Reachability: attach-time only, driven by PCI config state (firmware). No unprivileged-user trigger β€” this is a firmware/hardware-state defect on machines that actually have CardBus bridges (laptops of the yenta era).

Proof

cbb_busnum_alloc_demo.c replicates :299 and :344-359 verbatim (DEVPRINTF dropped) with fixture bridges; compiled with guest cc 8.3 and executed on the guest (run.log):

  • Case 1 (firmware gave a PCIe root port bus 2; cbb socket SECBUS=0): cbb0 is renumbered to secbus=2 -> => COLLISION: two bridges now own bus 2.
  • Case 2 (128 sockets): socket #127 -> secbus_reg=256 -> u_int8_t secbus=0; no >255 check exists.

Hardware gate: the audit guest's entire PCI inventory is i440FX/PIIX3/PIIX4-PM/virtio β€” no class 0x0607 (CardBus) device (guest_hw_evidence.txt). cbb(4) is compiled into the kernel but never attaches, so the in-kernel path cannot execute; hence not_reproduced with certain allocator logic.

Fix

Minimal guard (fix.diff): fail attach (goto err) when curr_bus_number > 254 instead of truncating into the u_int8_t fields. The collision axis needs the real fix: allocate secondary bus numbers centrally (walk the already-enumerated tree for the highest assigned bus, or delegate to the PCI core's bus-number allocation, as FreeBSD's later pccbb rework does) rather than a driver-static counter seeded at 2.

Fix validation

Not testable on this guest (no CardBus bridge; a single-fix kernel build has no CardBus attach to observe). The guard's arithmetic (254+1 = 255 max) and the truncation semantics are exercised by the demo's case 2.

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_testable
baseline no→ patch + rebuild →patched clean

not_testable: no CardBus bridge exists on the guest, so a single-fix kernel build has no attach to observe; the guard's boundary arithmetic (254+1=255 max, no truncation) is validated by the demo's truncation case.

fix.diff
↓ fix.diffper-fix-DF-4738

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (guest cc 8.3 build; collision case and 256->0 truncation case); guest_hw_evidence.txt (pciconf: no class 0x0607 CardBus device, cbb never attaches); VERDICT.md (line-by-line root cause at pccbb_pci.c:299/345-358, pccbbvar.h:68-69)

PoC changes

No seed existed; authored host-side replica of the renumbering block with fixture bridges, compiled and executed on the guest.

Verified recommended fix

Bound the allocator (fail attach when curr_bus_number > 254, fix.diff) and replace the driver-static counter with centralized bus-number allocation that accounts for buses already assigned elsewhere in the tree (FreeBSD's later pccbb rework).

Verdict

Allocator logic defect certain and demonstrated by a guest-run verbatim replica of the static curr_bus_number renumbering block (run.log: a cbb socket left SECBUS=0 by firmware is assigned bus 2 that a firmware-configured bridge already owns -> 'COLLISION: two bridges now own bus 2'; and socket #127 draws secbus_reg=256 which truncates to u_int8_t secbus=0, the driver's own 'unconfigured' sentinel). Real-world manifestation is firmware/hardware-gated: it requires a machine with a CardBus bridge whose firmware left the socket unprogrammed while assigning overlapping low bus numbers elsewhere (historically real on yenta-era laptops; FreeBSD rewrote this allocator, DragonFly still carries the 2005 code whose own comment says 'XXX EVILE BAD'), or >126 sockets for the truncation axis. The QEMU audit guest has no class 0x0607 device at all (guest_hw_evidence.txt), so cbb(4) never attaches and the in-kernel path cannot run; hence not_reproduced with certain allocator logic and speculative-to-likely field manifestation. Worst-case impact if triggered: CardBus config cycles alias an unrelated bridge's subordinate bus, letting a card's driver read/program an unrelated device's config space (BARs/command) -> system-wide misbehavior or DMA-address corruption; no unprivileged-user trigger exists.