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)
PoC verification
Evidence pack
findings/poc/DF-5769 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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-1432CID match loop never hits) βkmalloc(struct mmc_ivars)(mmc.c:1440-1441, ~0.7 kB, M_DEVBUF) plusdevice_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 atbus_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.
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) atbus_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);
kldunloadimpossible (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_childrencost atmmc.c:1424-1433slows 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
fixedPatched 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/)
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.
No comments yet.