# DF-3915 — Wild OOB read (and NVRAM garbage write) in `_mapping_commit_enc_entry` via unvalidated enclosure `start_index`

**File:** `sys/dev/raid/mpr/mpr_mapping.c:129-130` (sink); ingestion `:2310-2311`; trigger `:2481-2493`
**Class:** CWE-125 Out-of-bounds Read (wild pointer)
**Severity:** Medium — pass 2 (GLM 5.3)
**Not** a duplicate of DF-1282: same DPM `DeviceIndex` *source*, but a
different sink reached through a state DF-1282's own write cannot produce
(`num_slots == 0` bypasses the reservation loop entirely).

## The bug (source-confirmed)

1. `_mapping_process_dpm_pg0()` ingests each DPM enclosure entry with:
   - `dev_idx = le16toh(dpm_entry->DeviceIndex)` (`:2251`) — unchecked u16;
   - `num_slots = MappingInformation & 0x07F0 >> 4` (`:2293-2295`) — **can be 0**;
   - `et_entry->start_index = dev_idx` (`:2310`) and
     `et_entry->dpm_entry_num = entry_num` (`:2311`) — stored unvalidated;
   - the reservation loop `:2328-2347` (`for (map_idx = dev_idx; map_idx <
     dev_idx + num_slots; …)`) **executes zero iterations when
     `num_slots == 0`**, so DF-1282's OOB write in this same function never
     fires — the corrupt entry survives ingestion quietly (`init_complete`
     stays 0).
2. `mpr_mapping_check_devices()` (`:2481-2493`) later walks the enclosure
   table; for any `!init_complete` entry it bumps `missing_count` and, when
   `dpm_entry_num != MPR_DPM_BAD_IDX`, calls
   `_mapping_commit_enc_entry(sc, et_entry)` (`:2491`).
3. `_mapping_commit_enc_entry()` dereferences
   `mt_entry = &sc->mapping_table[et_entry->start_index]` (`:129`) and reads
   `mt_entry->id` into `dpm_entry->DeviceIndex` (`:130`) — with
   `start_index` up to `0xFFF0`+ this is a read up to
   `65535 * 32 = 2,097,120` bytes past the `max_devices * 32`-byte
   `mapping_table` allocation (`:2140`).

Consequences: wild read → panic on unmapped page (local DoS from the HBA's
perspective), or silent garbage persisted into the controller's NVRAM DPM
entry (`DeviceIndex`), which then feeds `_mapping_process_dpm_pg0` on the next
boot. Preconditions: Enc/Slot mapping mode, a DPM enclosure entry with slot
bits == 0 and out-of-range `DeviceIndex` (malicious/corrupt NVRAM or IOC
firmware), and ≥1 other enclosure that completed init (`:2438-2446` gates the
walk).

## Reproduce

Hardware-gated (no SAS3 HBA on the audit guest — `env.txt`). The harness
replicates the exact structs/flow: proves (a) the `num_slots == 0` entry
executes the `:2328` loop zero times (DF-1282 bypassed) and (b) the `:129`
indexing lands `0xFFF0 * 32 = 2,096,640` bytes past an 8,448-byte allocation
(guard-page variant included).

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

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

```
  DPM entry: dev_idx=0xfff0 num_slots=0 dpm_entry_num=3
  reservation loop (:2328) iterations: 0 (DF-1282 write BYPASSED)
  mapping_table size       : 8448 bytes (max_devices=264)
  start_index*32           : 2096640 bytes past base
  read mt_entry->id        : 0x5f36 (silent garbage; kernel feeds it to NVRAM)
RESULT: WILD READ PRIMITIVE PROVEN - index 0xfff0 * 32 = 2096640 bytes past a 8448-byte allocation
```

## Fix

`fix.diff` validates `et_entry->start_index` (`BAD` or `>= max_devices`) at
the top of `_mapping_commit_enc_entry` and refuses the DPM commit otherwise.
Compile-validated with `-Werror` on the guest (`fix_build.log`).
