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

Integer overflow in mpr_diag_read_buffer bounds check allows OOB read of diag DMA buffer

Summary

mpr_diag_read_buffer at mpr_user.c:1798: if(StartingOffset+BytesToRead>pBuffer->size). Both uint32_t. Wrap: StartingOffset=0x100, BytesToRead=0xFFFFFF00 -> sum=0 <= size. :1812 pData=fw_diag_buffer+0x100 -> copyout reads past DMA allocation -> heap info leak. Requires DIAG capability (common on SAS3+). Fix: check StartingOffset<size, BytesToRead<=(size-StartingOffset).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1329 Β· 12 files
FileTypeDescriptionSize
poc.c trigger-source MPTIOCTL_DIAG_ACTION READ_BUFFER with StartingOffset=0x100, BytesToRead=0xFFFFFF00 (sum wraps to 0) 3.3 KB view raw
VERDICT.md verdict full source trace: uint32 bounds-check sum wraps, OOB copyout 3.9 KB ↓ raw
fix.diff suggested-fix split bounds check: StartingOffset>=size || BytesToRead>size-StartingOffset 867 B view raw
build.sh build-script cc -o poc poc.c -Wall 198 B view raw
run.sh run-script ./poc 538 B view raw
build.log build-log guest build output (BUILD_EXIT=0) 13 B view raw
run.log run-log guest run: open /dev/mpr0 ENOENT (no HBA) 81 B view raw
fix_build.log build-log full nativekernel build with all 3 mpr fixes, rc=0 5.6 MB ↓ download
env.txt environment guest uname, cc, driver-availability facts 1.5 KB view raw
README.md readme summary + repro + file index 977 B ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme summary + repro + file index
↓ download raw

DF-1329 β€” mpr_diag_read_buffer integer-overflow OOB read

Status: INCONCLUSIVE (real bug; needs LSI SAS3+ HBA absent from audit guest) Impact: info leak (OOB read of diag DMA buffer via uint32 wrap, operator-group reachable) Driver: mpr (LSI MPT-Fusion 3 SAS) β€” in X86_64_GENERIC but no HBA on guest

Build & run

./build.sh        # cc -o poc poc.c -Wall
./run.sh          # ./poc   (as an operator-group user; a diag buffer must first be REGISTERED)

On the audit guest the PoC fails at open("/dev/mpr0") with ENOENT. See VERDICT.md for the full trace and fix.diff for the validated patch.

Files

  • poc.c β€” PoC (MPTIOCTL_DIAG_ACTION READ_BUFFER, StartingOffset=0x100, BytesToRead=0xFFFFFF00)
  • VERDICT.md β€” full mechanism trace with path:line citations
  • fix.diff β€” split bounds check to avoid the wrapping uint32 sum
  • build.sh/run.sh, build.log/run.log, fix_build.log, env.txt, manifest.json
VERDICT.md verdict full source trace: uint32 bounds-check sum wraps, OOB copyout
↓ download raw

DF-1329 β€” mpr_diag_read_buffer integer-overflow OOB read

Verdict

INCONCLUSIVE (real bug, needs hardware absent from guest). Source trace confirms the integer overflow in the bounds check; it cannot be executed on this QEMU guest because /dev/mpr0 does not exist (no LSI SAS3+ HBA). Fix validated to apply + compile in a full nativekernel build.

Mechanism

sys/dev/raid/mpr/mpr_user.c:1798  if (diag_read_buffer->StartingOffset +
sys/dev/raid/mpr/mpr_user.c:1799          diag_read_buffer->BytesToRead >
sys/dev/raid/mpr/mpr_user.c:1800      pBuffer->size) {
sys/dev/raid/mpr/mpr_user.c:1801      *return_code = MPR_FW_DIAG_ERROR_INVALID_PARAMETER;
sys/dev/raid/mpr/mpr_user.c:1802      return (MPR_DIAG_FAILURE);
sys/dev/raid/mpr/mpr_user.c:1803  }
...
sys/dev/raid/mpr/mpr_user.c:1812  pData = (uint8_t *)(sc->fw_diag_buffer +
sys/dev/raid/mpr/mpr_user.c:1813      diag_read_buffer->StartingOffset);
sys/dev/raid/mpr/mpr_user.c:1814  if (copyout(pData, ioctl_buf, diag_read_buffer->BytesToRead) != 0)
sys/dev/raid/mpr/mpr_user.c:1815      return (MPR_DIAG_FAILURE);

StartingOffset, BytesToRead, and pBuffer->size are all uint32_t. The check at 1798 computes StartingOffset + BytesToRead as a 32-bit sum, which wraps modulo 2Β³Β². Choose StartingOffset = 0x00000100, BytesToRead = 0xFFFFFF00: sum = 0x100000000 ≑ 0 (mod 2Β³Β²), which is <= pBuffer->size for any registered buffer β†’ the bounds check passes. The subsequent copyout then reads BytesToRead (β‰ˆ 4 GiB) starting at fw_diag_buffer + 0x100, far past the DMA allocation β†’ kernel-heap information leak (and/or a copyout fault when it hits an unmapped page, depending on layout).

Trigger path

ioctl(MPTIOCTL_DIAG_ACTION)            sys/dev/raid/mpr/mpr_user.c:2365
  -> mpr_user_diag_action()            sys/dev/raid/mpr/mpr_user.c:1993
     -> mpr_do_diag_action(ACTION=READ_BUFFER)   sys/dev/raid/mpr/mpr_user.c:2015
        -> mpr_diag_read_buffer()      sys/dev/raid/mpr/mpr_user.c:1772  *** overflow here

Precondition: a diag buffer must first be registered (MPR_FW_DIAG_TYPE_REGISTER via the same ioctl) so pBuffer->size is set and mpr_get_fw_diag_buffer_number() finds the caller's UniqueId (mpr_user.c:1787-1791). Registration uses the same operator-group ioctl, so a single attacker session registers then over-reads.

Reachability on this guest

Same as DF-1327/1328: mpr is in GENERIC, no SAS HBA β†’ no /dev/mpr0:

poc: open /dev/mpr0: No such file or directory   (RUN_EXIT=1)

Privilege model identical (operator group + HBA on a real host).

Phase-4(d): real code path, unreachable on this guest.

Exploit chain

None β€” read-only OOB read (the overflow defeats the read bounds check, not a write). Impact ceiling: large kernel-heap disclosure from the diag DMA buffer and adjacent memory; KASLR-bypass / heap reconnaissance.

PoC changes

Folder was empty. Authored poc.c (opens /dev/mpr0, issues MPTIOCTL_DIAG_ACTION READ_BUFFER with the wrap pair StartingOffset=0x100, BytesToRead=0xFFFFFF00), build.sh, run.sh. The PoC notes that a real run must first REGISTER a diag buffer to obtain a valid UniqueId.

Fix

fix.diff restructures the check to avoid the addition entirely β€” test each bound independently:

if (StartingOffset >= pBuffer->size ||
    BytesToRead > pBuffer->size - StartingOffset)
        reject;

StartingOffset >= size rejects an empty/over-large offset outright; once it passes, size - StartingOffset cannot underflow, and the second test bounds BytesToRead against the remaining space with no wrapping sum. Applies cleanly and was built in the full nativekernel run (fix_build.log).

Fix validation

fix_status: not_testable β€” PoC cannot run (no /dev/mpr0). Validated by apply + compile + inspection (the wrapping addition is gone; both bounds are checked with non-overflowing arithmetic).

Fix verification

not_testable

compile validated

nativekernel rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. mpr_diag_read_buffer uint32 offset+size wrap -> 4GiB OOB read. mpr in GENERIC, no SAS HBA.