# 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:
```c
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).
