# DF-3691 — No parser validates array `width`; the I/O strategy turns it into OOB disks[] access (incl. wild `dev` deref and OOB `last_lba` writes)

## Root cause (source)
Every vendor parser copies an attacker-controlled "array width" into
`raid->width` with no check against `MAX_DISKS` (`disks[]` is 16 entries):

- Adaptec  `:1685` `width = be16toh(configs[0].total_disks)` — **16-bit** (0..65535)
- HPTv2    `:1868` `width = meta->array_width` (u8)
- HPTv3    `:2039/:2045/:2051/:2057` `width = configs[0].total_disks` (u8, or /2)
- LSI v2   `:2828/:2835/:2843` `array_width` (u8)
- LSI v3   `:2971/:2976` `total_disks`/`array_width` (u8)
- nVidia   `:3108` `array_width` (u8)
- Promise  `:3248` `array_width` (u8)
- SII      `:3563` `raid0_disks` (u8)

`total_disks` (which gates READY) is a *separate* metadata field in most
formats, so an array can be READY with a single online disk while width is
arbitrary. The volumetric I/O path then indexes `disks[]` with width-derived
values:

- RAID0/RAID5 `ata_raid_strategy:360/:366` — `drv = (blkno / interleave) % width`
  (RAID0) → user LBA chooses `drv` in [0, width); `:400-409` reads
  `rdp->disks[drv].flags` OOB and sets `request->dev = rdp->disks[drv].dev`
  from OOB heap → `ata_raid_send_request` → `device_get_softc(wild)`.
- RAID1/01 `:421-426`, `:441-463` — reads `disks[drv + width]` OOB;
  `:581` `drv += width` → `:585-587` `request->dev = disks[width].dev`
  (wild) and **`rdp->disks[width].last_lba = blkno + chunk` — OOB write of a
  user-chosen 64-bit value** at `disks + width*48 + 32`.
- RAID1/01 rebuild composites `:504-505`, `:562-563` —
  `composite->request[drv]` / `composite->request[this]` with
  `this = drv ± width` up to 2*width-1: `request[]` is 32 entries
  (MAX_COMPOSITES), width ≥ 16 writes a kernel pointer OOB past the
  objcache'd `ata_composite`.
- `ata_raid_config_changed:941-949` — RAID1/01 READY gating reads
  `disks[disk + width]` OOB at attach time.
- `ata_raid_done:681-684/:743` — `mirror = this ± width` OOB deref.

## Reproduce (this pack used the RAID0 path)
`f2_width.img` = FreeBSD/Promise-native metadata (vendor-independent
fallback parser — any controller): RAID0, `interleave=1`, `width=64`,
`total_disks=1`, `disks[0]` ONLINE, checksum valid. Boot with the disk
attached:

```
ar0: 48MB <FreeBSD PseudoRAID RAID0 (stripe 0 KB)> status: READY
ar0: disk0 READY using ad0 at ata0-master
ar0: MBR magic not found; assume a COMPATIBILITY_SLICE (s0)

Fatal trap 12: page fault while in kernel mode
fault virtual address	= 0x80
instruction pointer	= 0x8:0xffffffff8068cc00
current process	= Idle
Stopped at      device_get_softc:       movq    0x80(%rdi),%rax
```

The kernel's own MBR slice-scan reads /dev/ar0; for `blkno % 64 >= 16` the
strategy computes `drv >= 16`, reads `disks[drv].dev` OOB (zeroed heap ⇒
NULL) and dereferences it: fault VA 0x80 = `offsetof(struct device, softc)`
with dev == NULL. **No user process needed — presenting the disk is enough.**

## Expected
Baseline: panic shown above (captured in `panic.txt`).
Patched (fix.diff "illegal array geometry" guard in ata_raid_strategy):
array still assembles but every bio fails EIO; boot completes, no fault
(`run.patched.log`).

## Files
`mkmeta.py` (shared builder), `run.sh`, `panic.txt`, `run.patched.log`,
`fix.diff`, `verdict.json`.
