# DF-1017 — READ_CAPACITY block_len=0 divide-by-zero — VERDICT

## Verdict: INCONCLUSIVE (runtime) / CONFIRMED (source-level)

The bug is **confirmed real** by line-by-line source tracing. Runtime
reproduction is blocked by missing hardware: this QEMU guest has no SCSI
Direct Access (`da`) device, only a DVD-ROM (`cd0`). The `da` driver code
path is compiled into the GENERIC kernel but not exercised at runtime.

## Mechanism (source-level trace)

1. **Attacker input:** A malicious SCSI device (USB/iSCSI/FC) responds to
   READ_CAPACITY with `length = 0` in the 8-byte response struct.

2. **Unvalidated propagation:**
   - `dadone()` at `sys/bus/cam/scsi/scsi_da.c:1831`:
     `block_size = scsi_4btoul(rdcap->length);` — device-controlled, no validation.
   - `dagetcapacity()` at `scsi_da.c:2228`:
     `block_len = scsi_4btoul(rcap->length);` — same pattern, no validation.
   - `dadone()` at `scsi_da.c:1853`:
     `dasetgeom(periph, block_size, maxsector);` — passes 0 directly.

3. **Sink — secsize set to 0:**
   `dasetgeom()` at `scsi_da.c:2289`:
   ```c
   dp->secsize = block_len;   /* block_len=0, NO zero check */
   ```

4. **Divide-by-zero sites (7 locations):**
   - `dadump()` `scsi_da.c:797`: `ap->a_offset / secsize`
   - `dadump()` `scsi_da.c:798`: `ap->a_length / secsize`
   - `dastart()` TRIM `scsi_da.c:1341`: `count = bp->b_bcount / secsize`
   - `dastart()` TRIM `scsi_da.c:1342`: `lba = bio1->bio_offset / secsize`
   - `dastart()` RW `scsi_da.c:1490`: `KKASSERT(bio->bio_offset % secsize == 0)`
   - `dastart()` RW `scsi_da.c:1500`: `bio->bio_offset / secsize`
   - `dastart()` RW `scsi_da.c:1501`: `bp->b_bcount / secsize`

5. **Automatic trigger:** After `dasetgeom`, `dadone` sets
   `info.d_media_blksize = softc->params.secsize` (line 1863) and calls
   `disk_setdiskinfo()`. This triggers automatic partition probing
   (`disk_probe_slice` → `dev_dstrategy` → `dastart`), which divides by
   `secsize=0` → **#DE trap → kernel panic**. No user interaction needed.

6. **Error path gap:** At `scsi_da.c:1948`, the error path sets
   `info.d_media_blksize = 512` but does NOT set `softc->params.secsize`,
   so it stays 0 — subsequent I/O still divides by zero.

## Exploit chain

**Not applicable — this is a divide-by-zero (DoS), not memory corruption.**
A malicious SCSI device causes an immediate kernel panic. No privilege
escalation primitive. Impact ceiling: **kernel DoS / panic** from a
malicious physical or virtual device.

## Why runtime reproduction is blocked

- This QEMU guest has **no SCSI Direct Access disk**. `camcontrol devlist`
  shows only `<QEMU QEMU DVD-ROM>` (cd device, handled by `scsi_cd.c`, not
  `scsi_da.c`).
- Adding a SCSI disk would require modifying the QEMU command line, which
  is outside the scope of this audit guest.
- The bug is in code compiled into GENERIC but not reachable at runtime
  without a `da`-class device.

## PoC changes

Created `harness.c` — a source-level arithmetic demonstration showing
that `secsize=0` triggers division-by-zero at all 7 sink sites. Built and
verified on the guest (compiles cleanly, produces expected output).

## Fix validation

The fix (`fix.diff`) adds validation in `dasetgeom()`: if `block_len < 512`
or `block_len > MAXPHYS`, fall back to 512. Applied + compiled successfully
in a combined-fix kernel build (`#1`, rc=0, boots cleanly). Runtime
before/after testing not possible (no da device to trigger the bug).

**fix_status: not_testable** (diff applies + compiles; runtime test blocked
by missing HW — the `da` code path cannot be exercised on this guest).

## Recommended fix

Add `block_len` validation at the top of `dasetgeom()` (scsi_da.c:2286):
```c
if (block_len < 512 || block_len > MAXPHYS)
    block_len = 512;
```
This matches the finding proposal. The fix is at the single point where
secsize is set, protecting all 7 divisor sites.
