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)
PoC verification
Evidence pack
findings/poc/DF-4077 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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_testableHardware-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.
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.
No comments yet.