16-byte memcpy out of the 4-byte MSG_RAID_ACTION_REPLY.ActionData publishes stale reply-frame bytes as RAID resync progress
Summary
sizeof(union action_data)=16 is copied from ActionData, a single U32 at reply offset 0x18 - a 12-byte over-read past the reply message into stale 256-byte reply-frame residue. Because the indicator request supplies no SGE buffer, those stale bytes become sync_progress and are printed to console/log every resync tick; the feature can never report correct data. Console disclosure of stale IOC-reply DMA residue (no kernel pointers) + permanently-lying status readout. Copy-the-field fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4213 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 1.8 KB | β raw | |
| env.txt | β | 992 B | view raw | |
| fix.diff | β | 682 B | view raw | |
| manifest.json | β | 693 B | view raw | |
| verdict.json | β | 1.9 KB | view raw |
DF-4213 β 16-byte memcpy out of a 4-byte reply field publishes stale reply-frame bytes as RAID sync progress
- File:
sys/dev/disk/mpt/mpt_raid.c(+sys/dev/disk/mpt/mpilib/mpi_raid.has the type evidence) - Lines: 578β581 (copy), 1327β1343 (producer), 1521β1536 (print)
- Class: OOB read within allocation / stale DMA data disclosure to console (CWE-125 / CWE-908)
- Hardware gate: LSI MPT Fusion RAID controller during volume resync
Root cause
mpt_raid_reply_frame_handler() copies the reply's action data into the
per-request result:
action_result = REQ_TO_RAID_ACTION_RESULT(req);
memcpy(&action_result->action_data, &reply->ActionData,
sizeof(action_result->action_data)); /* 16 bytes */
action_data is a union whose largest member is MPI_RAID_VOL_INDICATOR
(two U64s = 16 bytes), but MSG_RAID_ACTION_REPLY.ActionData is a
single U32 (offset 0x18, struct ends 0x1C β mpi_raid.h). The copy
therefore reads 12 bytes past the reply message into the remainder of the
256-byte reply frame (MPT_REPLY_SIZE, mpt.h:816) β bytes the IOC never
wrote for this reply, i.e. residue of whatever reply previously occupied
the frame slot.
Where the stale bytes go
mpt_refresh_raid_vol() issues MPI_RAID_ACTION_INDICATOR_STRUCT with
addr=0, len=0 (mpt_raid.c:1327) β no SGE data buffer is supplied, so
the IOC has nowhere to write the 16-byte progress indicator the MPI spec
returns via the ActionData SGE. The code then treats the 4 real + 12 stale
bytes as sync_progress (1336β1343) and mpt_refresh_raid_data() prints
them to console/log:
mpt_vol_prt(mpt, mpt_vol, "%ju of %ju blocks remaining\n", left, total);
Result: (a) the resync-progress feature can never report correct numbers (functional bug), and (b) 96 bits of stale IOC-reply DMA residue are disclosed to any reader of the console/dmesg on every resync status tick (15-min interval while resync runs). Leak value is low (reply frames are IOC-written reply data, not kernel pointers), hence severity Low.
Verification status
untested β hardware-gated (no MPT HBA on the guest, env.txt). Source
certainty: struct sizes read directly from mpi_raid.h.
Fix
fix.diff bounds the copy to the field that actually exists. (The complete
functional fix would also supply a real SGE buffer for the indicator, as
mps(4)/mpr(4) do.)
VERDICT β DF-4213
status: untested (hardware-gated) Β· impact: low-value kernel/DMA info leak to console + broken feature Β· confidence: certain (source)
Not runnable on the audit guest (no LSI MPT hardware; env.txt). Source
proof is complete:
struct mpt_raid_action_result.action_dataunion: max memberMPI_RAID_VOL_INDICATOR=U64 TotalBlocks; U64 BlocksRemaining;= 16 bytes (sys/dev/disk/mpt/mpilib/mpi_raid.h,_MPI_RAID_VOL_INDICATOR).MSG_RAID_ACTION_REPLYends at 0x1C withU32 ActionDataat 0x18 β 4 bytes (same header,_MSG_RAID_ACTION_REPLY).memcpy(..., sizeof(action_result->action_data))atmpt_raid.c:578-581copies 16 bytes from a 4-byte field β 12 bytes over-read. Physically it stays inside the 256-byte reply frame (MPT_REPLY_SIZE,mpt.h:816), so no heap OOB β it reads stale reply-frame residue.- The producer passes
addr=0, len=0(mpt_raid.c:1327), so no SGE buffer exists for the indicator; the reply frame is the only source β guaranteeing the printedsync_progress(mpt_raid.c:1336-1343) contains 12 stale bytes, printed atmpt_raid.c:1521-1536.
Impact ceiling: console/dmesg disclosure of stale IOC reply bytes (previous
replies in that frame slot β transfer counts, event data, config echoes), no
kernel pointers. Low severity, certain mechanism. Fix bounded-copy diff
provided; not runtime-validated (no hardware) β 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_testablediff authored and git-apply-verified against the read-only sys/ tree; runtime fix validation impossible without the triggering hardware on the QEMU guest
findings/poc/DF-4213/fix.diff (git apply --check passed)
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4213/VERDICT.md, findings/poc/DF-4213/env.txt (guest pciconf: no triggering hardware), findings/poc/DF-4213/fix.diff
PoC changes
no runnable PoC: hardware-gated path absent from the audit guest (env.txt)
Verified recommended fix
bound the memcpy to sizeof(reply->ActionData); supply an SGE buffer for the indicator (as mps/mpr do)
Verdict
mpt_raid_reply_frame_handler memcpy's sizeof(union action_data)=16 (MPI_RAID_VOL_INDICATOR, 2xU64) from MSG_RAID_ACTION_REPLY.ActionData which is a single U32 (mpi_raid.h) β a 12-byte over-read past the reply message into stale 256-byte reply-frame residue. The indicator request supplies no SGE buffer (addr=0,len=0, mpt_raid.c:1327) so the stale bytes become sync_progress and are printed to console every resync tick. Low-value info leak + permanently-wrong feature.
No comments yet.