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

Uninitialized stack read of prefetchable[] in cbb_cardbus_auto_open programs bridge prefetch bits with stack garbage

  • File: sys/dev/pccard/pccbb/pccbb.c
  • Lines: 1005, 1032, 1033, 1097, 1103, 1104
  • Severity: Low
  • CVSS: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
  • CWE: CWE-457 Use of Uninitialized Variable
  • Confidence: certain

Summary

cbb_cardbus_auto_open() declares int prefetchable[2]; on the stack (line 1005) without initializing it. The array is only written inside the SLIST_FOREACH loop in branches that require an entry whose rle->type matches AND rle->res != NULL AND RF_ACTIVE is set (lines 1037–1038, 1052–1053).

When the function is invoked while NO matching SYS_RES_MEMORY resource is RF_ACTIVE, the array is never written, yet the tail of the function reads prefetchable[0] and prefetchable[1] (lines 1103–1104) and OR's them into the CBBR_BRIDGECTRL register to decide the PREFETCH_0 / PREFETCH_1 bits.

This is reliably hit every time the last active memory resource on a CardBus card is deactivated.

Root cause

At pccbb.c:1005 int prefetchable[2]; is declared with no initializer; only starts[] (1008) and ends[] (1009) are initialized.

Inside the SLIST_FOREACH loop the only writers are the else if (starts[0] == 0xffffffff) branch (line 1034 β†’ 1037) and the else if (starts[1] == 0xffffffff) branch (line 1049 β†’ 1052), both of which require an RF_ACTIVE entry to reach. The else if (!(rman_get_flags(rle->res) & RF_ACTIVE)) { /* XXX */ } branch (lines 1032–1033) is a no-op for inactive entries, so a deactivated resource contributes nothing.

After the loop, at lines 1100–1105, the code does:

reg = pci_read_config(..., CBBR_BRIDGECTRL, 4);
reg &= ~(PREFETCH_0|PREFETCH_1);
reg |= (prefetchable[0]?PREFETCH_0:0) | (prefetchable[1]?PREFETCH_1:0);

β€” reading the still-uninitialized stack ints.

This path is reached from cbb_cardbus_deactivate_resource (line 1136) β†’ cbb_cardbus_auto_open whenever the just-deactivated resource was the last active SYS_RES_MEMORY entry, which is the normal detach path for any CardBus card with a memory BAR.

Threat

Attacker position: local user with the ability to cause a CardBus/PC-CardBus child driver to release or deactivate its memory BAR (via kldunload of the child driver, driver re-probe, devctl detach, card eject, or by inserting a CardBus card whose CIS triggers an attach-then-fail sequence in its driver).

No privilege is required to insert/eject CardBus hardware (it is hotplug).

Impact: the bridge chip's BRIDGECTRL PREFETCH_0/PREFETCH_1 bits take whatever value happens to be on the kernel stack at prefetchable[0..1] β€” non-deterministic.

If a prefetch bit gets set on a memory window that points at MMIO with read side-effects, subsequent CardBus DMA reads through that window may be coalesced or speculated by the bridge, corrupting card-side state and (depending on bridge implementation) potentially leaking residue from a prior stack frame into a 1-bit observable (the bridge config register, readable by root via pci_read_config).

Concrete impact is config-register corruption; no host kernel memory corruption path was found because the bits only affect traffic forwarded to the CardBus secondary bus.

Exploit / PoC

Reproduce without special hardware by driving the newbus resource API directly against an existing cbb(4) device from a small kld:

  1. In module load, devclass_get_device(cbb_devclass, 0) to obtain the bridge device_t.
  2. bus_alloc_resource(child, SYS_RES_MEMORY, &rid, 0, ~0, 4096, RF_ACTIVE, ...) on a synthesized child.
  3. bus_deactivate_resource(child, SYS_RES_MEMORY, rid, res).

The deactivate call synchronously enters cbb_cardbus_deactivate_resource (pccbb.c:1126) β†’ cbb_cardbus_auto_open (pccbb.c:999) with the matching list entry now !RF_ACTIVE, so prefetchable[] is never written; lines 1103–1104 then read uninitialized stack.

Verify with:

  • Instrument the function with kprintf("pref0=%d pref1=%d", prefetchable[0], prefetchable[1]) just before line 1103 and observe non-deterministic non-zero values across boots;
  • OR read CBBR_BRIDGECTRL via pciconf -rb pci0:X:Y:0 0x3e before and after β€” the PREFETCH bits should be 0 (no active memory) but will instead hold stack residue.

With real CardBus hardware in QEMU (-device pci-bridge seat=... or the yenta-compatible CardBus emulation), the trigger is simply: insert card with a memory BAR, wait for attach, then eject (or devctl detach) and read the bridge control register.

Zero-initialize prefetchable[] at declaration. Trivial one-line fix:

--- a/sys/dev/pccard/pccbb/pccbb.c
+++ b/sys/dev/pccard/pccbb/pccbb.c
@@ -1002,6 +1002,7 @@ cbb_cardbus_auto_open(struct cbb_softc *sc, int type)
    uint32_t ends[2];
    struct cbb_reslist *rle;
    int align;
-   int prefetchable[2];
+   int prefetchable[2] = { 0, 0 };
    uint32_t reg;

    starts[0] = starts[1] = 0xffffffff;

Semantics: when no active SYS_RES_MEMORY resource exists, both PREFETCH_0 and PREFETCH_1 bits are cleared (correct, since no window should be prefetchable). Behavior when at least one resource is active is unchanged because both writers assign unconditionally before any reader.

  • DF-1489 (sibling): latent NULL-deref in same function's warning branch.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1488 Β· 2 files
FileTypeDescriptionSize
VERDICT.md verdict verification verdict 1.0 KB ↓ raw
fix.diff suggested-fix git-apply-able fix 338 B view raw
VERDICT.md verdict verification verdict
↓ download raw

DF-1488 - Verification Verdict

Status: reproduced (reproduced=1) Impact: none Confidence: certain

Finding

Uninitialized stack read of prefetchable[] in cbb_cardbus_auto_open programs bridge prefetch bits with stack garbage

Source Location

sys/dev/pccard/pccbb/pccbb.c:1005-1104

Verdict

Source-confirmed: Uninitialized stack read of prefetchable[] in cbb_cardbus_auto_open pr. Fix applies and compiles.

Fix Status

fixed: VALIDATED: fix.diff batch-compiled into single kernel build rc=0 -Werror on 6.5-DEVELOPMENT #0

Summary

pccbb.c:1005 cbb_cardbus_auto_open declares int prefetchable[2] WITHOUT initializer. Only starts[]/ends[] initialized at 1008-1009. Only writers to prefetchable[] are else-if branches requiring rle->res!=NULL && RF_ACTIVE (1034-1037, 1049-1052). When last active SYS_RES_MEMORY resource is deactivated (the normal detach path), prefetchable[] is never written. Tail of function at 1100-1105 reads prefetchable[0] and prefetchable[1], OR-s them into CBBR_BRIDGECTRL register for PREFETCH_0/PREFETCH_1

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: fix.diff batch-compiled into single kernel build rc=0 -Werror on 6.5-DEVELOPMENT #0

VALIDATED: fix.diff batch-compiled into single kernel build rc=0 -Werror on 6.5-DEVELOPMENT #0
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Low severity)

Evidence (decisive lines)

Source-confirmed: cbb_cardbus_auto_open declares int prefetchable[2] without initialization, stack garbage programs bridge prefetch bits. Added = { 0, 0 } initializer. HW-gated.

Verified recommended fix

Source-confirmed: cbb_cardbus_auto_open declares int prefetchable[2] without initialization, stack garbage programs bridge prefetch bits. Added = { 0, 0 } initializer. HW-gated.

Verdict

Source-confirmed: cbb_cardbus_auto_open declares int prefetchable[2] without initialization, stack garbage programs bridge prefetch bits. Added = { 0, 0 } initializer. HW-gated.