# DF-1028 — VERDICT

## Verdict

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

The OOB heap read in `READ ELEMENT STATUS` parsing is real and the
cited line numbers are exact, but it cannot be exercised on the audit
guest because the only SCSI device on the guest is a QEMU DVD-ROM (CD
-ROM, SCSI device type 5) and the ch peripheral only attaches to SCSI
device type 8 (medium changer). There is no `/dev/ch0` and therefore
no code path to `chgetelemstatus()`. The bug is *latent* on this
guest and *live* on any host that has a (real or emulated) SCSI medium
changer against which a privileged user issues `CHIOGSTATUS`.

## Mechanism (code trace)

`chgetelemstatus()` (`scsi_ch.c:1053-1200`) drives a SCSI `READ
ELEMENT STATUS` command and parses the response. Three device-controlled
quantities drive the parser with no validation:

* `sys/bus/cam/scsi/scsi_ch.c:1118`
  ```c
  desclen = scsi_2btoul(pg_hdr->edl);
  ```
  The per-descriptor length, taken from the device's response page
  header. Attacker controls this.

* `sys/bus/cam/scsi/scsi_ch.c:1159`
  ```c
  avail = scsi_2btoul(st_hdr->count);
  ```
  The number of available elements, taken from the device's response
  status header. Attacker controls this.

* The loop at `scsi_ch.c:1176-1183`
  ```c
  for (i = 0; i < avail; ++i) {
      ...
      copy_element_status(softc, pg_hdr->flags, desc, ces);
      desc = (struct read_element_status_descriptor *)
             ((uintptr_t)desc + desclen);
  }
  ```
  iterates `avail` times and reads ~84 fixed bytes per descriptor
  regardless of `desclen`.

`copy_element_status()` (`scsi_ch.c:983-1051`) unconditionally
dereferences `desc->eaddr`, `flags1`, `reserved0`, `sense_code`,
`sense_qual`, `dt_scsi_flags`, `dt_scsi_addr`, `reserved1`, `flags2`,
`ssea`, `pvoltag` (36 bytes), `avoltag` (36 bytes) — about 84 bytes
total (verified from `struct read_element_status_descriptor` at
`scsi_ch.h:208-252`). Two distinct OOB reads follow:

* **desclen < 84**: each call to `copy_element_status` reads
  `(84 - desclen)` bytes past the per-descriptor slot. `data` is sized
  for `desclen * cesr->cesr_element_count` bytes, so even one short
  descriptor overruns the end of `data`.

* **avail > cesr->cesr_element_count**: the loop walks `desc` past the
  data allocation, copying the OOB-read bytes into `user_data` (which
  is sized for `avail`, so no overflow there). The leaked kernel heap
  bytes are then `copyout` to the (privileged) user via
  `scsi_ch.c:1186-1188`.

The ch driver is part of the `cam.ko` module, which IS loaded on the
audit guest (the QEMU DVD-ROM uses CAM via `natapicam`). But the ch
*peripheral* only attaches when CAM detects a SCSI device type 8.

## Why it does not reproduce on this guest

| Audit-guest fact | Evidence |
|---|---|
| Only SCSI device is a QEMU DVD-ROM (type 5, CD-ROM) | `camcontrol devlist` shows only `QEMU DVD-ROM` at scbus1 target 0 lun 0 |
| No `/dev/ch0` device node exists | `ls /dev/ch*` returns nothing |
| ch peripheral never registers because no medium-changer device is enumerated | ch only matches `T_CHANGER` (type 8) at peripheral registration |

There is no code path on the audit guest that reaches
`chgetelemstatus()`.

## Exploit chain

`none` — pure OOB heap read (CWE-125). The ch driver's `chopen` is
gated by `SYSCAP_RESTRICTEDROOT` so this is a privileged-user-only
read; on a host with a malicious SCSI changer the leak ceiling is
"kernel heap bytes the size of the (avail-count) gap past `data`,
re-routed to userspace". Not exploitable as corruption.

## Fix

`fix.diff`:
1. Rejects `desclen < sizeof(struct read_element_status_descriptor)`
   (84 bytes) with `EIO` and a diagnostic print, preventing the
   per-slot OOB read.
2. Clamps `avail` to `cesr->cesr_element_count`, preventing the loop
   from walking past `data`.

Both checks land before the `user_data` kmalloc and the descriptor
loop, so they close both OOB paths.

## Fix validation

* `fix.diff` applies cleanly to `/usr/src/sys/bus/cam/scsi/scsi_ch.c`
  with `patch -p1` (1 hunk succeeded).
* 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`
