# DF-5271 VERDICT — mode-page blk_desc_len OOB read/write: REPRODUCED

## Bottom line
**Reproduced.** A fake CD-ROM CAM SIM (`evilcd.ko`) driving the stock
guest kernel's `cd(4)` driver demonstrates, in one run:

1. **device-forced 10-byte mode switch** — the device fails MODE_SENSE(6)
   with CAM_REQ_INVALID; `cd6byteworkaround()` (scsi_cd.c:3127-3131)
   permanently sets `softc->minimum_command_size = 10`
   (dmesg: `MODE_SENSE(6) failed, increasing minimum CDB size to 10 bytes`);
2. **evil MODE SENSE(10) header** — `data_length = 0` (passes the only
   sanity check, scsi_cd.c:3437-3466) and `blk_desc_len = <computed>`
   (16-bit, device-chosen);
3. **OOB READ to userland** — CDIOCGETVOL returns bytes read at
   `mode_buf + 8 + blk_desc_len + {9,11,13,15}`; the first run returned
   `00 00 00 ab`, i.e. bytes of a neighboring heap chunk and of the probe
   victim's stamp (0xAB from 0xABCD0003);
4. **OOB WRITE from userland data** — CDIOCSETVOL/CDIOCSETPATCH write
   user-supplied volume/patch bytes through the same pointer;
5. **byte-exact victim attribution** — the SIM allocates a stamped 32-byte
   probe victim from inside the MODE SENSE(10) handler (same zone as the
   32-byte `mode_buf`, delta 0x40 in the decisive run) and aims
   `blk_desc_len` at it; `check_victims` finds it corrupted with exactly
   the four user bytes (`a3 68 a4 69` = patch[2], vol[2], patch[3], vol[3]);
   a second foreign chunk received `66 67` (vol[0], vol[1]) and the final
   GETVOL read those back (`vol = 66 67 68 69`), proving the write
   persisted in memory the driver does not own.

## Primitive characterization
- **write**: 4 fully user-chosen bytes per ioctl (CDIOCSETVOL `vol[0..3]`
  at page+9/11/13/15, CDIOCSETPATCH `patch[0..3]` at page+8/10/12/14),
  plus fixed bytes CHANNEL_0/1 (0x01/0x02); target offset
  `mode_buf + 8 + blk_desc_len + {8..15}` with `blk_desc_len` ∈ [0,0xFFFF]
  chosen by the device per command, i.e. any offset up to ~64 KB past a
  32-byte heap chunk; unlimited repetitions, no crash in the verified
  range (silent corruption of neighboring objects).
- **read**: CDIOCGETVOL returns 4 OOB bytes per ioctl at the same
  device-chosen offset (heap info leak / KASLR-relevant data disclosure).
- The 6-byte variant (`blk_desc_len` 1 byte) reaches +259 from the same
  32-byte chunk and is also unguarded; `cd6byteworkaround()`'s
  MODE_SELECT conversion reads `find_mode_page_6()` OOB (scsi_cd.c:3173-3183)
  feeding `page_size`.

uid=0 escalation was **not** pursued to completion: the entry point is
gated by `caps_priv_check_self(SYSCAP_RESTRICTEDROOT)` (scsi_cd.c:1006), so
the realistic threat is malicious-device-driven kernel corruption (the same
model as the known TOC findings DF-0987/DF-2268), not unpriv→root.  The
primitive (repeatable 4-controlled-byte write at a 64 KB device-chosen
window, plus an OOB read for aiming) is however straightforwardly
weaponizable against adjacent heap objects.

## Why the kernel is wrong (path:line)
- scsi_cd.c:1970 (and every audio ioctl): `alloc_len =
  sizeof(union cd_mode_data_6_10)` = 32 bytes.
- scsi_cd.c:3400-3411 `scsi_mode_sense_len()` transfers at most 32 bytes —
  the *transfer* is bounded, the *header fields are not validated*.
- scsi_cd.c:3437-3466: the only post-transfer check compares `data_length`
  against `alloc_len`; `blk_desc_len` is never checked.
- scsi_all.h:1418-1438 `find_mode_page_6/10()` return
  `buf + hdrsize + blk_desc_len` unconditionally.
- scsi_cd.c:1880-1893 `cdgetpage()` hands that pointer to the ioctls.
- Write sinks: scsi_cd.c:1986-1987 (PLAYTRACKS flags RMW), 2081-2082,
  2122-2123, **2443-2448 (SETPATCH, 4 user bytes)**, 2508-2515
  (**SETVOL, 4 user bytes**), 2542-2547, 2574-2579, 2606-2609, 2636-2639,
  2666-2669; read sink: scsi_cd.c:2476-2481 (GETVOL → userland).
- Device-controlled path switch: scsi_cd.c:3127-3128 / 3391-3395.

## Fix validation
`fix.diff` makes `cdgetmode()` reject mode headers whose
`hdr_size + blk_desc_len + sizeof(union cd_pages)` exceeds `alloc_len`
(the existing ENOSPC path).  Built as a single-fix kernel
(`make nativekernel KERNCONF=X86_64_GENERIC`, log: `fix_build.log`),
installed, rebooted, exact same PoC re-run:
- CDIOCGETVOL now fails with `ENOSPC` (driver prints
  `allocated mode page ... < returned length ...`),
- **no** probe-victim corruption (`check_victims: probe victim untouched`,
  0 corrupted chunks),
- no readback of previously written bytes.
See `run.patched.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.
