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:
- In module load,
devclass_get_device(cbb_devclass, 0)to obtain the bridgedevice_t. bus_alloc_resource(child, SYS_RES_MEMORY, &rid, 0, ~0, 4096, RF_ACTIVE, ...)on a synthesized child.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_BRIDGECTRLviapciconf -rb pci0:X:Y:0 0x3ebefore and after β thePREFETCHbits 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.
Recommended fix
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.
Related findings
- DF-1489 (sibling): latent NULL-deref in same function's warning branch.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1488 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | verification verdict | 1.0 KB | β raw |
| fix.diff | suggested-fix | git-apply-able fix | 338 B | view 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
fixedVALIDATED: 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
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.
No comments yet.