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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3950 Β· 9 files
FileTypeDescriptionSize
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
README.md doc bug + reachability + fix
↓ download 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.

VERDICT.md verdict source proof
↓ download raw

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:1646 declares int error, count;
  • sys/dev/raid/mlx/mlx.c:1655-1659: loop while ((mc->mc_status == MLX_STATUS_BUSY) && (count < 30)) tsleep(mc->mc_private, PCATCH, "mlxwcmd", hz); β€” count has no increment anywhere in the function (grep: the only writes are the initializer at 1655). PCATCH breaks a single tsleep on signal, then the (unchanged) condition re-sleeps β€” no exit.
  • mlx_start() sets mc->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), synchronous mlx_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_testable
baseline no→ patch + rebuild →patched clean

not_testable: no Mylex hardware in guest

fix.diff
↓ fix.diffper-fix-DF-3950

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.