# DF-1227 — mpr_build_nvme_prp heap overflow via unbounded user-supplied data length

## Claim
`mpr_build_nvme_prp()` (`sys/dev/raid/mpr/mpr.c:2716`) allocates **one** PRP
list page (`mpr_alloc_prp_page` → a single `PAGE_SIZE` buffer holding
`PAGE_SIZE/8 = 512` entries). Its `while(length)` loop (`mpr.c:2757`) is
driven by `data_in_sz`/`data_out_sz`, which the ioctl caller
`mpr_user_pass_thru()` (`sys/dev/raid/mpr/mpr_user.c`) copies verbatim from
the user's `mpr_pass_thru.DataSize`/`DataOutSize` with **no cap against
`sc->maxio`** (only `RequestSize` is capped, at `mpr_user.c:804`). A user
`DataSize` of e.g. 8 MB describes ~2048 pages → the loop writes ~1536 PRP
entries **past the end of the single PRP page** → kernel heap overflow with
attacker-controlled physical addresses. Reachable on the NVMe-encapsulated
branch (`function == MPI2_FUNCTION_NVME_ENCAPSULATED`, `mpr_user.c:945`).

## Verdict
**NOT TESTABLE on this audit guest** (real bug, traced line-by-line; fix
authored and compile-validated into GENERIC). The guest has **no LSI SAS
(mpr) controller**, so `/dev/mpr0` does not exist and the ioctl is
unreachable here. See `VERDICT.md`. **A runnable PoC (`mpr_nvme_prp_overflow.c`)
is included** — it triggers the overflow on any host that (a) has an attached
mpr(4) controller and (b) lets the caller open `/dev/mprN` rw (root or the
`operator` group; `mpr_user.c:205` creates the node `UID_ROOT, GID_OPERATOR,
0640`).

## Why not reproduced here
- `pciconf -l` shows no LSI/SAS controller and there is no `/dev/mpr*`; the
  cdev is created only in `mpr_attach` (`mpr_user.c:205`).
- Even with the controller, the node is `0640 root:operator` — a non-operator
  unprivileged user (e.g. `maxx`, uid 1001, not in wheel/operator) cannot
  open it. So the realistic privilege boundary is operator/root → kernel
  (a local hardening gap), not unpriv → root.

## Trigger (on suitable hardware)
```
  cc -O -I/usr/src/sys -o mpr_nvme_prp_overflow mpr_nvme_prp_overflow.c
  ./mpr_nvme_prp_overflow /dev/mpr0          # as root or operator
```
Issues `MPTIOCTL_PASS_THRU` with `Function = MPI2_FUNCTION_NVME_ENCAPSULATED`
and `DataSize = 8 MB`. On an unpatched kernel `mpr_build_nvme_prp` overflows
its single PRP page before the ioctl returns.

## Fix
`fix.diff` caps the user `DataSize`/`DataOutSize` to `sc->maxio` in
`mpr_user_pass_thru`, right after the existing `RequestSize` cap
(`mpr_user.c:804`) — the root-cause fix matching how the rest of the driver
treats `maxio` as the maximum transfer.
