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

Reply-frame range check off-by-one in mps_intr_locked accepts a ReplyFrameAddress exactly one frame past the end

Summary

The recomposed reply pointer is rejected only when reply > reply_frames + fqdepth*ReplyFrameSize*4. reply == exactly that end value (one frame past the last valid frame) passes and is dereferenced as MPI2_DEFAULT_REPLY* - OOB read past the reply_frames DMA map, of chip-influenced length. Distinct unchecked quantity from DF-1369 (SMID). '>='-tighten fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4077 Β· 5 files
FileTypeDescriptionSize
verdict.json β€” 1.9 KB view raw
README.md β€” 180 B ↓ raw
VERDICT.md β€” 1.5 KB ↓ raw
fix.diff β€” 246 B view raw
manifest.json β€” 647 B view raw

DF-4077 β€” mps reply-frame range check off-by-one accepts a one-past-end reply pointer

File: sys/dev/raid/mps/mps.c:1541-1552 Source-level finding; chip-gated. See VERDICT.md.

VERDICT.md
↓ download raw

DF-4077 VERDICT β€” source-level certain, chip-gated

Root cause

mps_intr_locked() recomposes the reply pointer from the chip-supplied ReplyFrameAddress (:1532-1534) and bounds-checks it (:1541-1543):

if ((reply < sc->reply_frames) ||
    (reply > (sc->reply_frames + (sc->fqdepth * sc->facts->ReplyFrameSize * 4))))
    ... panic ...

The upper bound uses >, so reply == reply_frames + fqdepthReplyFrameSize4 β€” the address exactly ONE FRAME PAST the last valid frame β€” passes. Valid reply frames occupy [base, base + (fqdepth-1)fs); the allocation (mps_alloc_replies:716-718: ReplyFrameSize * max(fqdepth, num_replies) * 4 == fqdepth frames) ends at end. reply == end is then dereferenced as MPI2_DEFAULT_REPLY (:1554, :1581-1582) β‡’ OOB read past the DMA map of chip-influenced length (MsgLength drives later reads/prints).

Adjacent to DF-1369 (SMID validation) but a distinct unchecked quantity. Low: requires a controller that returns exactly the end address.

Fix

Reject reply >= end - (one frame): the last valid frame START is base + (fqdepth-1)*fs; better, require frame alignment and reply < end - fs.

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

Hardware-gated finding (no AMD VegaM GPU / no LSI mps controller on the audit guest): fix authored at source level (fix.diff) but not build/boot-validated.

fix.diff in this pack; authored post-analysis, not hardware-validated.
↓ fix.diffper-fix-DF-4077

Confirmed kernel references

Detail

Evidence (decisive lines)

mps.c:1541-1552: 'reply > (reply_frames + fqdepth*ReplyFrameSize*4)' accepts reply == end

PoC changes

n/a (source-level)

Verified recommended fix

Tighten the bound: reply >= reply_frames + (fqdepth-1)ReplyFrameSize4 (or check alignment + last-frame exclusion).

Verdict

Source-level certain, chip-gated. The reply-pointer recomposition check in mps_intr_locked() at mps.c:1541-1543 rejects only reply > reply_frames + fqdepthReplyFrameSize4; a chip-supplied ReplyFrameAddress equal to exactly the one-past-end value passes and is then dereferenced as MPI2_DEFAULT_REPLY => OOB read past the reply_frames DMA map. Valid frame addresses end one frame earlier; the bound must be >= end - ReplyFrameSize4 (or reject non-frame-aligned addresses). One-frame OOB read of chip-influenced length (MsgLength) consumed by print/complete paths.