Non-terminating reply walk in mpt_recv_handshake_reply when IOC reports MsgLength < 2 (negative loop counter, attach-time livelock holding MPT_LOCK)
Summary
left = (MsgLength << 1) - 2 from the IOC-authored reply header; for MsgLength in {0,1} left becomes -2 or 0, and for -2 the post-decrement while never becomes false. The loop only exits via a DB-int wait failure (up to 30s spin) and never if a malicious IOC keeps the interrupt asserted. Runs during attach/init handshakes with MPT_LOCK held -> boot/attach livelock. VERIFIED: guest harness shows the identical loop condition running past 1e8 iterations without exiting. Clamp fix compiles -Werror-clean.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3912 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger_analysis.c | trigger-source | userspace demonstrator of the kernel index/pointer/division arithmetic | 2.0 KB | view raw |
| build.sh | build-script | cc build of the demonstrator | 65 B | view raw |
| run.sh | run-script | runs the demonstrator | 89 B | view raw |
| build.log | build-log | guest compiler output | 11 B | view raw |
| run.log | run-log | guest runtime output (decisive run) | 268 B | view raw |
| env.txt | environment | guest uname + pciconf (no AMD GPU / no MPT HBA) | 838 B | view raw |
| fix.diff | suggested-fix | git-apply-able unified diff | 287 B | view raw |
| fix_build.log | build-log | fix apply + nativekernel rebuild summary, rc=0 -Werror | 6.3 KB | view raw |
| VERDICT.md | verdict | full verification narrative | 2.1 KB | β raw |
| README.md | readme | human-readable summary | 1.4 KB | β raw |
| ../DF-3908/fix_kernel_full.log | build-log | full nativekernel build log shared from DF-3908 pack | 5.6 MB | β download |
DF-3912 β mpt handshake reply walk: negative left from IOC MsgLength==0 β non-terminating polling loop (attach-time livelock)
File: sys/dev/disk/mpt/mpt.c
Lines: 1475-1491 (mpt_recv_handshake_reply)
Severity: Low (malicious/faulty IOC β driver-attach hang with MPT_LOCK held) β hardware-gated
Class: base:dev / CWE-835 (loop with unreachable exit), pass 2 (checklist (f): reply-frame walks beyond the known set)
Build / Run
./build.sh && ./run.sh
Expected output
MsgLength=0 -> left=-2 loop ran 100000000 iterations and was cut off by the harness ... trigger rc=1 (1 == non-terminating loop demonstrated)
Root cause
left = (hdr->MsgLength << 1) - 2; (:1475) with the IOC-supplied MsgLength
(u8, dwords). For MsgLength < 2 left is negative, and while (left--)
(:1477) can never become false (int underflow wraps through INT_MIN). The
loop body only exits via mpt_wait_db_int() failure β 300000 Γ DELAY(100)
= up to 30 s of spinning per attempt β so a malicious IOC that keeps the
doorbell interrupt asserted pins the attach thread in a livelock holding
MPT_LOCK (boot hang). Bounded-write side is safe (if (reply_left-- > 0)),
so this is purely the loop-exit defect.
Fix
fix.diff clamps left < 0 to 0 (skip the remainder walk). Validated in
the batch nativekernel rebuild: mpt.c compiled in-kernel and as module with
-Werror, rc=0 (fix_build.log).
DF-3912 β Verification Verdict
Status: not_reproduced (reproduced=0) Impact: none on guest (no MPT HBA present) Confidence: certain (source-level)
Finding
mpt_recv_handshake_reply() computes left = (hdr->MsgLength << 1) - 2
from the IOC-authored reply header (first two doorbell words, :1450-1461)
and loops while (left--) (:1475-1477). MsgLength β {0,1} yields left = -2
or 0; for -2 the post-decrement condition never terminates (int wrap), so
the loop exits only when mpt_wait_db_int() times out β up to
MPT_MAX_WAIT(300000) Γ 100 Β΅s per attempt β and never if a malicious IOC
keeps MPT_DB_INTR asserted. The loop runs during attach/init handshakes with
MPT_LOCK held β livelock/DoS.
Verification
- Source chain certain (README).
- Guest run (run.log): the harness loop with left=-2 ran 1e8 iterations without exiting and was cut only by the harness bound (the kernel has no such bound); rc=1. The kernel path requires an MPT IOC (absent β env.txt), hence not_reproduced-on-guest (device-gated), consistent with the DF-2481 mpt precedent.
Fix validation
fix.diff: if (left < 0) left = 0; after the subtraction. Applied with the
batch; nativekernel rebuild rc=0, mpt.c compiled in-kernel + KLD module with
-Werror, 0 errors (fix_build.log). fix_status = fixed
(compile-validated).
Negative notes tied to this audit of the walk family
- Write side of the handshake walk is bounded (
reply_left-- > 0, :1487). - mpt_intr handler dispatch is masked: MPT_CONTEXT_TO_CBI ANDs with (MPT_NUM_REPLY_HANDLERS-1) (mpt.h:829-830) β no OOB function-pointer call; req_index is bounds-checked at mpt.c:811.
- tgt_cmd_ptrs access bounded at mpt.c:765-773.
- DF-1280 (address-reply offset) and DF-1281 (EventDataLength) remain the known walk bugs; not re-derived here.
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
fixedfixed (compile-validated): fix.diff applied to clean guest /usr/src with the batch; nativekernel rebuild rc=0, 0 errors; mpt.c compiled in-kernel and as KLD module with -Werror. Runtime re-test impossible on this guest (no MPT HBA).
fix_build.log (apply + compile lines + NK_DONE rc=0); run.log (harness-bounded non-terminating loop); full build log shared from DF-3908/fix_kernel_full.log.
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log on guest: 'loop ran 100000000 iterations and was cut off by the harness'; VERDICT.md walk-family negative notes (cb_index masked, req_index bounded); fix_build.log: mpt.c -Werror clean in nativekernel rc=0
PoC changes
Pass-2 reviewer authored trigger/analysis + fix from source; harness built and run on the DragonFly guest (non-terminating loop demonstrated); fix compile-validated via full nativekernel rebuild.
Verified recommended fix
Clamp left to 0 when (MsgLength<<1)-2 is negative before the remainder walk.
Verdict
Source-certain loop-exit defect: mpt_recv_handshake_reply() computes left=(MsgLength<<1)-2 from the IOC reply header; MsgLength<2 makes left negative and while(left--) can never terminate (int wrap), exiting only via a 30s doorbell timeout - a malicious IOC holding the doorbell interrupt asserted livelocks the attach thread with MPT_LOCK held (boot hang). Guest harness shows the loop cannot exit naturally (cut off at 1e8 iterations); kernel path needs an MPT IOC, absent from the guest.
No comments yet.