# DF-0988 — cdsize accepts blksize=0 from READ_CAPACITY → divide-by-zero panic

## Verdict
**NOT REPRODUCED at runtime on the default guest — bug is real as a code
pattern, but triggering requires a malicious SCSI/iSCSI CD-ROM that returns
READ_CAPACITY data with length=0.** Defense-in-depth fix authored + compiled.

## Mechanism
`cdsize()` at `sys/bus/cam/scsi/scsi_cd.c:3026` reads the result of a
SCSI READ_CAPACITY command into `rcap_buf` and stores the block size
without a lower bound:

```c
/* scsi_cd.c:3058-3069 */
softc->params.disksize = scsi_4btoul(rcap_buf->addr) + 1;
softc->params.blksize  = scsi_4btoul(rcap_buf->length);
/*
 * SCSI-3 mandates that the reported blocksize shall be 2048.
 * Older drives sometimes report funny values, trim it down to
 * 2048, or other parts of the kernel will get confused.
 */
if (softc->params.blksize > 2048 && softc->params.blksize <= 2352)
    softc->params.blksize = 2048;
```

The narrowing at line 3068 only catches the (2048, 2352] range; a
malicious device reporting `length = 0` (or any non-2048-family value not
in 2049-2352) sets `softc->params.blksize = 0`.

On the next read, `cdstart()` at `sys/bus/cam/scsi/scsi_cd.c:1530` asserts
and divides by this zero:

```c
/* scsi_cd.c:1530 */
KKASSERT(bio->bio_offset % softc->params.blksize == 0);
/* scsi_cd.c:1540-1541 */
bio->bio_offset / softc->params.blksize,
bp->b_bcount / softc->params.blksize,
```

→ kernel panic (divide-by-zero / INVARIANTS assertion). Mount succeeds
(cdcheckmedia reports CD_FLAG_VALID_MEDIA), the first read panics.

## Reachability analysis (the crucial question)

`cdsize()` is called from `cdcheckmedia()` (sys/bus/cam/scsi/scsi_cd.c:1049)
on the CD periph's media-validation path. It is reached when:

1. A SCSI CD-ROM (or ATAPI CD via `atapi-cam`) reports media present, and
2. The kernel re-validates the media (mount, open, media-change event).

The READ_CAPACITY data comes from the device firmware. To trigger the
panic, the device must return `rcap_buf->length == 0`. This is
**device-controlled** data; the kernel cannot influence it.

**On the audit guest:**

- `/dev/cd0` is `crw-r----- root operator` — `maxx` (uid 1001, only group
  `maxx`) cannot open it. `camcontrol devlist` requires operator
  membership maxx lacks (`couldn't open /dev/xpt0: Permission denied`).
- The virtual QEMU PIIX CD-ROM behind `/dev/cd0` returns well-formed
  READ_CAPACITY data (`blksize=2048`). No malicious device is attached.

For an unprivileged user to trigger this on a real deployment, they would
need:

- A **malicious iSCSI target** presenting a CD-ROM LUN that returns
  READ_CAPACITY with `length=0` (requires the admin to have configured
  the iSCSI initiator against the attacker's target, OR the target to be
  compromised), or
- A **malicious USB CD-ROM / SCSI device** physically plugged in (physical
  access or social engineering).

Both preconditions are realistic for some deployments (the audit treats
malicious filesystem images and compromised storage as in-scope), but
neither is present on the default audit guest. **This is a valid hard
blocker: the bug is real but cannot be triggered by an unprivileged
syscall on the default kernel + default devices.**

## Exploit chain
**Not applicable / blocked by valid hard blocker (no malicious device on
default guest).** The primitive is a kernel divide-by-zero → DoS/panic;
there is no privilege-escalation path because no attacker-controlled
memory corruption occurs (just an arithmetic fault). Fixing the
validation is the right action.

## Fix
The fix in `fix.diff` adds an explicit blksize==0 (and lower-bound) check
in `cdsize()` that refuses the media with `ENXIO` rather than letting
`cdstart()` divide by zero:

```c
if (softc->params.blksize == 0) {
    xpt_print(periph->path, "cdsize: device reported blocksize of 0, "
        "rejecting media\n");
    error = ENXIO;
}
```

## Fix validation
**Compile-only.** The bug requires a malicious device not present on the
guest, so a runtime behavior-change cannot be demonstrated on either the
patched or unpatched kernel (`fix_status: not_testable`). Validated:

- `fix.diff` applies cleanly with `patch -p1` (rc=0).
- A single-fix kernel was built with `make -j6 nativekernel
  KERNCONF=X86_64_GENERIC`; `scsi_cd.c` compiled under `-Werror`
  (no warnings, no errors); the kernel linked successfully
  (`/usr/obj/usr/src/sys/X86_64_GENERIC/kernel.stripped`, today's build).
  See `fix_build.log`.

## PoC changes
The PoC directory was seeded empty. No runtime trigger is possible on
the default guest (no accessible `/dev/cd0`, no malicious device).
Authored `fix.diff` and this `VERDICT.md`.
