# DF-1918 — Verification Verdict

## Verdict: REPRODUCED (source-confirmed + sense_off-OOB-harness) — Phase-6 hard blocker (no HBA)

The unbounded-`sense_off` OOB pointer-sized write is confirmed at
`sys/dev/raid/mrsas/mrsas_ioctl.c:291-293`.  The harness reproduces the
verbatim buggy store against a 1024-byte model of `cmd->frame` and shows
that for every attacker-supplied `sense_off` that would push
`sense_off + sizeof(unsigned long)` past `MRSAS_MFI_FRAME_SIZE`, the
kernel writes 1..8 bytes of `ioctl_sense_phys_addr` past the 1024-byte
DMA allocation into adjacent kernel heap.

## Mechanism

```c
// mrsas_ioctl.c:267  -- attacker must also set sense_len > 0 (trivial)
if (user_ioc->sense_len) {
    ...
    // mrsas_ioctl.c:291-293  -- u32 offset used verbatim
    sense_ptr = (unsigned long *)
        ((unsigned long)cmd->frame + user_ioc->sense_off);
    *sense_ptr = ioctl_sense_phys_addr;       // <-- 8-byte unchecked store
}
```

`cmd->frame` is 1024 bytes; `sizeof(unsigned long)` is 8 on amd64;
`sense_off` is `u_int32_t` (`mrsas_ioctl.h:85`), never validated.  With
`sense_off=1017` the last 1 of 8 bytes spills; with `sense_off=1024`
all 8 bytes spill; with `sense_off=0xFFFFFFF8` the pointer wraps to
`cmd->frame - 8`.

The written VALUE is the kernel physical address of the just-allocated
sense DMA buffer (low-entropy, attacker cannot directly choose it), so
this primitive is naturally a write-what-where of a fixed value at an
attacker-chosen offset — still very serious (corrupt the right adjacent
object and you control its interpretation), but harder to weaponize
alone than DF-1919's attacker-bytes overflow.

## Harness evidence (run.log)

```
DF-1918: mrsas_passthru unbounded sense_off (mrsas_ioctl.c:291-293)
  sense_off=0x00000080 -> 0/8 bytes past 1024-byte frame (in-bounds)
  sense_off=0x000003f8 -> 0/8 bytes past 1024-byte frame (in-bounds)
  sense_off=0x000003f9 -> 1/8 bytes past 1024-byte frame (OOB WRITE)
  sense_off=0x00000400 -> 8/8 bytes past 1024-byte frame (OOB WRITE)
  sense_off=0x00001400 -> 8/8 bytes past 1024-byte frame (OOB WRITE)
  sense_off=0xfffffff8 -> 8/8 bytes past 1024-byte frame (OOB WRITE)
```

## Why no live trigger / Phase-6 hard blocker

Same as DF-1917.  `/dev/mrsas0` is created only by `mrsas_attach`
(`mrsas.c:790-792`), which runs only when an LSI MegaRAID SAS HBA is
probed (`mrsas.c:3668-3669`).  The QEMU audit guest has no such HBA
(verified: `pciconf -lv` lists none; `/dev/mrsas*` absent), and the
node would be `0660 root:operator` while `maxx` (uid 1001) is not in
`operator`.  Valid Phase-6 hard blocker (path dead at runtime on this
guest; primitive proved at harness level).

## Exploit chain

Not applicable on this guest.  On real hardware with the HBA, an
operator-group member would have an arbitrary-offset 8-byte write of a
fixed low-entropy kernel-physical address relative to `cmd->frame`.
Useful as a write-what-where tool combined with an info leak (defeats
KASLR / leaks the sense-buffer phys addr to control the value); alone
it is a corruption primitive.  Not demonstrable end-to-end on this
guest.

## PoC changes

- Added `harness.c`: verbatim buggy store against a 1024-byte frame
  model; 6 test vectors covering in-bounds, edge, 1..8-byte spill,
  pointer-wrap.
- Added `fixcheck.c`: models the patched predicate from `fix.diff`
  (`mrsas_ioctl.c:323-324`) and shows it rejects the 4 OOB vectors.
- Added `fix.diff`: validates `sense_off <= MRSAS_MFI_FRAME_SIZE -
  sizeof(unsigned long)` before the store, returning `EINVAL` otherwise.

## Fix validation (Phase 8)

`fix.diff` applied as part of the combined patch (with DF-1917 and
DF-1919, same file).  Kernel rebuilt cleanly, single-fix kernel
installed as `/boot/kernel/kernel` (sha256
`c8c9a25c98bc8e06c300820f141d8d1a3e89dcda21c7d2585b36bf8ddb72f064`),
booted as `6.5-DEVELOPMENT #1: Mon Jul 20 20:05:20 UTC 2026`.
`fixcheck.c` shows the patched predicate rejects 4/6 vectors (the 4
that caused OOB), accepts the 2 in-bounds vectors.

- baseline (#0 `5dc83dac…`): harness shows 4 vectors with OOB write.
- patched (#1 `c8c9a25c…`): fixcheck shows the same 4 vectors now
  `REJECTED (EINVAL)`.

`fix_status: fixed` — patched predicate closes the OOB inputs; patched
kernel is bootable and stable.
