# DF-2274 — scsi_da.c divide-by-zero from device-supplied block size of zero

## Verdict: NOT REPRODUCED (hardware-gated) — source bug CONFIRMED real; fix.diff compiles.

**Classification:** `not_reproduced` / HW-gated / impact=none. The div-by-zero is
**real in source**; the defense-in-depth `fix.diff` **compiles cleanly**
(`nativekernel rc=0`, `-Werror`, `-DINVARIANTS`).

### Why not reproduced (the gate)

The bug is in the **da(4) direct-access disk peripheral** (`dasetgeom` +
`XPT_CALC_GEOMETRY`). The guest has **no da(4) target**: the only disk is the
virtio-blk root device (`vtblk0`, not SCSI/CAM), and `camcontrol devlist` shows only
the QEMU DVD-ROM. There is **no `/dev/da*`** node. The trigger additionally requires
a **malicious SCSI/USB disk** returning READ CAPACITY with `length == 0`. The
unprivileged actor `maxx` cannot reach any da(4) path.

Gate proof (this guest):
```
$ camcontrol devlist   ->  only QEMU DVD-ROM (cd0); no da target
$ ls /dev/da*          ->  No such file or directory
$ pciconf -l | grep mass-storage  ->  atapci0 (PIIX3 IDE), virtio_pci1 (vtblk root disk)
$ id maxx              ->  uid=1001(maxx) ... groups=1001(maxx)
```

### The source bug (real, cited `path:line`)

`sys/bus/cam/scsi/scsi_da.c`:
- READ CAPACITY parse: `block_size = scsi_4btoul(rdcap->length)` (`scsi_da.c:1831`,
  also `:1850` long form) — **device-controlled**, no validation.
- `dasetgeom(periph, block_size, maxsector)` (`:1853`, `:2270`) stores it verbatim:
  `dp->secsize = block_len` (`:2289`), `ccg->block_size = dp->secsize` (`:2302`).
- Every subsequent I/O divides by `secsize`: TRIM merge (`:1341-1342`, `:1366`),
  `KKASSERT(bio->bio_offset % secsize == 0)` (`:1490`), read/write LBA math
  (`:1500-1501`), and the SIM's `XPT_CALC_GEOMETRY` handler. `secsize == 0` → `#DE`
  → `trap_fatal` → **kernel panic**.
- A second path: when READ CAPACITY fails with a non-0x25 sense, `dasetgeom` is never
  called, so `secsize` stays at its `M_ZERO` initial `0`, again dividing by zero on
  the next I/O.

**Note (upstream mitigation observed during tracing):** the XPT core
`xpt_action()` `XPT_CALC_GEOMETRY` dispatcher at `sys/bus/cam/cam_xpt.c:3063` already
filters `block_size == 0` (and `volume_size == 0`) **before** calling the SIM, so the
SIM-side `XPT_CALC_GEOMETRY` div-by-zero variants in the HBA drivers (DF-2411/2415)
are not reachable via the da→`dasetgeom`→`xpt_action` path on this kernel. The `da`
driver itself, however, still caches `secsize == 0` and divides by it in its own
strategy/trim paths, so the panic remains reachable from a malicious device.

Attacker model: malicious SCSI/USB disk (evil-maid auto-probe on plug-in, or iSCSI
target with crafted READ CAPACITY). No user account required for the auto-probe path.

### Exploit chain

Not developed — no da(4) target on this guest and no malicious disk. Valid hard
blocker: device-controlled primitive, no malicious device present.

### Fix (defense-in-depth, validated to compile)

`fix.diff` makes `dasetgeom()` reject `block_len == 0` (returning `EINVAL`) before
caching it as `secsize`, converting the signature to `int` so callers can propagate
the failure. Validated in the combined `nativekernel` build (`fix_build.log`).

### Reproduce

No runnable PoC on this guest (no da target, no malicious disk). Artifacts:
`env.txt`, `fix.diff`, `fix_build.log`.
