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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3912 Β· 11 files
FileTypeDescriptionSize
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
README.md readme human-readable summary
↓ download raw

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

VERDICT.md verdict full verification narrative
↓ download raw

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

fixed
baseline no→ patch + rebuild →patched clean

fixed (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.
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT (nativekernel, patched, rc=0, -Werror clean)

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.