# DF-3914 — Unbounded firmware `NumSlots` heap OOB write in the direct-attached enclosure reservation path

**File:** `sys/dev/raid/mpr/mpr_mapping.c` (SAS: `:1241-1243` + `:1292-1299`; PCIe: `:1392-1394` + `:1443-1450`)
**Class:** CWE-787 Out-of-bounds Write (heap)
**Severity:** High — pass 2 (GLM 5.3)
**Not** a duplicate of DF-1282 (DPM-pg0 `DeviceIndex` write at `:2327`) or DF-1283
(`slot - start_slot` underflow at `:1637`): different entry path (live topology
event, not DPM ingestion) and different missing bound (table size vs `NumSlots`).

## The bug (source-confirmed)

1. An Enclosure Status Change event creates an enclosure-table entry with
   `et_entry->num_slots = le16toh(event_data->NumSlots)` (`mpr_mapping.c:2843`).
   The **only** validation of `NumSlots` in the entire file is `!= 0`
   (`:2760-2765`). It is a firmware/u16 value (0..65535).
2. When devices for that enclosure appear, `_mapping_get_dev_info()`
   (SAS, `:1241`) / `_mapping_get_pcie_dev_info()` (PCIe, `:1392`) map the
   enclosure:
   * **expander path** (`exp_handle != 0`): `_mapping_find_enc_map_space()`
     only returns a `start_idx` after finding `num_slots` **contiguous free
     entries below `end_of_table <= max_devices`** (`:979-993`) — bounded.
   * **direct-attached path** (`exp_handle == 0` / `switch_dev_handle == 0`):
     ```c
     map_idx = sc->num_rsvd_entries;          /* :1242 */
     et_entry->start_index = map_idx;         /* :1243 — NO num_slots bound */
     ```
     … and then the reservation loop (`:1292-1299`, PCIe `:1443-1450`)
     initializes `et_entry->num_slots` consecutive entries starting at
     `num_rsvd_entries` with **no check that
     `num_rsvd_entries + num_slots <= sc->max_devices`**.
3. `sc->mapping_table` is `kmalloc(max_devices * 32, M_MPR)` (`:2140-2141`),
   `max_devices = facts->MaxTargets + MaxVolumes` (u16, `:2565`, typically
   ~256-2048).

An enclosure whose reported `NumSlots` exceeds the free table space (lying or
compromised enclosure/backplane SES data surfaced by IOC firmware, malicious
IOC firmware, or NVRAM corruption) therefore produces a heap OOB write of up
to `(0xFFFF - max_devices + num_rsvd) * 32` bytes (~2 MB) past the
`mapping_table` allocation, writing at a 32-byte stride:
`physical_id` = enclosure SAS address (**fully firmware-controlled 64 bits**),
`device_info` = `MPR_DEV_RESERVED`, `phy_bits` = firmware u32,
`missing_count` = 0.

## Reproduce

Hardware-gated: the audit QEMU guest has no LSI/Avago SAS3 HBA (`pciconf -l`
shows virtio-only — `env.txt`), so `mpr_attach` never runs there. The pack
contains an object-level harness that replicates the exact structs and the
exact unguarded loops and proves the write primitive (canary region after the
allocation receives the attacker value at the 32-byte stride):

```sh
build.sh   # cc -O2 -Wall -o harness_df3914 harness_df3914.c
run.sh     # ./harness_df3914
```

Expected (observed on the guest, `run.log`):

```
  enclosure NumSlots (fw)  : 2048
  reservation loop wrote   : [1, 2049)
  table end                : 264  -> overflow of 1785 entries (57120 bytes)
  guard[+0..] physical_id == 0x5141524153534141 at 1785/4096 entries (stride 32)
RESULT: OOB WRITE PRIMITIVE PROVEN (canary smash with attacker value, 32-byte stride)
```

## Fix

`fix.diff` bounds `num_slots` on both direct-attached paths and reuses the
existing "no space found" handling (mark the event's add entries processed and
stop). Compile-validated: applied to the guest `/usr/src` copy and compiled
with `-Werror` in both the static-kernel and KLD_MODULE passes of
`make nativekernel` (`fix_build.log`).
