# DF-1556 — PoC: missing-MIN() heap overflow in mps_config_get_raid_volume_pg0

- **File:** `sys/dev/raid/mps/mps_config.c:1117`
- **Class:** CWE-787 Out-of-bounds Write (missing length guard)
- **Severity:** High
- **Status:** REPRODUCED (source-trace + userspace harness; latent on this guest — no LSI HBA)

## Build

```sh
./build.sh    # cc -O2 -Wall -Wextra -o harness harness.c
```

## Run

```sh
./run.sh              # default: PageLength=22 (minimal +4 byte overflow)
./run.sh 22           # explicit minimal overflow
./run.sh 255          # max overflow with U8 PageLength (+936 bytes)
./run.sh 21           # control: exact-fit, no overflow
```

## Expected output (bug present)

```
[BUGGY] bcopy(page, caller_buf, cm_length=88) into 84-byte buffer...
overflow by 4 bytes -> write will land in guard page (PROT_NONE) -> SIGSEGV
>>> SIGSEGV/11 caught: OOB write past 84-byte caller buffer confirmed.
>>> PRIMITIVE CONFIRMED: a malicious HBA returning PageLength=22 (cm_length=88)
    overflows the mps_wd_config_pages RAID-volume buffer by 4 bytes.
>>> This is the exact code at sys/dev/raid/mps/mps_config.c:1117 (no MIN()).
```

The control run (`./run.sh 21`) prints `cm_length=84 <= buf=84: no overflow
this run.` and exits cleanly, confirming the overflow boundary is exactly at
`PageLength=22`.

## How it works

The harness reproduces the exact C logic of the kernel
`mps_config_get_raid_volume_pg0()` final `bcopy`:

```c
/* mps_config.c:1084 */  cm->cm_length = le16toh(mpi_reply->Header.PageLength) * 4;
/* mps_config.c:1117 */  bcopy(page, config_page, cm->cm_length);  /* no MIN() */
```

against a destination buffer sized identically to the sole in-tree caller's
allocation (`mps_config.c:378-380`):

```c
raid_vol_pg0 = kmalloc(sizeof(Mpi2RaidVolPage0_t) +
    (sizeof(Mpi2RaidVol0PhysDisk_t) * MPS_MAX_DISKS_IN_VOL), /* = 44 + 4*10 = 84 */
    M_MPT2, M_ZERO | M_INTWAIT);
```

A `PROT_NONE` guard page is placed immediately after the 84-byte caller
buffer so any overflow faults with `SIGSEGV`, proving the primitive.  Every
sibling config-page getter in `mps_config.c` (lines 163, 634, 880, 998, 1237,
1382) uses `MIN(cm->cm_length, sizeof(...))`; only line 1117 omits it.

## Why a harness (not a live trigger)?

The audit guest has no LSI SAS HBA, so the `mps(4)` driver — although
compiled into `X86_64_GENERIC` — never attaches, and the live
`mps_wd_config_pages()` → `mps_config_get_raid_volume_pg0()` code path is
never executed at runtime on this guest.  The harness reproduces the exact
kernel C logic in userspace to prove the primitive; the bug itself is
confirmed by source trace at `sys/dev/raid/mps/mps_config.c:1117`.

## Fix

See `fix.diff` — bound the final `bcopy` by the caller's actual buffer
capacity (`sizeof(Mpi2RaidVolPage0_t) + sizeof(Mpi2RaidVol0PhysDisk_t) *
MPS_MAX_DISKS_IN_VOL`), preserving the legitimate PhysDisk tail.  Validated
to apply cleanly and compile into the single-fix `#1` kernel with `-Werror`
and zero warnings; see `VERDICT.md` and `fix_run.log`.

## Files

- `harness.c` — userspace reproduction of the kernel bcopy logic
- `build.sh`, `run.sh` — exact reproducible commands
- `build.log`, `run.log`, `run.2.log`, `run.3.log` — full run logs
- `fix.diff` — git-apply-able fix (supersedes the finding proposal)
- `fix_build.log`, `fix_run.log` — Phase 8 kernel-build validation
- `env.txt` — guest environment
- `VERDICT.md` — full narrative
- `manifest.json` — machine-readable catalog
