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

pccard_attach_card re-entry on card-detect bounce: previous function list orphaned (unbounded kernel memory leak) and duplicate children stacked over live drivers

Summary

No already-attached guard: the cbb event thread re-runs the insert path while the card is present and a CD glitch inside the 1s debounce is seen as present - STAILQ_INIT orphans the previous pf/cfe chains (hundreds of KB/cycle permanent leak, free path only walks the current list), device_add_child stacks duplicates over live instances, final removal leaves zombie devices over destroyed windows. Evil card wiggling CD1#/CD2# every ~2s = unbounded kmem exhaustion. Hardware-gated (static trigger trace). pf_head-non-empty EBUSY guard fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5356 Β· 21 files
FileTypeDescriptionSize
rig2_main.c β€” 18.2 KB view raw
rig2_shims.h β€” 13.8 KB view raw
extract2.sh β€” 8.1 KB view raw
build.sh β€” 66 B view raw
run.sh β€” 123 B view raw
run_matrix.sh β€” 1.9 KB view raw
run.log β€” 8.0 KB view raw
fixed_run.log β€” 1.1 KB view raw
build.log β€” 2.5 KB view raw
extract2_manifest.txt β€” 2.4 KB view raw
matrix.log β€” 1.6 KB view raw
env.txt β€” 323 B view raw
fix.diff β€” 601 B view raw
fixes/ β€” 4.0 KB ↓ download
VERDICT.md β€” 6.3 KB ↓ raw
README.md β€” 1.6 KB ↓ raw
verdict.json β€” 6.2 KB view raw
postboot_run.log β€” 16.8 KB view raw
postboot_extract_manifest.txt β€” 2.4 KB view raw
kernel_apply.log β€” 231 B view raw
kernel_build.tail.log β€” 4.6 KB view raw

DF-5356 -- pccard_attach_card re-entry on card-detect bounce (leak + duplicate children)

Impact: unbounded kernel memory leak (376+ bytes per CD bounce cycle, linear growth), zombie device tree with duplicate driver instances over destroyed bridge windows, leaked I/O + CCR windows (which prime DF-5355's busy-panic). Physical-access kmem-exhaustion DoS driven by an evil card wiggling CD1#/CD2# every ~2 s.

Reproduce (verbatim rig -- shared with DF-2318/DF-5355)

# on the DragonFly guest (pristine /usr/src):
sh build.sh            # machine-extracts production pccard.c/cis/subr_bus code
./rig2 reentry_leak    # the DF-5356 scenario (51 CD-bounce cycles)
./rig2 benign_cycle    # negative control (attach/detach, zero leak)
sh run_matrix.sh matrix

Success criterion (baseline):

[after attach #2 (bounce)]  ORPHANED: pf=1 cfe=1 (leak=376 bytes) | zombies=2
TOTAL after 51 bounce cycles: orphaned-kmalloc-blocks=102 leak=19176 bytes |
                              zombie-children=51 | io-held=28 ccr-held=28

With the fix (fixed_run.log / postboot): TOTAL ... blocks=0 leak=0 zombies=0 io-held=0 ccr-held=0, while benign_cycle still attaches 51 times.

The full rig lives in ../DF-2318/; this pack is a self-contained copy.

Fix

fix.diff (== fixes/df5356.diff): EBUSY guard at the top of pccard_attach_card when sc->card.pf_head is non-empty (the removal path empties the list at pccard.c:317-324, keeping the marker correct). The originally-seeded hunk was malformed and was regenerated; validated by rig A/B + combined-fix kernel boot. Never applied to sys/.

VERDICT.md
↓ download raw

DF-5356 -- VERDICT

Verdict in one line

REPRODUCED (verbatim-rig level; deterministic, linear unbounded growth) -- impact: dos (unbounded kernel-memory leak + device-tree corruption). pccard_attach_card has no already-attached guard: a card-detect bounce that re-runs the bridge insert path while the card is present re-enters it with no intervening detach; STAILQ_INIT at pccard.c:189 (and again inside pccard_read_cis, pccard_cis.c:104) orphans the previous pf/cfe chains (permanent kmalloc leak), and device_add_child at :256 stacks duplicate children over live driver instances. The kernel entry is hardware-gated (no PCMCIA bridge in QEMU -- env.txt); the software behavior is proven on the byte-identical extracted production code.

What was executed

Same verbatim rig as DF-2318/DF-5355 (extract2.sh, sha256-gated, byte-identical pccard_attach_card/pccard_detach_card/pccard_read_cis chain + ledgered kmalloc + tracked device tree). Scenario reentry_leak drives the exact production re-entry sequence the cbb event thread produces on a CD glitch (pccbb.c:621-627 clears CBB_CARD_OK and wakes the thread; the thread's 1 s debounce at pccbb.c:479-483 still sees the card present; cbb_insert at :458-461 -> exca_insert at exca.c:759-764 -> CARD_ATTACH_CARD unconditionally -- no layer checks whether the card is already attached):

attach #1 (card inserted)         -> pf/cfe chain A, child 0, driver attached
attach #2 (CD bounce, NO detach)  -> STAILQ_INIT orphans chain A,
                                     child 1 created OVER THE LIVE CARD,
                                     both drivers running
detach   (final removal)          -> walks only the CURRENT list (pccard.c:303,
                                     :317-324): chain B freed, chain A and
                                     child 0 survive forever
x 51 cycles

The measured damage (run.log)

[after attach #2 (bounce)]  ORPHANED: pf=1 cfe=1 (leak=376 bytes) | zombies=2 | io-held=2 ccr-held=2
[after detach]              ORPHANED: pf=1 cfe=1 (leak=376 bytes) | zombies=1 | io-held=1 ccr-held=1
TOTAL after 51 bounce cycles: orphaned-kmalloc-blocks=102 leak=19176 bytes |
                              zombie-children=51 | io-held=28 ccr-held=28
  • 376 bytes permanently leaked per bounce cycle (pf 216 + cfe 160, canary-verified live-and-unreachable from the current list) -- exactly the per-cycle cost of a single-function card; multi-function / multi-CFE cards cost hundreds of KB per cycle (finding's estimate), and the growth is linear and unbounded (the leak continues even after the bridge windows are exhausted: cycles past window exhaustion still orphan pf/cfe chains).
  • 51 zombie children, each a device_add_child over an orphaned function whose driver instance stays attached in production; after final removal the duplicates survive over destroyed bridge windows.
  • 28 leaked I/O windows + 28 leaked CCR mappings: the orphaned rl entries keep res busy forever (the free path only walks the current list), so the bridge windows are never returned -- this is also the priming condition for DF-5355's busy-panic on subsequent inserts.

Negative control benign_cycle (attach -> detach, no bounce, x51): leak=0, zombies=0, io-held=0 -- the leak is exactly the no-detach re-attach, not the attach/detach machinery.

Fix validation

  • Single-fix A/B (matrix.log): if (!STAILQ_EMPTY(&sc->card.pf_head)) return (EBUSY); at the top of pccard_attach_card -> reentry_leak TOTAL becomes 0/0/0/0 across all 51 cycles; benign_cycle (attach/detach x51) still works (the removal path empties the list at :317-324, keeping the marker correct); no_vers1 (DF-2318) and busypanic (DF-5355) scenarios unchanged under this variant.
  • Combined three-fix kernel: -Werror nativekernel rc=0, installed, booted (kernel #1, sha256 4faad36e...); rig re-extracted from the RUNNING kernel's source (--allow-patched) and re-driven: fixed_run.log shows 0 leak / 0 zombies / 0 held windows over all 51 bounce cycles while the healthy attach/detach path still works. Guest reset to the clean-source snapshot afterwards.

NOTE on the seeded fix.diff: the originally-seeded hunk was malformed (patch: malformed patch at line 19) and placed declarations after statements; it was regenerated with the same semantics (EBUSY guard) placed after the declaration block -- this pack's fix.diff is the validated one.

Kernel references

Exploit chain

evil card grounds CD1#/CD2# -> card attaches -> card ungrounds CD briefly (CSC interrupt; pccbb.c:621-627 clears CBB_CARD_OK and wakes the event thread) -> card regrounds CD -> thread debounce (:479-483) observes present -> cbb_insert -> exca_insert -> CARD_ATTACH_CARD -> pccard_attach_card with no intervening detach -> previous pf/cfe chain orphaned (376+ bytes/cycle permanent kmalloc leak; linear, unbounded), duplicate children stacked over live drivers, bridge I/O + CCR windows leaked -> repeat every ~2 s: kernel memory exhaustion (kmem malloc zone pressure) + zombie device tree; window exhaustion also primes DF-5355's busy-panic on the next insert. The kernel's own comment at pccbb.c:562-567 treats CD jitter as routine.

Environment

Same guest/rig as DF-2318 (env.txt). Artifacts: run.log (baseline decisive, 51-cycle TOTAL), fixed_run.log (running-kernel fixed source, 0 leak), matrix.log, build.log, extract2_manifest.txt.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED. (1) Single-fix rig A/B (matrix.log): reentry_leak TOTAL becomes 0 orphaned / 0 bytes / 0 zombies / 0 held windows across all 51 bounce cycles; benign_cycle (attach->detach x51) still attaches cleanly every cycle, proving the guard does not break normal insert/remove; no_vers1 (DF-2318) and busypanic (DF-5355) scenarios unchanged under the f5356 variant. (2) Combined three-fix kernel: -Werror nativekernel rc=0, installkernel rc=0, booted kernel #1 (sha256 4faad36e...); rig re-extracted from the RUNNING kernel's patched source (--allow-patched) and re-driven: fixed_run.log shows 0 leak / 0 zombies / 0 held over 51 bounce cycles. In-kernel behavioral test impossible (no PCMCIA bridge).

fix.diff; fixed_run.log; matrix.log; postboot_extract_manifest.txt + postboot_run.log + kernel_apply.log + kernel_build.tail.log (in DF-2318 pack)
↓ fix.diffper-fix-DF-5356

Confirmed kernel references

Detail

Exploit chain

evil card grounds CD1#/CD2# -> card attaches -> card ungrounds CD briefly (CSC intr, pccbb.c:621-627 clears CBB_CARD_OK, wakes event thread) -> card regrounds CD -> thread debounce pccbb.c:479-483 observes present -> cbb_insert :458 -> exca_insert exca.c:759 -> CARD_ATTACH_CARD -> pccard_attach_card re-entered, no detach ran -> STAILQ_INIT (pccard.c:189, pccard_cis.c:104) orphans previous pf/cfe chain (376+ bytes/cycle permanent kmem leak, linear, unbounded) + device_add_child (:256) stacks duplicate children over live drivers -> bridge I/O + CCR windows leaked (busy rl entries never released) -> repeat ~every 2 s: kmem exhaustion + zombie device tree over destroyed windows; window exhaustion primes DF-5355 busy-panic on next insert

Evidence (decisive lines)

run.log (baseline decisive: per-cycle cycle-0 trace + 'TOTAL after 51 bounce cycles: orphaned-kmalloc-blocks=102 leak=19176 bytes | zombie-children=51 | io-held=28 ccr-held=28'); fixed_run.log (running-kernel fixed source: 0/0/0/0 across all 51 cycles); matrix.log (f5356 variant flips exactly reentry_leak; benign_cycle + no_vers1 + busypanic unchanged); extract2_manifest.txt; build.log; DF-2318 pack run.log/run.2.log/run.3.log (determinism incl. benign_cycle zero-leak control)

PoC changes

No seed PoC existed (pack was a static trace only). Wrote the reentry_leak scenario in the verbatim lifecycle rig (shared with DF-2318/DF-5355): CD-bounce re-attach driven through the byte-identical attach/detach chain with ledgered kmalloc, tracked device tree, and io/ccr window accounting; 51-cycle amplifier plus benign_cycle negative control. The seeded fix.diff was malformed (patch: 'malformed patch at line 19', declarations after statements) and was regenerated with the same EBUSY-guard semantics placed correctly.

Verified recommended fix

Guard pccard_attach_card against re-entry: return EBUSY when sc->card.pf_head is non-empty (the removal path empties the list at pccard.c:317-324, keeping the marker correct across insert/remove cycles); validated by rig A/B and combined-fix kernel.

Verdict

REPRODUCED at the verbatim-rig level (deterministic, linear unbounded growth), upgrading the seeded static-only verification to executed evidence. The production pccard_attach_card/pccard_detach_card/pccard_read_cis chain was machine-extracted BYTE-IDENTICAL from the guest's /usr/src (sha256 gates; PCCARD_BYTE_IDENTICAL=yes) and driven through the exact re-entry sequence the cbb event thread produces on a card-detect bounce (pccbb.c:621-627 -> :479-483 -> :458-461 -> exca.c:759-764 -> CARD_ATTACH_CARD, no already-attached guard anywhere): attach, re-attach with NO intervening detach, final detach, x51 cycles. Measured damage per cycle: 376 bytes (pf 216 + cfe 160) permanently orphaned by the STAILQ_INIT at pccard.c:189 (and pccard_cis.c:104) -- the detach free path walks only the CURRENT list (pccard.c:303, :317-324); plus one zombie child (device_add_child at :256 stacking a duplicate driver instance over the live card); plus one leaked bridge I/O window and one leaked CCR mapping (orphaned rl entries keep res busy forever). 51 bounce cycles: 102 orphaned blocks / 19176 bytes / 51 zombies / 28 held io + 28 held ccr (window pool exhausts; the pf/cfe leak continues linearly regardless) -- unbounded kernel-memory exhaustion at ~2 s CD-glitch periods, and the leaked busy windows prime DF-5355's panic on subsequent inserts. Negative control benign_cycle (attach->detach x51, no bounce): leak=0/zombies=0/held=0, isolating the defect to the no-detach re-attach. Kernel entry itself is hardware-gated (no PCMCIA bridge in QEMU, env.txt) -- DF-1040/1041/2319 precedent; the trigger trace pccbb.c:458/479/621 + exca.c:759 was verified line-by-line in the guest's own tree.