# DF-4059 — VERDICT

**REPRODUCED** (panic + controlled OOB write + kernel stack disclosure; fix
validated on a rebuilt kernel).

Production-handler KLD dispatch rig (`dfamr_rig.c`, the DF-4240 dpt class):
the guest's X86_64_GENERIC kernel contains the amr driver
(`nm /boot/kernel/kernel`: `amr_done` T, `amr_start`/`amr_std_get_work`/
`amr_std_poll_command`/`amr_std_submit_command`/`amr_complete` t) but no
MegaRAID HBA exists, so the rig fabricates the post-`amr_pci_attach` softc
(real bus_dma tags, real DMA-mapped mailbox64/s-g table/ccb array, real
`lockinit` of both locks, production `amr_init_qhead`/queue setup, production
std-firmware handler trio at amr_attach:212-214) and stands in only for the
card: a memory-mapped register file (`X86_64_BUS_SPACE_MEM` — the
AMR_SGET_ISTAT/AMR_SPOST_COMMAND accesses are plain volatile memory ops),
completions written into the real DMA mailbox, and the card-side
"deassert the interrupt line once the driver took the mailbox" semantic
(which a memory file cannot express) modeled by `rig_get_work()` wrapping the
**production `amr_std_get_work`**. Command clusters are built by a verbatim
copy of `amr_alloccmd_cluster` (amr.c:1922-1979 — gcc 8.3 inlined that symbol
into `amr_attach`, so it cannot be dispatched by address; everything else on
the vulnerable path is dispatched from the shipped kernel image, addresses
`nm`-resolved and prologue-byte-verified against `objdump` — all four OK).

Everything at issue runs as shipped-kernel production code: `amr_done` with
its inlined `amr_freeslot`, driven by production `amr_std_get_work` over
production `amr_alloccmd`/`amr_start`/`amr_releasecmd` structures.

## Measured alias (empirical, not assumed)

`offs` ioctl on the fabricated-but-production-shaped softc:
`offsetof(softc, amr_ready)=1536, amr_busycmd=1552, amr_busyslots=3592`,
`sizeof(softc)=3920`; `offsetof(command, ac_sc)=8, ac_slot=16, ac_status=20`,
`sizeof(command)=232`; `sizeof(amr_mailbox)=82` (`mb_completed[46]` at +18).
`amr_busycmd[-1]` **empirically equals `&sc->amr_ready.stqh_first`**
(`busykm1_value == ready_stqh_last, match=1`) — a non-NULL kernel pointer on
every production attach (amr.c:197 `amr_init_qhead(&sc->amr_ready)`).

## Reproduction 1 — completion id 0, idle controller: instant panic (bootB)

One mailbox: `{mb_nstatus=1, mb_completed[0]=0}` raised as the card would:

```
[amrrig] DF-4059A: idle controller, completion id 0 -> amr_freeslot derefs busycmd[0]=NULL as softc
panic: amr: slot 0 not busy?
amr_done() at amr_done+0x134
```
(bootB_serial.log). Mechanism, line-verified: `idx = mb_completed[i] - 1 = -1`
(amr.c:1788) → `ac = sc->amr_busycmd[-1]` (amr.c:1789) = `&amr_ready.stqh_first`
≠ NULL → `amr_freeslot(ac)` (amr.c:1795) reads `ac->ac_sc` (+8) =
`amr_ready.stqh_last` = softc-interior pointer `&amr_ready`, and `ac->ac_slot`
(+16) = low byte of `amr_busycmd[0]` = 0 → `sc2->amr_busycmd[0]` reads
`*(softc+3088)` = `amr_busycmd[192]` = NULL (production maxio ≤ 120, slot 192
can never be busy) → production `panic("amr: slot %d not busy?")`
(amr.c:1510). **One hostile completion byte = guaranteed kernel panic on the
interrupt path.**

## Reproduction 2 — completion id 0, slot 0 busy: controlled corruption (bootA)

Real production cluster + production `amr_start` on two commands
(`busycmd[0]`=ac0? — the freelist is LIFO so slots 2 and 1 went busy; slot 0
idle → forged `ac->ac_slot` byte = 0). Rig pre-poisons only the qword
production will test/clear so the "not busy?" guard does not fire first
(`0x0000133700001337` at softc+3088; `0x4000` at softc+5128). After one
production `amr_done`:

```
[4059B] NULL store @softc+3088: 0x0000133700001337 -> 0x0000000000000000
[4059B] busyslots decr @softc+5128: 0x4000 -> 0x3fff
[4059B] ac_status store corrupted busycmd[0] byte4: 0x5a (mb_status 0x5a)
[4059B] real sc busyslots after: 2      (fake sc2 never touched the real counter)
```

What production executed: `amr_freeslot` treated `&amr_ready` as a softc and
(1) wrote NULL through it at `&amr_ready + off_busycmd + 8*slot`
(`&amr_ready + 1552 + 8*slot` = softc+3088+8*slot — for heap-groomed slot
bytes ≥ 63 this leaves the `amr_busycmd[]` array entirely and clears
arbitrary softc fields: `amr_freecmds`, `amr_cmd_clusters`, cam pointers);
(2) `atomic_subtract_int(&sc2->amr_busyslots, 1)` at `&amr_ready+3592` =
softc+5128 — **1208 bytes past the end of the 3920-byte softc** (on a real
host: whatever follows the device softc in the heap); (3) back in `amr_done`,
`ac->ac_status = mb_status` (amr.c:1798) stored the controller-chosen status
byte at `&amr_ready+20` = softc+1556 — **corrupting the `amr_busycmd[0]`
command pointer inside the live softc**. The `slot` steering byte is the low
byte of the slot-0 command's cluster address (16 KB `kmalloc` — heap-groomable
on a real host), exactly the "wild kernel pointer deref" the finding claims,
with a write primitive attached.

## Reproduction 3 — std-firmware stack overread + log disclosure (bootA)

`mb_nstatus = 255` (8-bit, controller-chosen) with `mb_completed[0..45]` set
to `0x80..0xab`. Production `amr_done` loop iterates 255 times reading
`mb_completed[i]` for i = 46..254 — **209 bytes past the 82-byte stack
mailbox** — and each byte becomes `idx = byte - 1` printed by
`device_printf(... "bad slot %d completed")` (amr.c:1802) unless NULL. 255
disclosure lines per run captured (leak2/leak7 marker regions in
`dmesg_bootA.txt`, bootA_serial.log). Decoded bytes beyond the mailbox
include:

* a canonical kernel heap pointer `0xfffff8001813f878` (byte run
  `78 f8 13 18 00 f8 ff ff`),
* runs of `0x5a` and `0xa5` — the rig caller frames' pattern buffers
  (`rig_nest0`/`rig_nest` pads) immediately below `amr_done`'s frame,
* zero bytes (idx −1) and pointer-fragment bytes throughout.

Two runs at different call depths (2 vs 7) disclose **different bytes**
(`... 113 23 ... / 55 242 ...` vs `... 207 114 ... / 103 244 ...`) — the
overread demonstrably reads live kernel stack, and every byte lands in the
console/message buffer. This is a kernel-stack info disclosure of up to 209
bytes per completion mailbox to anyone who can read the log, driven purely by
controller-supplied data.

## Accidental third facet (production-faithful, first rig iteration)

While shaking down the rig (virtual card left the mailbox-busy flag stuck),
the second `amr_start` took the production EBUSY path
(`amr_setup_data`:1597-1600 → `amr_requeue_ready`) which left
`amr_ready.stqh_last = &requeued_cmd.ac_link`. In that state
`amr_busycmd[-1]` resolves to **a real `amr_command`** (the requeued one), so
a completion id 0 makes `amr_freeslot` free that live command's slot a second
time → `panic("amr: slot 1 not busy?")` (observed on serial; corrected
register model since). I.e. under ordinary driver states the id-0 alias is
either a softc-interior pointer (repro 1/2) or a real queued command
(double-free/double-complete) — both non-NULL, both weaponized by the same
missing validation.

## Why this is reachable (threat model)

All inputs are controller-supplied: a malicious/faulty MegaRAID (firmware
bug, transient, or passed-through/virtualized device) or DMA of the shared
mailbox. The path is the ordinary interrupt/poll completion
(`amr_pci_intr`:469, `amr_std_poll_command`:1353, `amr_rescan_drives`:462 —
root-only ioctls on /dev/amr0 reach the poll loops). Not unpriv→root: the
device node is 0600 root:operator and no HBA exists on the audit guest; the
ceiling is one-byte-to-panic plus heap-directed NULL-store/decrement/
pointer-corruption and stack disclosure on hosts that have the card.

## Fix validation (patched kernel #1, nativekernel gcc 8.3, rc=0)

`fix.diff` = clamp `i < nitems(mbox.mb_completed)` (46) + reject
`mb_completed[i] < 1` in `amr_done` (amr.c:1786-1789). On the rebuilt kernel
(run_patched.sh / fix_run.log):

* 4059B: `bad completion id 0` printed, **no NULL store, no decrement,
  `busycmd[0]` intact** (`write_after == write_before`, `decr_after ==
  decr_before`);
* leak run: `excessive completion count 255` printed, exactly 46 entries
  processed (`bad slot 127..172`), **zero stack-derived bytes** in the log;
* 4059A: `bad completion id 0` + guest **alive** (former panic gone).

Baseline behaviors gone; benign completions unaffected (tmo/late scenarios
still complete real ids through the patched loop).

## Not re-verified here / adjacent

`amr_quartz_get_work`'s write overflow into its own `completed[46]` is
DF-1358 (separate fix); the patched `amr_done` clamp also protects the
quartz-driven downstream loop, but the quartz write itself is out of scope
for this pack.

## Artifacts

* `dfamr_rig.c` / `dfamr_ctl.c` / `Makefile` / `build.sh` — the rig
* `bootA_serial.log` — 4059B + leak runs + 4060 evidence + 4060 finale panic
* `bootB_serial.log` — 4059A idle-controller panic
* `panic_4060.txt`, `panic_4059A.txt` — panic excerpts
* `leak2.dmesg` / `leak7.dmesg` — full disclosure runs (re-captured on the
  patched-build baseline boot; identical mechanism)
* `fix.diff` (git-apply clean), `fix_build.log`, `fix_run.log`

## 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.
