# DF-2489 — ata-disk.c divide-by-zero from device-supplied zero IDENTIFY geometry

**Finding:** `sys/dev/disk/nata/ata-disk.c` — `ad_get_geometry()` copies
`adp->heads` / `adp->sectors` raw from the ATA IDENTIFY words (word 3 /
word 6, else-branch at :476-480, no lower bound); a device reporting
heads=0 or sectors=0 makes `ad_attach()` at **:147** compute
`total_secs/(heads*sectors)` → x86 #DE, **Fatal trap 18: integer divide
fault** at attach. The identical divisor is re-evaluated in the
bootverbose path of `ad_describe()` at **:540**. Medium / CWE-369.

## Verdict: REPRODUCED (panic ×4 variants), then FIX VALIDATED.

## Rig (nata verbatim-interposition class — DF-2210/DF-249x)

`dfrig2489.c` (KLD, loaded AT BOOT via loader.conf; runtime kldload GPFs
on this INVARIANTS guest):

* `#include`s the production `sys/dev/disk/nata/ata-disk.c` **VERBATIM**
  (module plumbing neutralized, unused global `ad_devclass` renamed;
  `build.sh` sha256-checks the file =
  `8773a2dfc4cfc372b1d2be6c2775fd72e0445e56cdc974615fb94654f6b78de1`
  == audited `sys/` tree).  The rig therefore carries its own
  byte-identical `ad_attach` / `ad_describe` / `ad_get_geometry`.
* **Full production funnel dispatch (go1/2/3):** the kernel's own global
  `ata_identify()` (ata-all.c:683) is called on a fabricated channel
  (`struct ata_channel`, real `"ata"` devclass, `ATA_IDLE`, PIO-only
  `dma=NULL`, production `ata_queue_init()`).  It runs the REAL
  kmalloc'ed `ata_device`, the REAL `ata_getparam()` — the crafted
  512-byte IDENTIFY image is served by a `hw.begin_transaction` stub
  through the REAL `ata_queue_request`/`ata_start` machinery and parsed
  by the REAL le16toh/bswap/btrim code — the REAL newbus child, REAL
  `ad_probe`, and the **RUNNING KERNEL'S OWN `ad_attach`**
  (`nm`: `t ad_attach` = `0xffffffff80328d30`).  Everything from the
  IDENTIFY bytes to the fault is shipped production code.
* The ata_if setmode/locking methods on the channel are the interface's
  own DEFAULT implementations (`ata_if.m` CODE blocks: `ata_null_setmode`
  via `ata_limit_mode`, `ata_null_locking`); the scenarios run in a rig
  worker thread holding the MP token, i.e. the boot-attach context (the
  built-in atapicam driver's identify/attach also fires on every probed
  channel and `xpt_bus_register` asserts the MP token, like at boot).

## Scenarios (`sysctl kern.dfrig2489.go=N`, results on serial + status)

| N | what | stock kernel result |
|---|------|---------------------|
| 1 | CONTROL: full funnel, real geometry (16383/16/63, LBA 0x2000000) | clean attach: `ad0: 16384MB <DF2489 SANE GEOMETRY FW000001> at ata0-master PIO4` + bootverbose `33554432 sectors [33288C/16H/63S] 16 sectors/interrupt 1 depth queue`, RAID metadata scan (4 reads, all "check1 failed"), RETURNED |
| 2 | full funnel, IDENTIFY **heads=0** (word 3) | **Fatal trap 18** at `ad_attach+0x24b: divq %rsi,%eax` (kernel own, shipped binary) |
| 3 | full funnel, IDENTIFY **sectors=0** (word 6) | identical trap |
| 4 | VERBATIM rig copy `ad_attach`, heads=0 | identical trap (`divq`) |
| 5 | VERBATIM rig copy `ad_describe`, heads=0 ad_softc, bootverbose | `48MB <...>` line prints, then **Fatal trap 18** at `ad_describe+0x1bf: divq` (:540) |
| 6/7 | semi-funnel diagnostics (sane) | clean (used to bracket the newbus machinery) |

Line attribution is airtight (`ad_attach_stock_disasm.txt`): the trap IP
`0xffffffff80328f7b` = `ad_attach+0x24b` is `movslq %ecx,%rsi; div %rsi`
— the u64 `total_secs` divided by the 32-bit `heads*sectors` product —
sandwiched between the `info.` field stores and the
`device_add_child(dev, "subdisk", device_get_unit(dev))` sequence of
line 151.

## Reachability (honesty)

Trigger: a malicious or buggy ATA/SATA device (or bridge/VM firmware)
reporting IDENTIFY word 3 = 0 and/or word 6 = 0, with word 53 not
reporting words 54-58 valid (or current_heads/current_sectors = 0).  The
production chain copies IDENTIFY verbatim (`ata_getparam`,
ata-all.c:590-663 — the only check is a printable model byte) and
nothing validates the geometry before the division.  Words 3/6 are
retired in modern ATA spec (real devices report 16/63), so this is
AV:P (physical/local device attach, boot or hot-plug) — a panic-class
DoS of the attach path, **not** an escalation: the only attacker
influence on the faulting instruction is the divisor (zero) and the
dividend; there is no memory corruption and no control beyond the #DE
trap (guest dead at `db>`).

## Fix validation

`fix.diff` clamps the geometry at its single source in
`ad_get_geometry` (heads→16, sectors→63 when 0; the current_* branch is
already guarded).  With the clamp both :147 and :540 can never see a
zero divisor, and the values published to the disk layer
(`d_nheads`/`d_secpertrack`) stay sane.

Built with `make -j6 nativekernel + installkernel` (fix_build.log),
rig rebuilt from the patched source, exact scenarios re-run
(fix_run.log): go2 attaches cleanly with clamped
`[..C/16H/63S]` describe output; go1 control output is byte-identical to
the stock kernel.

## Reproduce (host)

    dfbsd-qemu/vm.sh reset with-src
    # copy this directory to the guest as /root/dfrig2489
    dfbsd-qemu/vm.sh run_root 'sh /root/dfrig2489/build.sh'      # KLD + boot-load setup
    dfbsd-qemu/vm.sh run_root 'shutdown -r now'                  # module loads at boot
    dfbsd-qemu/vm.sh run_root 'sh /root/dfrig2489/run.sh 1'      # control (clean)
    dfbsd-qemu/vm.sh run_root 'sh /root/dfrig2489/run.sh 2'      # Fatal trap 18 on serial
    dfbsd-qemu/vm.sh log                                          # panic text

One fatal scenario per boot (`vm.sh reset with-src` between).
