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)
PoC verification
Evidence pack
findings/poc/DF-4738 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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:
- Collision-blind. When firmware leaves a CardBus socket unprogrammed
(
SECBUS == 0) β the state this block exists to handle β the socket is givencurr_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".) - Unbounded 8-bit space.
curr_bus_numberis anintwith no <=255 check;sc->secbus/sc->subbusareu_int8_t(pccbbvar.h:68-69) andpci_write_config(..., 1)truncates to a byte. Socket #127 drawssecbus_reg = 256-> softcsecbus = 0β the very "unconfigured" sentinel:345treats 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). - Each socket unconditionally consumes two bus numbers (
:353-354) even when one would do, accelerating exhaustion; andsubbusis 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_testablenot_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
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.
No comments yet.