# DF-1029 — VERDICT

## Verdict

**NOT REPRODUCED (live) — bug CONFIRMED via code trace, fix.diff compiles cleanly.**

The OOB heap read via unchecked `blk_desc_len` is real and the cited
line numbers are exact, but it cannot be exercised on the audit guest
for the same reason as DF-1028: there is no SCSI medium-changer
device, so no `ch` peripheral attaches and the vulnerable code paths
(`chstart` → `chdone`, and `chgetparams`) never run. The bug is
*latent* on this guest and *live* on any host that attaches a SCSI
changer; unlike DF-1028, the trigger is *automatic at probe time* —
no userland cooperation is required.

## Mechanism (code trace)

`find_mode_page_6()` at `sys/bus/cam/scsi/scsi_all.h:1418-1427`:

```c
static __inline void *
find_mode_page_6(struct scsi_mode_header_6 *mode_header)
{
    void *page_start;
    page_start = (void *)((u_int8_t *)&mode_header[1] +
                          mode_header->blk_desc_len);
    return(page_start);
}
```

`blk_desc_len` is a `u_int8_t` at offset 3 of
`struct scsi_mode_header_6` (`scsi_all.h:956-962`) — i.e. a byte the
device fully controls in its MODE SENSE response. The function adds
that byte to `&mode_header[1]` (= `mode_header + 4`) with NO bounds
check, so a `blk_desc_len == 0xFF` (255) makes `page_start` point 259
bytes into a buffer that may be far smaller.

`chstart` at `sys/bus/cam/scsi/scsi_ch.c:485-519` allocates the probe
mode buffer with:

```c
mode_buffer_len = sizeof(struct scsi_mode_header_6) +   // 4
                  sizeof(struct scsi_mode_blk_desc)  +   // 8
                  sizeof(struct page_element_address_assignment);  // 20
// total = 32 bytes
```

If the device lies and returns `blk_desc_len == 0xFF`,
`find_mode_page_6(mode_header)` returns `mode_buffer + 4 + 255 =
mode_buffer + 259` — 227 bytes past the 32-byte allocation.

Three call sites then dereference that OOB pointer:

* `sys/bus/cam/scsi/scsi_ch.c:546-547` (probe path, `chdone`): reads
  `ea->mtea/nmte/fsea/nse/fieea/niee/fdtea/ndte` (16 bytes) into
  `softc->sc_firsts[]` / `sc_counts[]`.
* `sys/bus/cam/scsi/scsi_ch.c:1409-1419` (`chgetparams`, element
  page): same 16-byte read of `ea->` into `softc->sc_firsts[]` /
  `sc_counts[]`.
* `sys/bus/cam/scsi/scsi_ch.c:1474-1484` (`chgetparams`, cap page):
  reads `cap->move_from[]` / `exchange_with[]` (16 bytes) into
  `softc->sc_movemask[]` / `sc_exchangemask[]`.

Some of the resulting softc fields are then exposed to userspace via
`CHIOGPARAMS` (slot counts). This is an *automatic* OOB read at
device-probe time — no userland action needed beyond attaching the
device.

## Why it does not reproduce on this guest

Same as DF-1028: no SCSI device type 8 (medium changer) is present,
so no `ch` peripheral attaches and `chstart`/`chdone`/`chgetparams`
never run. The cam.ko module is loaded (QEMU DVD-ROM uses CAM), but
the ch driver only matches `T_CHANGER`.

## Exploit chain

`none` — pure OOB heap read (CWE-125) at probe time. Read size is
up to ~16 bytes per call site, fed into softc state that is partially
readable by `CHIOGPARAMS` (root-only). On a malicious-changer
scenario, the read ceiling is bounded by the slab bucket / page
adjacency of the 32-byte probe mode_buffer.

## Fix

`fix.diff` adds a small `static __inline int ch_mode_header_sane()`
helper that requires `mode_header->blk_desc_len <= buflen -
sizeof(*mode_header)`, and calls it at all three call sites before
`find_mode_page_6()`. On a bogus value the driver logs a diagnostic
and bails (probe announcement becomes empty; `chgetparams` returns
`EIO`). This is a minimal, root-cause fix that matches the finding's
"validate blk_desc_len against buffer size before find_mode_page_6"
recommendation.

## Fix validation

* `fix.diff` applies cleanly to `/usr/src/sys/bus/cam/scsi/scsi_ch.c`
  with `patch -p1` (4 hunks all succeeded; 2 with a 23-line offset
  because the helper was inserted above the first call site — patch
  auto-applied the offset).
* The patched `cam.ko` module (which contains `scsi_ch.o`) compiles
  cleanly with `-Werror` (see `fix_build.log`).
* Not live-tested (no SCSI changer device to attach).

`fix_status: not_testable` (no live trigger available).

## PoC changes

The finding folder was empty; this run authored:
- `poc.c` — documentation harness
- `fix.diff` — the verified fix
- `build.sh`, `run.sh`, `build.log`, `run.log`, `env.txt`,
  `fix_build.log`, `manifest.json`, `VERDICT.md`
