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

mmc_discover_cards() has no host-side termination: a card that keeps answering CMD2 loops enumeration forever and (fresh CIDs) allocates mmc_ivars+bus children without bound

Summary

The while(1) loop exits only on CMD2 error - termination delegated entirely to the card (JEDEC: a real card stops answering once it owns an RCA). A crafted card answering every CMD2 with a fresh CID makes the CID-match never hit: each iteration kmallocs ~0.7kB mmc_ivars and adds a bus child only freeable at bus_generic_attach - never reached. Rig on the production code: M_DEVBUF grew 102K->89.1M->94.1M monotonically (~110k allocations in 8 min, still climbing), discovery wedged permanently, kldunload impossible, guest dead to ssh. Static-CID variant re-enumerates forever too. bound-at-32 fix validated (mmcsd0..31 then stop, clean unload).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5769 Β· 12 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 4.6 KB ↓ raw
dfh_harness.c β€” 19.8 KB view raw
Makefile β€” 397 B ↓ download
build.sh β€” 97 B view raw
run.sh β€” 149 B view raw
build.log β€” 5.8 KB view raw
run.log β€” 1.8 KB view raw
fix.log β€” 1.2 KB view raw
env.txt β€” 322 B view raw
fix.diff β€” 1.2 KB view raw
verdict.json β€” 4.6 KB view raw

DF-5769 β€” mmc_discover_cards() never terminates / unbounded child+ivar allocation for a card that keeps answering CMD2

File: sys/bus/mmc/mmc.c (pass 2, GLM 5.3) Class: CWE-400 (uncontrolled resource consumption) + CWE-835 (loop with unreachable exit condition)

What

mmc_discover_cards() (sys/bus/mmc/mmc.c:1413) enumerates cards in a while (1) loop whose only exits are CMD2 (ALL_SEND_CID) errors (mmc.c:1415-1422: err == MMC_ERR_TIMEOUT / err != MMC_ERR_NONE) and per-card error breaks. Per JEDEC, a real card leaves the identification state (stops answering CMD2) once it owns an RCA β€” termination is delegated entirely to the card. A crafted card that keeps answering CMD2:

  • mutating CID β€” every pass looks like a new card (mmc.c:1426-1432 CID match loop never hits) β†’ kmalloc(struct mmc_ivars) (mmc.c:1440-1441, ~0.7 kB, M_DEVBUF) plus device_add_child() (mmc.c:1704-1707, unnamed child, no unit ceiling) per iteration, forever: unbounded kernel-memory consumption, no path ever frees them (children are only attached at bus_generic_attach, mmc.c:1908, which is never reached);
  • static CID β€” the same card is re-enumerated forever (ivar->rca = rca++ each pass, mmc.c:1572, wrapping uint16_t), discovery never returns.

Either way: the discovery context (config_intrhook / mmc_delayed_attach) is permanently wedged, the mmc bus is unusable, kldunload is impossible, and with mutating CIDs the kernel leaks memory without bound until the system degrades/dies. Reachable at boot or card-insertion/resume by any malicious MMC-mode card (SD probe is made to fail β†’ mode_mmc, mmc.c:1779-1786).

How to reproduce (no SD hardware needed)

Same harness family as DF-5768 (production mmc.c compiled into the KLD, fake mmcbr bridge), built with DFH_MUTATING_CID: every CMD2 is answered with success and a fresh CID.

# guest (root), from a clean boot:
cd /tmp/df5769 && SYSDIR=/usr/src/sys make
sh ./run.sh          # kldloads in background, samples vmstat -m

Expected (stock kernel): mmc0 attaches, discovery loops forever in the dfh_boot thread; M_DEVBUF grows monotonically without bound (measured: 102 K β†’ 89.1 M β†’ 94.1 M over ~8 min, β‰ˆ110k allocations, still climbing; the O(NΒ²) CID-match/device_get_children cost slows but never stops it); the guest eventually stops accepting ssh sessions; no crash β€” a pure wedge/exhaustion DoS. With the fix (fix.diff), discovery stops after 32 iterations, 32 mmcsd children attach, kldunload is clean and the guest stays healthy.

VERDICT.md
↓ download raw

DF-5769 β€” VERDICT

Finding: mmc_discover_cards() has no host-side termination β€” a card that keeps answering CMD2 (ALL_SEND_CID) loops the enumerator forever and (fresh CIDs) allocates one mmc_ivars + one bus child per iteration without bound. File: sys/bus/mmc/mmc.c:1413-1710 (loop head 1413, exits only at 1417-1422; alloc 1440-1441; child add 1704-1707). Verdict: REPRODUCED β€” unbounded kernel-memory consumption / permanent wedge (DoS); fix validated on a patched kernel.

1. Root cause

mmc_discover_cards() enumerates while (1); the ONLY loop exits are CMD2 errors/timeouts (mmc.c:1417-1422) and per-card error breaks. Per JEDEC, a real card exits the identification state (stops answering CMD2) once it owns an RCA, so termination is delegated to the card. A crafted card that keeps answering CMD2:

  • mutating CID β€” the CID-match loop (mmc.c:1426-1432) never hits, each pass is a "new card": kmalloc(sizeof(struct mmc_ivars)) (mmc.c:1440-1441, ~0.7 kB M_DEVBUF) + device_add_child (mmc.c:1704-1707, unnamed child β€” no unit ceiling, no devclass cap) per iteration, forever. The children are only attached (and only become freeable) at bus_generic_attach (mmc.c:1908) β€” which is never reached because discovery never returns.
  • static CID β€” the same card is re-enumerated forever (ivar->rca = rca++ per pass, mmc.c:1572, uint16_t wrap) β€” non- terminating even without memory growth.

Consequences: the discovery context (config_intrhook / mmc_delayed_attach, mmc.c:1976-1982) is wedged forever; the mmc bus is permanently unusable; kldunload is impossible; kernel memory is consumed monotonically. Reachable at boot / card insertion / resume by any malicious MMC-mode card (the SD probe is made to fail β†’ mode_mmc, mmc.c:1779-1786; the harness does exactly that).

2. Reproduction (no SD hardware)

Same harness family as DF-5768 (production mmc.c compiled into the KLD, fake mmcbr bridge) built with DFH_MUTATING_CID: every CMD2 answered with success and a fresh CID.

Baseline (stock kernel, clean snapshot):

DFH: MOD_LOAD (variant: DF-5769 mutating-CID)
DFH: boot thread: creating dfhbr0
mmc0: <MMC/SD bus> on dfhbr0          <- production discovery now loops forever

vmstat -m (M_DEVBUF "in-use"):
  fresh boot:                       102K
  t+~6 min:                        89.1M   (+ ~110,000 ivar+child allocations)
  t+~7.5 min:                      94.1M   (monotonic, unbounded)
  • the monitored ssh session timed out (guest saturated); ~12 min after load the guest stopped accepting new ssh sessions entirely (serial console alive β€” pure wedge/exhaustion, no crash);
  • kldunload impossible (the never-returning scan holds the module);
  • there is NO exit condition in production code for this card behavior (verified by trace; the O(NΒ²) CID-match/device_get_children cost at mmc.c:1424-1433 slows the loop over time but never stops it).

Honest scope note: on the 4 GB test guest the leak rate (after the O(NΒ²) slowdown sets in) means full OOM takes hours; the demonstrated, code-certain property is unbounded growth + non-termination + permanent bus wedge from a single card insertion. impact=dos.

3. Fix validation

fix.diff bounds the loop host-side: while (iter++ < 32) at mmc.c:1415. Applied in the guest's /usr/src only (together with DF-5768's hunks β€” both in mmc.c, non-overlapping); rebuilt kernel #1: Sun Oct 11 18:02:40 UTC 2026; harness rebuilt against patched source; identical PoC re-run:

kldload rc=0
mmcsd0 .. mmcsd31 attach            <- exactly 32 cards, then discovery STOPS
mmcsd31: reading primary partition table: error accessing offset ... (fake
          card refuses reads -> normal production error path, no crash)
kldunload rc=0
vmstat -m: devbuf 227K              <- all card structures reclaimed

Guest healthy and responsive throughout; baseline wedge GONE.

4. Impact / threat

Malicious SD/MMC card at insertion/boot/resume β†’ unbounded kernel memory consumption and a permanently wedged mmc subsystem (and, on the static- CID variant, an infinite non-allocating rediscovery loop that still blocks all card I/O). CWE-400 + CWE-835. DoS, not memory corruption.

5. Environment

See env.txt; guest restored to the clean with-src snapshot after the runs.

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

Patched kernel (kernel #1 Sun Oct 11 18:02:40 UTC 2026) with the 32-iteration bound at mmc.c:1415: identical PoC enumerates exactly mmcsd0..mmcsd31 and STOPS, the fake disks error through the normal production read-failure path (no crash), kldunload rc=0, devbuf returns to 227K, guest responsive throughout. Baseline unbounded growth/wedge eliminated.

fix.log (patched-run output: mmcsd28..31 partition-probe errors then clean kldunload + devbuf 227K); fix.diff (git apply --check clean against sys/)
↓ fix.diffper-fix-DF-5769

Confirmed kernel references

Detail

Exploit chain

malicious MMC-mode card (SD probe fails -> mode_mmc) -> answers every CMD2 with success + fresh CID -> mmc_discover_cards() never exits -> unbounded kmalloc(mmc_ivars)+device_add_child -> kernel memory exhaustion + permanently wedged mmc bus + unloadable module + dead discovery context. Static-CID variant: infinite re-enumeration (rca++ wrap) with the same non-termination. Impact ceiling: denial of service (no memory-corruption primitive; allocations are zeroed and never reused).

Evidence (decisive lines)

run.log (vmstat -m samples: devbuf 102K -> 89.1M -> 94.1M; ssh timeouts; serial console shows discovery starting and never returning); fix.log (patched kernel: mmcsd0..mmcsd31 then STOP, kldunload rc=0, devbuf 227K); VERDICT.md; dfh_harness.c (DFH_MUTATING_CID); build.log

PoC changes

Same harness family as DF-5768 (production mmc.c compiled into the KLD, fake mmcbr bridge); the mutating-CID variant is selected by a #define baked into the source copy (a Makefile CFLAGS+= -D flag proved unreliable under DF's kmod makefile - caught because the first build silently took the static path and hit the DF-5768 panic instead; variant markers added to MOD_LOAD output to make the active variant self-evident). Monitoring moved to periodic vmstat -m sampling because the wedge kills the controlling ssh session.

Verified recommended fix

Bound the enumeration loop host-side: while (iter++ < 32) at mmc.c:1413 (32 cards is already generous for one MMC/SD bus) - see fix.diff, validated end-to-end (32 cards then stop, clean unload, memory reclaimed).

Verdict

mmc_discover_cards()'s while(1) (mmc.c:1413) exits only on CMD2 failure or per-card errors (mmc.c:1417-1422) - termination is delegated entirely to the card, which per JEDEC stops answering CMD2 after RCA assignment. A crafted card that keeps answering CMD2 with fresh CIDs makes every iteration a new card: kmalloc(mmc_ivars) (mmc.c:1440-1441) + device_add_child (mmc.c:1704-1707) per iteration, forever, never freed (attach only happens at mmc.c:1908, never reached); with a static CID the same card is re-enumerated forever (ivar->rca = rca++ per pass, mmc.c:1572). Reproduced end-to-end with the production-mmc.c KLD harness (mutating-CID fake card): M_DEVBUF grew 102K -> 89.1M -> 94.1M monotonically (~110k allocations in ~8 min, still climbing), the discovery thread never terminated, kldunload became impossible, the mmc bus wedged permanently, and the guest stopped accepting ssh sessions ~12 min after insertion. Fix validated on a rebuilt kernel: while (iter++ < 32) host-side bound -> exactly 32 cards enumerated, clean kldunload, all memory reclaimed (devbuf 227K), guest healthy.