# DF-5284 — sasetparams transmits uninitialized kernel heap in MODE SELECT

`sys/bus/cam/scsi/scsi_sa.c` — CAM SCSI sequential-access (tape) driver.

## Root cause

`sasetparams()` allocates the compression-page scratch `ccomp` with
`kmalloc(sizeof(sa_comp_t), M_SCSISA, M_INTWAIT)` — **no M_ZERO**
(scsi_sa.c:2757) — then fills it via `sagetparams(..., ccomp)`.

But `sagetparams()` silently drops `SA_PARAM_COMPRESSION` from its local
`params_to_get` when `SA_QUIRK_NOCOMP` is set (scsi_sa.c:2569-2572) and
never writes `*tcs`. The quirk is set *inside* that very call when the
compression-page EINVAL fallback exhausts (scsi_sa.c:2601-2607) — i.e.
after the caller (the `MTCOMP` ioctl, which had checked the quirk before
it was set) already committed to setting compression.

`sasetparams()` then:

* `bcopy(ccomp, cpage, sizeof(sa_comp_t))` — copies 16 bytes of
  uninitialized kernel heap into the MODE SELECT buffer (scsi_sa.c:2796,
  error path again at 3009),
* branches on the uninitialized `ccomp->hdr.pagecode` byte
  (scsi_sa.c:2863) — a kernel-control-flow decision driven by stale heap,
* transmits the buffer to the device via `scsi_mode_select()`
  (scsi_sa.c:2954) whenever the stale pagecode byte matches
  `SA_DEVICE_CONFIGURATION_PAGE` (0x10) or `SA_DATA_COMPRESSION_PAGE`
  (0x0f) — on production (non-INVARIANTS) kernels that byte is heap
  groomable.

## Build (guest, root)

```sh
cd /root/df5284
touch opt_cam.h && make
cc -O -o poc_comp poc_comp.c
```

## Run

```sh
sh run.sh
```

1. `kldload evilsa.ko` — fake T_SEQUENTIAL target attaches as `sa0`.
2. `poc_comp /dev/nsa0` (pinned with `usched :1`):
   * opens the tape (mount with a valid compression page),
   * flips `debug.evilsa.nocomp=1`: MODE SENSE pages 0x0f/0x10 now fail
     with `CAM_REQ_INVALID` (→ EINVAL → the fallback that sets NOCOMP),
   * `debug.evilsa.spray16=1`: allocates 4096 x 16B chunks, stamps the
     kernel pointer `&ev_lock` at [8..15], and frees the chunks whose
     addresses end in 0x10/0x90 **last**, so the chunk returned by the
     next `kmalloc(16)` (= `ccomp`) has freelist-linkage byte0 = 0x10 —
     steering the uninitialized switch into the branch that transmits,
   * issues `MTCOMP` (algorithm 1).

## Expected result

 evilsa captures the 28-byte MODE SELECT and hexdumps payload[12..27]:
 the 16 uninitialized bytes. On a kernel without INVARIANTS these are
 the freelist linkage pointer (bytes 13..19) and the sprayed marker
 `&ev_lock` (bytes 20..27, byte 24 overwritten by the algorithm value):

```
[EVILSA] MODE SELECT #1 len 28 payload[12..27]:
[EVILSA]   10 00 f9 8a 00 f8 ff ff 00 00 00 01 c0 d5 60 82
[EVILSA]   MARKER HIT: payload[20..27] matches &ev_lock=0xffffffff8260d5c0 (byte 24 = calg)
```

(Example from the NOINVARIANTS baseline kernel; see `run.log`.)

## Guest-kernel caveat

The stock audit kernel is built `options INVARIANTS`, which poisons
bytes [0..7] of every non-M_ZERO kmalloc with `(void *)-1`. That makes
the uninitialized pagecode deterministically 0x7F, so on that build the
switch always falls into `default` → `ENODEV` and the page is never
transmitted (`run.inv-kernel.log`: `(sa0): device does not seem to
support compression` — the kernel *branching on the uninitialized byte*,
plus probe16 hexdumps showing the marker sitting in `ccomp`). The
transmit leg was verified on a same-source kernel without INVARIANTS
(`run.log`), which is what production ships.

## Fix

`fix.diff`: `M_ZERO` on the `ccomp` allocation (plus the DF-5285/DF-5286
guards). With the fix, the payload bytes are zero and the marker never
appears.
