# DF-4185 — acd_fixate() integer divide-by-zero panic via cur_write_speed == 0

* File: `sys/dev/disk/nata/atapi-cd.c` (pass 2)
* Class: kernel panic / local DoS from device-state-dependent division (CWE-369)
* Reachability: `CDRIOCFIXATE` on `/dev/acd0` (root / RESTRICTEDROOT per the
  disk-layer gate), against a drive whose capability page reads all failed at
  attach (sleepy/ill USB ODD, erroring firmware, or a crafted device).

## Root cause

`acd_get_cap()` (`sys/dev/disk/nata/atapi-cd.c:1696-1717`) retries the
capabilities MODE SENSE 5 times; **only on success** does it run
`cdp->cap.cur_write_speed = max(ntohs(...), 177);`. If all five attempts fail,
the `M_ZERO`-allocated `acd_softc` leaves `cap.cur_write_speed == 0` forever
(no other write site). Later,

```c
/* atapi-cd.c:1158-1160 */
if ((error = acd_test_ready(dev)) != EBUSY) {
	timeout = timeout / (cdp->cap.cur_write_speed / 177);   /* 0/177 == 0 */
```

divides by `cur_write_speed / 177` == 0 → x86 `idiv` trap 18 → kernel panic.

## Reproduction

Crafted/failed-device emulation on the audit guest (DF-3690-family method;
hook in `ata_atapicmd`, driver source unmodified): `df_audit.capfail=1` makes
every MODE SENSE page 0x2a (capabilities) fail during `acd_attach`, so
`cur_write_speed` stays 0 exactly as with a real failing drive. `df_audit.mode=1`
makes the drive accept the burner-only commands (MODE SENSE/SELECT page 0x05,
CLOSE TRACK) so fixate proceeds to the division.

```
./build.sh && ./run.sh          # same trigger binary as DF-4184
```

Observed (`panic.txt`, full serial in `panic_serial_full.log`):

```
Fatal trap 18: integer divide fault while in kernel mode
cpuid = 5; lapic id = 5
instruction pointer     = 0x8:0xffffffff80340232
current process         = 831
Stopped at      acd_ioctl+0x1842:       idivl   %ecx,%eax     <- inlined acd_fixate
```

Guest died to DDB; ssh sessions killed mid-command.

## Fix validation

`fix.diff` clamps the divisor (`write_speed < 177 -> 177`). Validated on the
guest with the identical emulated attack (`df_audit.capfail=1`):
pre-fix → trap 18 panic; post-fix (`DF-4185/fix_run.log` sample at
`../DF-4184/fix.2.log`) → the division path executes (`TEST_UNIT_READY
err=5 != EBUSY` traced) and the ioctl returns EIO after the normal wait,
**no panic, guest stays up**.

## Contents

* `panic.txt` — decisive serial-console excerpt (trap + backtrace)
* `panic_serial_full.log` — full serial log of the panic boot
* `fix.diff` — divisor clamp (subset of DF-4184's diff)
* trigger binary/source shared with `../DF-4184/`
