# DF-2210 — PoC evidence pack (verify run, 2026-10-17)

Finding: `sys/dev/disk/nata/chipsets/ata-national.c:77-78` —
`ata_national_setmode()` unconditionally executes

```c
    ch->dma->alignment = 16;			/* ata-national.c:77 */
    ch->dma->max_iosize = 64 * DEV_BSIZE;	/* ata-national.c:78 */
```

with no NULL guard. Its own template (the Cyrix twin this chip is a
clone of, `chipsets/ata-cyrix.c:82-101`) guards the identical derefs
behind `if (ctlr->r_res1)`. `ch->dma` is legitimately NULL in
production: `ata-dma.c:151-154` frees `ch->dma` and NULLs it whenever
DMA allocation fails ("WARNING - DMA allocation failed, disabling
DMA"), after which the channel keeps running PIO-only — and every
later setmode funnel (boot reinit, `IOCATASMODE` ioctl
`ata-all.c:523-526`, `acd_open` `atapi-cd.c:147/226`) panics the
kernel with a NULL-page WRITE instead of setting PIO mode.

## How it was verified (production-handler KLD dispatch rig)

Hardware is gated (National Geode SC1100, PCI ID 0x0502100b), so the
established rig class was used (`dfrig_nat.c` — same class as DF-252x
trm / DF-249x nata):

* the production `chipsets/ata-national.c` is `#include`d **verbatim**
  into the KLD (`build.sh` pins sha256
  `c694b9095ed2dc50acb932a2ecde564b535c5a0f66cba355a0345d69b16a43e6`
  = the audited `sys/` tree; the only textual intervention renames the
  never-called `ata_national_ident` to avoid a duplicate global with
  the built-in kernel copy — `ata_national_setmode` itself is compiled
  byte-for-byte under its production name);
* the **running kernel's own** `ata_national_setmode` (local symbol,
  `nm /boot/kernel/kernel` → `0xffffffff80321780` on the stock kernel)
  is dispatched by address — the shipped binary, zero recompilation;
* the fabricated state is exactly what the machine presents when DMA
  is unavailable: `ch->dma == NULL`, disk present
  (`ch->devices = ATA_ATA_MASTER`), PIO4/UDMA2-capable identify data;
  the channel is in the `ATA_STALL_QUEUE` state with a
  `hw.begin_transaction` that reports the drive accepting SETXFER, so
  the kernel's own production `ata_controlcmd()`/`ata_queue_request()`
  run unmodified (synchronous CONTROL completion, `ata-queue.c:100-115`);
  PCI config timing writes dispatch through the real kernel kobj
  machinery onto a recording fake bus;
* dispatch is either direct or through the extracted real call site
  (`ata_pcichannel_setmode`, `ata-pci.c:855-866`).

## Scenarios (`sysctl kern.dfrig_nat.go=N`)

| go | handler | ch->dma | stock kernel result |
|----|---------|---------|---------------------|
| 1  | running kernel own copy | NULL  | **Fatal trap 12, vad 0x58, `ata_national_setmode+0x65: movl $0x10,0x58(%rax)`** (line 77) |
| 2  | running kernel own copy | exists | normal setmode: alignment 2→16, max_iosize 65536→32768, PCI 0x44←0x80000000, 0x40←piotiming[4], mode=PIO4 |
| 3  | verbatim #included copy | NULL  | identical panic (byte-identity) |
| 4  | verbatim #included copy | exists | identical to go=2 |
| 5  | full extracted call site (`ata-pci.c:855-866`), requested ATA_UDMA2 | NULL | panic on the first `ctlr->setmode` call |
| 6  | full extracted call site | exists | two setmode calls; UDMA pass writes `udmatiming[2]=0x80911030`, mode=ATA_UDMA2 |

## Build / run (guest, root; requires `vm.sh reset with-src`)

```
cd /root/dfrig_nat && sh build.sh     # KLD with verbatim production file
kldload ./dfrig_nat.ko
KADDR=$(nm /boot/kernel/kernel | awk '/ t ata_national_setmode$/{print $1}')
sysctl -w kern.dfrig_nat.addr=0x$KADDR
sysctl -w kern.dfrig_nat.go=2         # CONTROL first (returns)
sysctl -w kern.dfrig_nat.go=1         # THE FINDING — panics a stock kernel
```

Expected on a **stock** kernel: `Fatal trap 12: page fault while in
kernel mode, fault virtual address = 0x58, supervisor write data, page
not present`, `Stopped at ata_national_setmode+0x65: movl
$0x10,0x58(%rax)` — the store of the constant 16 (0x10) to
`offsetof(struct ata_dma, alignment)` (0x58) through the NULL `ch->dma`
— and the guest dead at `db>`.

## Fix validation

`fix.diff` guards the two stores with `if (ch->dma) { ... }` (the
canonical guard the Cyrix twin already carries). Single-fix kernel
built with `make -j6 nativekernel KERNCONF=X86_64_GENERIC`
(`fix_build.log`); the exact same rig re-run against the patched
kernel (`fix_run.log`): go=1/go=3/go=5 RETURN — setmode completes in
PIO (mode set, PCI timings written, no panic) — and the go=2/4/6
controls behave identically to stock (dma fields still overridden when
dma exists).

## Artifacts

* `dfrig_nat.c` / `Makefile` / `build.sh` / `run.sh` — the rig
* `build.log` — full untrimmed baseline build (checker sha lines included)
* `run_control.log` — controls go=2/4/6 on stock kernel (dma exists)
* `panic.txt` — serial console: go=1, kernel's own handler, dma=NULL
* `panic_rig_copy.txt` — serial console: go=3, verbatim copy, dma=NULL
* `kernel_handler_disasm.txt` — objdump of the stock kernel handler
  (`+0x65` = line 77 store; next store `+0x73` = line 78)
* `fix.diff` — git-apply-able guard
* `fix_build.log` / `fix_run.log` — patched-kernel build + validation run
* `env.txt` — guest environment
* `VERDICT.md` — full narrative
* `manifest.json` / `verdict.json` — machine-readable verdicts
