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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4784 Β· 8 files
FileTypeDescriptionSize
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

VERDICT.md
↓ download raw

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)

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_testable
↓ fix.diffper-fix-DF-4784

Confirmed 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.