Bios submitted to a never-completing mlx controller park on mlx_bioq forever β unkillable D-state issuers and unbounded bioq growth (DF-5048/DF-5026 family, mlx instance)
Summary
The only dequeue restart is hardware completions or the 1Hz poll; mc_timeout is dead code (XXX never read) - a controller that accepts commands then stops completing pins every slot BUSY forever and all further bios park indefinitely (unkillable D-state, unbounded bioq growth). Hardware-fault trigger (no deterministic software path like ips DF-5026). 1Hz stall-watchdog in mlx_periodic fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5058 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.5 KB | β raw | |
| VERDICT.md | β | 4.4 KB | β raw | |
| env.txt | β | 415 B | view raw | |
| fix.diff | β | 2.2 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 4.7 KB | view raw |
DF-5058 β bios submitted to a never-completing mlx controller park on
mlx_bioq forever: unkillable D-state issuers + unbounded bioq growth
file: sys/dev/raid/mlx/mlx_disk.c (submit path; parking logic in
sys/dev/raid/mlx/mlx.c)
bucket: base:dev (DF-5026 / DF-5048 family β the mlx instance, the last
LD sibling)
severity: Low (trigger = controller hardware fault: accepts commands
then stops completing, or wedges with slots held)
Reproduce
Not runnable on this guest: no Mylex PCI hardware (env.txt), driver never attaches. Family twin DF-5048 (ida) was accepted with the same hardware-gated pack.
Expected behavior on real hardware
- Saturate
/dev/mlxd0with asynchronous I/O (e.g. several paralleldd/vinumreaders). - Cause the controller to stop delivering completions after accepting commands (firmware hang / surprise fault while BUSY).
- After the 256 slots fill, every further bio parks on
sc->mlx_bioq(mlx.c:1739-1742). No timeout exists anywhere (mc_timeoutis set at mlx.c:2086 and explicitly "not currently used");mlx_periodiconly pollsmlx_doneevery second (mlx.c:1040) and never fails a stale command. - Every issuer sleeps in
biowaitβ unkillable D-state processes; the bioq grows without bound (kernel memory exhaustion).
Fix
fix.diff: stall watchdog inside the existing 1 Hz mlx_periodic callout β
if parked bufs exist and mlx_done completed nothing for 60 consecutive
seconds, fail every queued bio with ETIMEDOUT and fail new arrivals until
completions resume.
DF-5058 VERDICT β parked bios / no completion path on a wedged mlx controller
status: untested (hardware-gated) | impact: none demonstrated (local DoS class) | confidence: certain (code trace)
Reproduced?
No. Requires a physical Mylex DAC960 controller that stops completing I/O.
Guest evidence (env.txt): no 0x1069 PCI function, no 0x1011:1065 function,
/dev/mlx*//dev/mlxd* absent, driver compiled into the running kernel
(X86_64_GENERIC:134). Untested = hardware absent β same basis the family
accepted for DF-5048.
Why the finding is real (trace)
Completion-flow enumeration β there is no biodone for bios that enter
mlx_submit_bio other than mlxd_intr via mlx_completeio:
mlxd_strategy(mlx_disk.c:156-190) local error paths all biodone (mlx_disk.c:166-170, 173-177, 183-189) β those are fine (this is the DF-5012 class, already clean here).- The submit path (mlx_disk.c:179-181) β
mlx_submit_bio(mlx.c:683-693):bioqdisksort(&sc->mlx_bioq, bio)+mlx_startio. mlx_startio(mlx.c:1713-1805): pops a bio only whenmlx_alloccmdsucceeds andmlx_getslotreturns 0; out of command slots (mlx.c:1739-1742,limit - 4reached) βbreak, bio stays parked onmlx_bioq. Commands whosemlx_startfails outright are completed with WEDGED (mlx.c:1796-1800 β mlx_completeio β mlxd_intr) β the fast wedge path is handled; the parked path below is not.- The ONLY restart for parked bios is
mlx_doneβmlx_startio(mlx.c:2160-2161), reached from the hardware interrupt (mlx.c:668-676) or the 1 Hzmlx_periodic(mlx.c:1040) β both rely on the controller eventually delivering completions. - No timeout exists anywhere:
mc_timeout = time_uptime + 60is set inmlx_start(mlx.c:2085-2086) under the comment "XXX not currently used"; nothing ever reads it.mlx_complete(mlx.c:2173-2223) only processesmc_status != MLX_STATUS_BUSY; the periodic enquiry/rebuild polls (mlx.c:977-1044) never fail stale commands. - Therefore: controller accepts commands then never completes them β all
slots (up to
me_max_commands, mlx.c:1961-1966) stay BUSY forever βmlx_getslotEBUSY β every subsequent bio parks onmlx_bioqindefinitely βbiowaitsleepers are unkillable D-states (local DoS of every process touching the array; if root/swap live on mlxd the box is dead) andmlx_bioqgrows unbounded β kernel memory exhaustion.mlxd_strategyitself has no timeout/cancel path (mlx_disk.c:172-181).
Trigger requires a real controller fault (firmware hang after accept, surprise removal while BUSY, channel stuck paused) β hence Low, matching the DF-5048 (ida) precedent; DF-5026 (ips) was Medium because an adapter-offline config state parked bios deterministically.
Not a duplicate
- DF-3951 (mlx.c): wedge path of
mlx_startleaksmlx_busycmds/ leaves the DMA map loaded β that is the fast-fail path; this finding is the parked-bioq/issuer-hang path (bios never dequeued at all). - DF-3950 (mlx.c):
mlx_wait_commandinfinite 1 s-sleep loop for synchronous internal commands (mlxctl rebuild/check) β not disk bios. - DF-2074 / DF-5057: different mechanisms (bio_driver_info staleness, detach lifetime).
- DF-5026 / DF-5048: the same defect in ips/ida; this is the mlx instance.
Fix assessment
fix.diff adds a stall watchdog to the existing 1 Hz mlx_periodic callout
(no new callout needed): a stall-tick counter in struct mlx_softc
(mlxvar.h) is reset whenever mlx_done completes anything (mlx.c:1040) and
incremented while mlx_waitbufs > 0; 60 consecutive no-progress seconds
β drain mlx_bioq failing every bio with ETIMEDOUT+B_ERROR and fail new
arrivals in mlx_submit_bio until completions resume. 60 s is far beyond
any healthy controller's worst-case completion latency under load (load
always produces completions, which reset the counter). Commands already
stuck in hardware keep their slots (fail-stop: the array stays EIO until
reset) β noted as the conservative choice. Cannot be build/boot-validated
(no hardware); diff is git-apply-clean against sys/.
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_testablefix.diff adds mlx_stall_ticks to struct mlx_softc (mlxvar.h), resets it whenever mlx_done completes anything (healthy load always produces completions, so no false positive under sustained I/O), and after 60 no-progress seconds with parked bufs drains mlx_bioq failing every bio with ETIMEDOUT+B_ERROR while mlx_submit_bio fails new arrivals β issuers become killable and the queue cannot grow unbounded. Commands stuck in hardware keep their slots (fail-stop until reset), the conservative choice. Cannot be build/boot validated: no Mylex hardware on the guest. Diff is git-apply-clean against sys/.
fix.diff (git apply --check: APPLIES_CLEAN); hunks at mlx.c mlx_periodic (watchdog) + mlx_submit_bio (wedged fast-fail) and mlxvar.h (mlx_stall_ticks)
Confirmed kernel references
Detail
Exploit chain
saturate /dev/mlxdN with I/O + controller fault that stops completions after accept -> all me_max_commands slots stuck BUSY -> mlx_getslot EBUSY for every subsequent bio -> bios park on mlx_bioq forever -> unkillable D-state issuers + unbounded queue growth (kernel memory exhaustion). DoS only, hardware-fault-triggered; no privilege-escalation primitive.
Evidence (decisive lines)
env.txt (live guest: no Mylex PCI function, no /dev/mlx*, driver in kernel); VERDICT.md completion-flow enumeration with path:line for every hop (no other biodone exists for parked bios); fix.diff (git apply --check: APPLIES_CLEAN)
PoC changes
No runnable PoC exists (hardware-gated). Pack contains hardware-absence evidence and the completion-flow trace; fix.diff generated via edited copies + diff -u and validated with git apply --check.
Verified recommended fix
Stall watchdog in the existing 1 Hz mlx_periodic callout: when parked bufs exist and mlx_done completed nothing for 60 consecutive seconds, fail every queued bio with ETIMEDOUT and fail new arrivals in mlx_submit_bio until completions resume.
Verdict
Genuine DF-5048/DF-5026-family parked-bio hang, mlx instance: mlxd_strategy (mlx_disk.c:156-190) -> mlx_submit_bio (mlx.c:683-693) queues every bio on sc->mlx_bioq; mlx_startio (mlx.c:1713-1805) dequeues only while a command slot is free β mlx_getslot EBUSY (mlx.c:1739-1742, limit-4 reached) parks the bio. The ONLY restart is mlx_done -> mlx_startio (mlx.c:2160-2161) from the hardware interrupt or the 1 Hz mlx_periodic (mlx.c:1040), both of which depend on the controller delivering completions. No timeout exists anywhere: mc_timeout is set in mlx_start (mlx.c:2085-2086) under 'XXX not currently used' and never read; mlx_complete (mlx.c:2173-2223) only processes status != BUSY; nothing ever fails a stale command or a queued bio. Therefore a controller that accepts commands and then stops completing (firmware hang after accept, surprise fault while BUSY) pins every command slot BUSY forever, after which every further bio parks on mlx_bioq indefinitely: biowait sleepers are unkillable D-states (system-wide hang if root/swap live on mlxd) and the bioq grows unbounded -> kernel memory exhaustion. The fast-fail wedge path IS handled (mlx_start failure -> mlx_completeio -> biodone(EIO), mlx.c:1796-1800); the parked path is not. Untested solely because the guest has no Mylex DAC960 PCI function (env.txt) although the driver is compiled into the running kernel. Severity Low matching DF-5048 (trigger requires a real controller fault, unlike ips DF-5026 whose offline config state parked bios deterministically).
No comments yet.