# DF-5284 VERDICT — reproduced (leak, kernel heap to device via MODE SELECT)

## What was claimed

`sasetparams()` in `sys/bus/cam/scsi/scsi_sa.c` transmits 16 bytes of
uninitialized kernel heap to the SCSI device inside a MODE SELECT
command when the compression-page EINVAL fallback fires.

## Reproduced? Yes — deterministically, first MTCOMP attempt, on a
same-source kernel built without INVARIANTS (what production ships).

### Mechanism (all lines verified in-tree and on-guest)

1. `MTCOMP` ioctl passes the quirk gate (`scsi_sa.c:1261` — neither
   `SA_QUIRK_NOCOMP` nor `SA_FLAG_COMP_UNSUPP` set yet).
2. `sasetparams(SA_PARAM_COMPRESSION)` kmallocs `ccomp` **without
   M_ZERO** (`scsi_sa.c:2757`) and immediately calls `sagetparams()`
   to fill it.
3. evilsa rejects MODE SENSE pages 0x0f and 0x10 with CAM_REQ_INVALID;
   `saerror()`→`cam_periph_error()` maps that to EINVAL, so
   `sagetparams()` walks its fallback (`scsi_sa.c:2593-2607`): page
   0x0f → retry page 0x10 → retry again **after setting
   `SA_QUIRK_NOCOMP`** — and on that third pass
   (`scsi_sa.c:2569-2572`) it silently drops SA_PARAM_COMPRESSION and
   returns success **without ever writing `*tcs`**. `ccomp` still holds
   whatever was in the recycled heap chunk.
4. `bcopy(ccomp, cpage, 16)` copies the stale bytes into the MODE
   SELECT page (`scsi_sa.c:2796`), the driver branches on the stale
   `ccomp->hdr.pagecode` byte (`scsi_sa.c:2863`), and with pagecode
   0x10 (heap-groomable — see below) issues the 28-byte MODE SELECT
   (`scsi_sa.c:2954`).

### The groom (why it is deterministic, not luck)

On a non-INVARIANTS kernel a freed 16B slab chunk carries the freelist
linkage in bytes[0..7] (`kern_slaballoc.c:1584` `chunk->c_Next =
z->z_LChunks`). evilsa's `spray16` frees only chunks whose own
addresses end in 0x10/0x90 *last*, so every chunk near the free-list
head links to another 0x10/0x90-address chunk — the chunk handed to
`ccomp = kmalloc(16)` deterministically has `pagecode == 0x10` → the
`SA_DEVICE_CONFIGURATION_PAGE` branch → MODE SELECT with the page.
Verified 3/3 boots, attempt 0 each time (`run.log`, `run.2.log`,
`run.3.log`, decode in `dmesg.leak.txt`).

### Leaked bytes (per call, repeatable per fresh mount)

* bytes[13..19]: live kernel heap pointer (slab freelist linkage,
  0xfffff8004f0bXXXX-class) — heap layout disclosure;
* bytes[20..27]: the remainder of the recycled chunk — in the PoC the
  sprayed marker `&ev_lock` (kernel .data address), proving arbitrary
  prior heap content crosses the trust boundary.

## Why the stock audit guest masked the transmit leg

The audit guest's kernel is `options INVARIANTS`; `slaballoc()` then
poisons bytes[0..7] of every non-M_ZERO kmalloc with `(void *)-1`
(`kern_slaballoc.c:1153`), forcing pagecode 0x7f → the switch takes
`default` → ENODEV before transmitting. On that build the
uninitialized-memory use is still directly observable: the console
shows `(sa0:evilsa0:0:0:0): device does not seem to support
compression` — the kernel *branching on the uninitialized byte*
(`run.inv-kernel.128loop.log`), and `probe16` hexdumps show the marker
sitting in the exact chunk `ccomp` received. The transmit was then
verified on the same-source `X86_64_NOINV` kernel.

## Fix validation

`fix.diff` (shared with DF-5285/DF-5286) adds `M_ZERO` to the `ccomp`
kmalloc. Same-source NOINV kernel rebuilt (`fix_build.log`),
installed, rebooted; identical PoC re-run: 128 MTCOMP attempts,
`debug.evilsa.msel28` stays **0**, no 28-byte MODE SELECT captured, no
marker bytes anywhere (`run.patched.log`). Baseline on the identical
unpatched kernel transmitted on attempt 0.

## Rig notes (honesty)

* One rig bug during bring-up: `int aidx[4096]` on the sysctl handler's
  kernel stack → stack overflow → double fault; moved to .bss.
* The "MARKER HIT" kprintf verdict line in evilsa mis-flags despite the
  bytes matching (cosmetic rig bug, not investigated further); the
  hexdump lines are the evidence and are decoded in `dmesg.leak.txt`.
* The ENODEV attempts consume `ccomp` chunks without freeing — that is
  the already-known DF-2265 leak, not re-reported here.

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