# DF-4021 — mpr_user_btdh off-by-one: `target > sc->max_devices` lets TargetID == max_devices read mapping_table[max_devices] out of bounds

Twin of **DF-4016** (mps), filed for the `sys/dev/raid/mpr` sibling. Source
analysis only — PoC skipped per contract (Low severity, HW-gated: the ioctl
needs an attached mpr(4) HBA, absent on the audit guest). The mps twin's
source-verification record is `findings/poc/DF-4016/`.

## Source path

- `mpr_user_btdh()`: `sys/dev/raid/mpr/mpr_user.c:2210-2256`.
- Bus/Target→DevHandle mode requires `bus == 0` and then guards the target
  index with **`if (target > sc->max_devices)`**
  (`sys/dev/raid/mpr/mpr_user.c:2240-2244`) — `target == sc->max_devices`
  passes.
- The passed index is immediately used:
  `dev_handle = sc->mapping_table[target].dev_handle;`
  (`sys/dev/raid/mpr/mpr_user.c:2245`).
- The mapping table has **exactly `sc->max_devices` entries**:
  `sc->mapping_table = kmalloc(sizeof(struct dev_mapping_table) *
  sc->max_devices, ...)` (`sys/dev/raid/mpr/mpr_mapping.c:2140-2141`), so
  valid indices are `0 .. max_devices-1`. Index `max_devices` reads one
  element past the end.
- `struct dev_mapping_table` (`sys/dev/raid/mpr/mprvar.h:113-121`, 24-byte
  stride): `dev_handle` is the u16 at byte offset 18 of the element, so the
  OOB load lands at `mapping_table + max_devices*24 + 18` — a 2-byte
  out-of-bounds heap read at the allocation boundary.
- If the garbage u16 is nonzero it is disclosed to the caller in
  `data->DevHandle` (`mpr_user.c:2246-2247`). Reachable by any member of
  group operator via `MPTIOCTL_BTDH_MAPPING` (`mpr_user.c:2406-2412`) on the
  0640 `root:operator` `/dev/mprN` node (`mpr_user.c:205`); `TargetID` is a
  user-controlled u16 (`mpr_ioctl.h:347-353`) so `TargetID = max_devices`
  (e.g. 256-ish for typical IOC facts) is directly requestable.

## Impact

One 24-byte-stride OOB heap read disclosing a single garbage u16 to an
operator-group caller — Low: info disclosure of adjacent heap bytes, no
write, no control over the disclosed value beyond the index. Same geometry
and severity as the mps twin (DF-4016).

## Fix

Change the guard to reject the boundary index:

```diff
-		if (target > sc->max_devices) {
+		if (target >= sc->max_devices) {
```

(`sys/dev/raid/mpr/mpr_user.c:2240`; the mps twin fix is identical at
`sys/dev/raid/mps/mps_user.c:2021`.)
