# DF-1374 — VERDICT

**Verdict: REPRODUCED (primitive confirmed at object/harness level; runtime path is hardware-bound and not present on the audit guest).**

This is the **mps driver's twin of DF-1282** (the identical bug in the `mpr`
driver's `mpr_mapping.c`). The DB `file` column reads `mpr_mapping.c` but the
cited line numbers (1495/1497/1544/1567) and the bug description match
`sys/dev/raid/mps/mps_mapping.c` exactly — the mps copy of the code. (In
`mpr_mapping.c` the same statements are at 2251/2260/2327/2358, already covered
by DF-1282.) The fix is authored against `mps_mapping.c` where the cited lines
actually reside.

## Mechanism (source trace)

`_mapping_process_dpm_pg0()` (`sys/dev/raid/mps/mps_mapping.c:1461`) copies the
HBA's persistent Device Persistence Mapping (DPM) entries into the driver's
`mapping_table`.

1. **Attacker-controlled source value** — `mps_mapping.c:1495`:
   ```c
   dev_idx = le16toh(dpm_entry->DeviceIndex);   /* u32, firmware-controlled */
   ```
   `dev_idx` is declared `u32` (`:1465`) and read directly from the
   controller's DPM page, whose contents are written by HBA firmware
   (influenced by attached devices) and/or NVRAM.

2. **Allocation size** — `mapping_table` is `kmalloc(sizeof(dev_mapping_table)
   * sc->max_devices, ...)`; `max_devices = facts->MaxTargets + max_volumes`,
   a `uint16_t` (`mpsvar.h`); `MaxTargets` is `U16`. Typical ~264.

3. **Bounded path (IR firmware)** — `mps_mapping.c:1497`:
   ```c
   if (sc->ir_firmware && (dev_idx >= start_idx) && (dev_idx <= end_idx)) { ... }
   ```
   This is the ONLY path that validates `dev_idx`.

4. **UNBOUNDED sinks** — `mps_mapping.c:1544` (Enc/Slot) does
   `mt_entry = &sc->mapping_table[dev_idx];` and `:1545` writes `num_slots`
   consecutive entries in a loop (`num_slots` also firmware-derived); `:1567`
   (Device Persistence) does `map_idx = dev_idx; mt_entry = &sc->mapping_table[map_idx];`
   — both with **no** check that `dev_idx < max_devices`.

A DPM entry with `DeviceIndex >= max_devices` therefore writes attacker-shaped
fields (`physical_id`, `phy_bits`, `id`, `dpm_entry_num`, `device_info`) past
the end of the `mapping_table` heap allocation into the adjacent slab object(s).

## Primitive characterization

- **Write size:** `sizeof(struct dev_mapping_table)` = 32 bytes per entry.
- **Multiplier:** Enc/Slot path writes `num_slots` consecutive entries (up to a
  firmware-controlled value); Device Persistence writes 1.
- **Content control:** largely attacker-shaped (`physical_id` = attacker
  enclosure WWN; `phy_bits`; `device_info = MPS_DEV_RESERVED`).
- **Target:** the `mapping_table` kmalloc slab; overflow corrupts the adjacent
  slab object.

## Harness proof

`harness.c` allocates `mapping_table[MAX_DEVICES]` + a canary guard region and
replays the Enc/Slot path with `dev_idx = max_devices+2`, `num_slots = 4`.
Output (`run.log`):
```
[DF-1374] BUG CONFIRMED: dev_idx=266 >= max_devices=264 -> write at mapping_table[266] is 64 bytes PAST the allocation (into the adjacent slab object)
[DF-1374] guard/slab region corrupted: YES -> next heap object(s) overwritten (4 of 64 entries touched)
```

## Why not a live in-kernel reproduction (the valid hard blocker)

The `mps` driver attaches to LSI/Avago SAS HBAs. The audit guest is a QEMU/KVM
VM with **only virtio devices** (no SAS/RAID HBA). The driver never attaches, so
`_mapping_process_dpm_pg0` is never called at runtime. Phase-6 valid hard
blocker #3: the vulnerable code path is unreachable at runtime on this guest;
the primitive is proven at the object/harness level and the live trigger
conditions (presence of an `mps`-attached HBA whose DPM table holds a malformed
entry — reachable via a malicious peripheral, malicious firmware, NVRAM
corruption, or a passed-through HBA to a malicious VM) are noted.

## Exploit chain / escalation

Write-capable primitive, but escalation requires the primitive to fire inside a
running kernel, which on this guest it cannot (no HBA). On a host with an `mps`
HBA present, the chain would be: trigger via topology/DPM event → overflow
corrupts adjacent slab object → groom so the victim is a function-pointer-bearing
or `ucred`-bearing object → redirect → `uid=0`. That chain cannot be demonstrated
on this guest (hardware absent); the honest reported impact is the corruption
primitive itself.

## Fix

`fix.diff` adds the missing bounds checks (`dev_idx >= sc->max_devices` and
`dev_idx + num_slots > sc->max_devices`) before the Enc/Slot indexings and
`dev_idx >= sc->max_devices` before the Device-Persistence indexing, mirroring
the check the IR path already performs. **Validated:** `patch -p1 --dry-run`
succeeds (both hunks), and a clean `mps.ko` build succeeds (`rc=0` —
`fix_build.log`; mps.ko grew to 193880 bytes vs 193512 baseline, confirming the
patched TU compiled in). Supersedes any pre-verification proposal by covering
both unbounded paths.

## Fix-validation status

`not_testable` for a *live* before/after (the driver path cannot run on the
guest). Evidence the fix is correct: (1) harness before/after shows the check
closes the primitive; (2) the fix compiles cleanly in-tree under `-Werror`.
