mlx_wait_command(): timeout counter never incremented -> unkillable infinite 1s-sleep loop on a never-completing command
Summary
The wait loop tests 'count < 30' but count is never incremented; the mc_timeout stamp set in mlx_start ('XXX not currently used') has no consumer. A never-completing command leaves the calling thread sleeping on 'mlxwcmd' forever - PCATCH lets signals interrupt each tsleep but the loop re-enters and the ioctl never returns. Affects MLX_COMMAND, MLXD_CHECKASYNC, MLX_REBUILDASYNC, synchronous mlx_enquire. Contrast the poll path which correctly counts. count++ fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3950 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | doc | bug + reachability + fix | 1.9 KB | β raw |
| VERDICT.md | verdict | source proof | 1.8 KB | β raw |
| mlx_ioctl_trigger.c | trigger | usercmd mode: never-completing command -> infinite mlxwcmd wait | 4.6 KB | view raw |
| build.sh | script | build | 108 B | view raw |
| run.sh | script | run | 118 B | view raw |
| build.log | log | guest compile | 356 B | view raw |
| run.log | log | guest run (ENOENT, no HBA) | 217 B | view raw |
| env.txt | env | guest uname | 185 B | view raw |
| fix.diff | suggested-fix | see README | 608 B | view raw |
DF-3950 β mlx_wait_command(): timeout counter never incremented β unkillable infinite wait
Severity: Low Β· CWE-835 (loop without exit) Β· operator/root gated (MLX_COMMAND path; also hit by MLXD_CHECKASYNC / MLX_REBUILDASYNC ioctls and synchronous mlx_enquire calls)
Bug
sys/dev/raid/mlx/mlx.c:1655-1659:
count = 0;
/* XXX better timeout? */
while ((mc->mc_status == MLX_STATUS_BUSY) && (count < 30)) {
tsleep(mc->mc_private, PCATCH, "mlxwcmd", hz);
}
count is declared (1646) and tested, but never incremented β the loop
condition count < 30 is always true while the command stays busy. If the
controller never posts a completion (wedged HBA, pulled enclosure, firmware
hang), the calling thread sleeps 1s at a time forever. PCATCH lets a signal
interrupt each individual tsleep, but the loop immediately re-enters β the
thread can never make progress or exit; the ioctl never returns. The timeout
stamp mc->mc_timeout = time_uptime + 60 set in mlx_start() (mlx.c:2086)
is documented "XXX not currently used" and indeed nothing consumes it
(grep-verified).
Contrast mlx_poll_command() (mlx.c:1689-1694) which correctly does
count++ < 15000000.
Reachability
Any MLX_COMMAND passthrough whose firmware command never completes, or
MLXD_CHECKASYNC/MLX_REBUILDASYNC against a stuck controller. Requires a
wedged/unresponsive DAC960 β hence Low (privileged user + flaky hardware),
but the defect is unconditional in the code path.
Build / Run / Expected
cc -O2 -Wall -o mlx_ioctl_trigger mlx_ioctl_trigger.c
./mlx_ioctl_trigger usercmd /dev/mlx0 # against a controller that hangs
Expected on hardware: ioctl never returns; ps shows the caller stuck in
mlxwcmd forever; SIGKILL does not reclaim the thread. On this guest:
ENOENT (no HBA) β hardware-gated.
Fix
Increment count each iteration (and abort with EIO after the 30s budget),
matching the poll path; see fix.diff.
DF-3950 β Verification Verdict
Status: inconclusive (hardware-gated; source-certain) Impact demonstrated: none (expected: permanent unkillable thread hang) Confidence: certain (source), runtime untestable (no DAC960 HBA)
Source proof
sys/dev/raid/mlx/mlx.c:1646declaresint error, count;sys/dev/raid/mlx/mlx.c:1655-1659: loopwhile ((mc->mc_status == MLX_STATUS_BUSY) && (count < 30)) tsleep(mc->mc_private, PCATCH, "mlxwcmd", hz);βcounthas no increment anywhere in the function (grep: the only writes are the initializer at 1655).PCATCHbreaks a single tsleep on signal, then the (unchanged) condition re-sleeps β no exit.mlx_start()setsmc->mc_timeout = time_uptime + 60(mlx.c:2086) with the comment "XXX not currently used"; no consumer exists.- Callers:
mlx_user_command(mlx.c:1922),mlx_check(mlx.c:1577),mlx_rebuild(mlx.c:1620), synchronousmlx_enquire(mlx.c:1486) β i.e. every wait-style ioctl submission.
Impact: thread permanently resident in kernel ("mlxwcmd") β a local operator/root-triggered DoS of the calling process (unkillable) and of the controller's command path. Not memory corruption.
Guest result
Trigger compile-tested (BUILD_RC=0 on DragonFly 6.5-DEVELOPMENT); runtime ENOENT β no Mylex hardware. Honest: inconclusive / source-certain.
Fix validation
fix.diff adds the missing increment + failure return. Not runtime-testable here (no HBA) β fix_status: not_testable.
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_testablenot_testable: no Mylex hardware in guest
fix.diff
Confirmed kernel references
Detail
Exploit chain
operator/root: ioctl(MLX_COMMAND, never-completing op) against wedged controller -> thread stuck in 'mlxwcmd' forever, unkillable
Evidence (decisive lines)
VERDICT.md cites mlx.c:1646,1655-1659,2086; mlx_ioctl_trigger.c (usercmd mode); build.log/run.log; fix.diff count++
PoC changes
no seed; self-contained trigger
Verified recommended fix
count++ in the wait loop (fall through to error after 30s).
Verdict
Source-certain infinite wait: mlx_wait_command (mlx.c:1655-1659) tests 'count < 30' but never increments count, so a never-completing controller command loops on 1s PCATCH tsleeps forever; signals interrupt individual sleeps but the loop never exits and the ioctl never returns. The mc_timeout stamp set in mlx_start (mlx.c:2086, 'XXX not currently used') has no consumer. Reachable via MLX_COMMAND/MLXD_CHECKASYNC/MLX_REBUILDASYNC ioctls (operator/root) against a wedged DAC960. Runtime inconclusive: no HBA in guest; trigger compile-tested.
No comments yet.