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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4213 Β· 6 files
FileTypeDescriptionSize
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.h as 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.md
↓ download raw

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_data union: max member MPI_RAID_VOL_INDICATOR = U64 TotalBlocks; U64 BlocksRemaining; = 16 bytes (sys/dev/disk/mpt/mpilib/mpi_raid.h, _MPI_RAID_VOL_INDICATOR).
  • MSG_RAID_ACTION_REPLY ends at 0x1C with U32 ActionData at 0x18 β€” 4 bytes (same header, _MSG_RAID_ACTION_REPLY).
  • memcpy(..., sizeof(action_result->action_data)) at mpt_raid.c:578-581 copies 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 printed sync_progress (mpt_raid.c:1336-1343) contains 12 stale bytes, printed at mpt_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_testable
baseline no→ patch + rebuild →patched clean

diff 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)
↓ fix.diffper-fix-DF-4213

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.