# DF-4092 VERDICT

**Status:** not_reproduced (kernel level) — hardware-gated; **primitive proven
at object level on the audit guest** (userland harness, verbatim loop
including the conflict gate). **Confidence:** certain (source arithmetic +
object-level proof; only the in-kernel execution is gated by absent
hardware).

mpr twin of DF-4085 (mps). Filed separately because the mps pass-2 audit
deliberately did not double-file sibling findings.

## Source chain (line-by-line)

1. `sys/dev/raid/mpr/mpr_pci.c` attach → `mpr_startup` → `mpr_mapping_initialize(sc)`
   (mpr_mapping.c:2545ff).
2. `mpr_mapping.c:2564-2565` `max_volumes = facts->MaxVolumes` (u8),
   `max_devices = facts->MaxTargets + facts->MaxVolumes` (u16 each, sum ≤ 511
   by MPI2 field widths). `:2569-2570` `max_dpm_entries =
   ioc_pg8.MaxPersistentEntries; is_dpm_enable = !!max_dpm_entries`.
3. `mpr_mapping.c:2140-2141` `mapping_table = kmalloc(sizeof(struct
   dev_mapping_table) * max_devices, M_MPR, M_ZERO|M_NOWAIT)` — 32
   bytes/entry, verified: harness prints sizeof == 32 on amd64
   (struct mprvar.h:113-125: u64+u32+u32+4*u16+4*u8 = 8+4+4+8+4 = 28 →
   padded to 32; **same 32-byte stride as the mps twin** despite the
   reserved1/channel layout difference).
4. `mpr_mapping.c:2593-2594` every entry cleared by `_mapping_clear_map_entry`
   (:65-75) → `dpm_entry_num = MPR_DPM_BAD_IDX` (0xFFFF).
5. `mpr_mapping.c:2614-2628` DPM pg0 page read into `sc->dpm_pg0` and
   `_mapping_process_dpm_pg0()` called (:2627) whenever `is_dpm_enable`.
6. `mpr_mapping.c:2216` `max_num_phy_ids = le16toh(sc->ioc_pg8.
   MaxNumPhysicalMappedIDs)` — firmware u16 from IOC page 8; grep confirms
   **no validation against max_devices anywhere in the driver** (all other
   uses are search-skip hints :955/:979/:1045/:1718/:1975).
7. `mpr_mapping.c:2273-2296` ENCLOSURE_SLOT_MAPPING branch:
   `dev_idx < num_rsvd_entries + max_num_phy_ids` ⇒ **`num_slots =
   max_num_phy_ids`** (:2290, up to 65535). Else-branch `num_slots` ≤ 0xF
   (4-bit slot field) — bounded.
8. `mpr_mapping.c:2327-2347` `mt_entry = &sc->mapping_table[dev_idx];`
   `for (map_idx = dev_idx; map_idx < (dev_idx + num_slots); map_idx++,
   mt_entry++)` — per entry: conflict gate `dpm_entry_num != MPR_DPM_BAD_IDX`
   → `goto fail` (:2330-2336), else writes `physical_id` (fw 64-bit
   PhysicalIdentifier, :2340), `phy_bits` (:2342), `id` (:2343),
   `dpm_entry_num` (:2344), `missing_count` (:2345), `device_info =
   MPR_DEV_RESERVED` (:2346). **No check that `dev_idx + num_slots <=
   max_devices`.**

Overflow condition: `dev_idx + max_num_phy_ids > max_devices` with `dev_idx`
in bounds (e.g. MaxTargets=508, MaxVolumes=2 ⇒ max_devices=510;
MaxNumPhysicalMappedIDs=0xFFFF; dev_idx=0x100):
- entries `[dev_idx, max_devices)` are always overwritten (conflict gate
  passes on the BAD_IDX-cleared table);
- the walk then crosses the allocation end: **2-byte OOB read** (u16
  dpm_entry_num at offset 16 of each 32-byte block) decides per block, and
  every consecutive block whose garbage u16 == 0xFFFF takes a **full 32-byte
  fw-controlled write**. Worst case (all-BAD_IDX region, e.g. a stale
  mapping table from a previous attach/reset cycle adjacent in the heap —
  `_mapping_clear_map_entry` fills whole tables with 0xFFFF sentinels):
  65,281 entries × 32 B = **2,088,992 bytes** past the 16,320-byte kmalloc,
  same magnitude as the mps twin.

### Distinct from the existing mpr findings

- **DF-3914** = *event* `NumSlots` reservation path (`_mapping_get_dev_info`
  / `_mapping_get_pcie_dev_info`), direct-attached boot default. This
  finding is the **DPM-pg0 ingestion** path with num_slots sourced from IOC
  pg8 `MaxNumPhysicalMappedIDs` — different data source, different loop,
  and (unlike DF-3914's boot-default path) requires only DPM to be enabled.
- **DF-3915** = `_mapping_commit_enc_entry` wild `start_index` read/NVRAM
  write when slot field is 0 — different function and sink.
- Both DF-3914/3915 fixes (bound `dev_idx`, bound event num_slots) do not
  touch the :2290 `num_slots = max_num_phy_ids` assignment; the count here
  is a distinct unvalidated firmware field with an **in-bounds** index.

## Verification performed

- Guest `pciconf -l` (env.txt): virtio-only PCI bus, no SAS2208/2308/3000
  HBA ⇒ `mpr_attach` never runs; kernel-level reproduction impossible on
  this target (same gate as DF-3914/DF-3915/DF-4085; guest is otherwise the
  intentionally permissive no-SMAP/SMEP/KASLR target — absent hardware is
  the only blocker).
- `harness_df4092.c` built and run **twice** as the unprivileged user on the
  guest (cc 8.3, zero warnings): verbatim mpr struct + verbatim
  :2327-2347 loop including the `goto fail` conflict gate. Three adjacent-
  heap scenarios:
  - A (cold garbage): 254 in-bounds entries written `[256,510)`, gate trips
    on the first OOB block — proves the unconditional in-bounds overwrite
    and the OOB read that gates the write;
  - B (4096 groomed OOB blocks): 4,096 × 32 B = 131,072 bytes of
    fw-controlled canary past the table until the first non-0xFFFF block;
  - C (all-0xFFFF region): 65,281 × 32 B = 2,088,992 bytes linear smash —
    the unbounded worst case, identical magnitude to the mps twin DF-4085.
  Canary 0x5141524153534141 lands at offset 0 of every 32-byte slot past
  the table end (verified programmatically; exit 0). `run.log`/`run.2.log`
  identical across runs.
- **This harness is stricter than the mps DF-4085 harness**: it models the
  per-entry `dpm_entry_num == MPR_DPM_BAD_IDX` conflict gate that both the
  mps and mpr kernels actually execute (the mps harness omitted it), so the
  OOB write is characterized precisely as "linear through consecutive
  0xFFFF-sentinel blocks" rather than unconditional.

## Fix validation

- `fix.diff` (this pack): bound-skip — reject any DPM entry with
  `dev_idx >= sc->max_devices` or `num_slots > sc->max_devices - dev_idx`
  before the enclosure-table store and the reservation loop (`continue`,
  matching the loop structure; `mpr_dprint(MPR_ERROR|MPR_MAPPING)` matching
  file style). Same shape as the validated DF-4085 fix.
- Applied together with DF-4093's clamp to the guest `/usr/src`;
  `cd /usr/src/sys/dev/raid/mpr && make` compiled **all** module sources
  with `-Werror` (flags include -Wall -Wredundant-decls -Wnested-externs
  -Wstrict-prototypes -Wmissing-prototypes ...) and linked **mpr.ko**
  (BUILD_EXIT=0) — `fix_build.log`.
- Runtime fix validation not testable: the path requires an mpr(4) HBA
  delivering firmware pages, absent on this guest.
- `/usr/src` restored pristine after the build (md5 match with repo,
  reverse-patch verified, no leftover artifacts).

## Bottom line

Source-certain CWE-787 heap OOB write at driver-attach time, primitive
proven at object level on the audit guest with the exact mpr struct/loop;
kernel-level reproduction hardware-gated (no mpr HBA in the QEMU guest).
Fix compiles clean with -Werror; recommended for upstream together with the
mps twin DF-4085.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
