suspend state leaves bio queue accepting I/O with no consumer and no bound: kernel-memory exhaustion under sustained load
Summary
mmcsd_suspend kills the worker and sets suspend=1, but strategy deliberately keeps enqueueing (running>0 || suspend>0) with no queue bound and no ENXIO - the only consumer is gone until resume. Mechanism witnessed live during DF-4783 rig testing ('panic: malloc limit exceeded' with consumerless queue); realistic producers are nbuf-bounded (severe pressure). Reject-or-bound-while-suspended fix. [VERIFIED pass2: reproduced on real emulated sdhci - after suspend 96 concurrent 4MB readers all queue consumerless vmstat b 96 about 400MB pinned paging began no bound no ENXIO; resume drains; fix compile-validated]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4784 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 629 B | β raw | |
| VERDICT.md | β | 2.4 KB | β raw | |
| env.txt | β | 309 B | view raw | |
| fix.diff | β | 562 B | view raw | |
| manifest.json | β | 821 B | view raw | |
| mmcrace4784.c | β | 7.9 KB | view raw | |
| run.log | β | 728 B | view raw | |
| verdict.json | β | 2.9 KB | view raw |
DF-4784 - mmcsd suspend queue unbounded (kernel-memory exhaustion)
Build: cd /root/mmcrig && make SYSDIR=/usr/src/sys KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC Run: QEMU_EXTRA with sdhci-pci + sd-card; kldload mmcrace.ko; sysctl hw.mmcrace.action=13 (suspend); launch 96x 'dd if=/dev/mmcsd0 bs=4m'. Observed: suspend -> 0; vmstat b=96 (all blocked, consumerless, no ENXIO); paging begins; resume (action=14) drains. malloc-limit panic witnessed in DF-4783 under the same consumerless queue.
Kernel: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
DF-4784 - VERDICT
status: reproduced | reproduced: 1 | impact: denial_of_service | confidence: certain
Mechanism reproduced on real emulated hardware (QEMU sdhci-pci + sd-card; mmcsd0 256MB attaches natively). Using the DF-4783 rig (mmcrace.ko, extended with a resume action) to invoke the REAL mmcsd_suspend() method: after 'mmcrace: suspend -> 0' (suspend=1, worker killed, running==-1), mmcsd_strategy (:312-315) kept enqueueing every bio into sc->bio_queue with no consumer, no bound and no ENXIO: 96 concurrent dd readers (4MB each) were all accepted and left permanently blocked (vmstat 'b' column = 96; ~400MB+ of buffers/pages pinned; paging activity began) - the queue accepted everything thrown at it. device_resume (mmcsd_resume restarts the worker and suspend=0) proves the bios were queued rather than rejected. Ceiling is kernel-memory exhaustion under sustained load - already witnessed live during the DF-4783 session as 'panic: malloc limit exceeded' with a consumerless queue; on this rig the emulated card's data phase times out, which does not affect the strategy-path demonstration (bioqdisksort happens before any card I/O). Trigger: physical card removal/hot-unplug mid-I/O or system suspend while I/O continues; no privilege needed to be a producer (any process with the device open). FIX STATUS: compile-validated only - mmcsd is compiled into the stock X86_64_GENERIC kernel (no loadable module to swap on a running system; kldstat shows no mmcsd module), so the runtime A/B could not be performed without a full kernel rebuild. fix.diff (reject-with-ENXIO once a bio is already queued while suspended-and-consumerless) builds clean as a module against KERNBUILDDIR.
Kernel references
Evidence
run.log (suspend -> 0; 96 blocked dd's; vmstat b=96 + memory/paging deltas; resume -> 0), mmcrace4784.c (rig + resume action)
Recommended fix
bound the suspend-queue (reject with ENXIO beyond a limit, or fail fast when running<0 while suspended).
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
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
run.log (suspend -> 0; 96 blocked dd's; vmstat b=96 + memory/paging deltas; resume -> 0), mmcrace4784.c (rig + resume action)
PoC changes
mmcrace.c from DF-4783 pack extended with action=14 (device_resume method-table invocation); build via bsd.kmod.mk with KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC
Verified recommended fix
bound the suspend-queue (reject with ENXIO beyond a limit, or fail fast when running<0 while suspended).
Verdict
Mechanism reproduced on real emulated hardware (QEMU sdhci-pci + sd-card; mmcsd0 256MB attaches natively). Using the DF-4783 rig (mmcrace.ko, extended with a resume action) to invoke the REAL mmcsd_suspend() method: after 'mmcrace: suspend -> 0' (suspend=1, worker killed, running==-1), mmcsd_strategy (:312-315) kept enqueueing every bio into sc->bio_queue with no consumer, no bound and no ENXIO: 96 concurrent dd readers (4MB each) were all accepted and left permanently blocked (vmstat 'b' column = 96; ~400MB+ of buffers/pages pinned; paging activity began) - the queue accepted everything thrown at it. device_resume (mmcsd_resume restarts the worker and suspend=0) proves the bios were queued rather than rejected. Ceiling is kernel-memory exhaustion under sustained load - already witnessed live during the DF-4783 session as 'panic: malloc limit exceeded' with a consumerless queue; on this rig the emulated card's data phase times out, which does not affect the strategy-path demonstration (bioqdisksort happens before any card I/O). Trigger: physical card removal/hot-unplug mid-I/O or system suspend while I/O continues; no privilege needed to be a producer (any process with the device open). FIX STATUS: compile-validated only - mmcsd is compiled into the stock X86_64_GENERIC kernel (no loadable module to swap on a running system; kldstat shows no mmcsd module), so the runtime A/B could not be performed without a full kernel rebuild. fix.diff (reject-with-ENXIO once a bio is already queued while suspended-and-consumerless) builds clean as a module against KERNBUILDDIR.
No comments yet.