# DF-5061 — VERDICT

**Status: reproduced (kernel page fault from the shipping driver's own code path)**
**Impact: panic (deterministic wild kernel-pointer dereference; DoS)**
**Confidence: certain** (types are statically provable; deref observed on guest)

## Root cause (line-accurate)

* `sys/dev/raid/ida/ida_pci.c:106-108`:
  ```c
  completed = ida_inl(ida, R_42XX_REPLY);
  if (completed == -1)
  return (0);			/* fifo is empty */
  ```
* `completed` is `bus_addr_t` = `uint64_t` on x86_64
  (`sys/cpu/x86_64/include/bus_dma.h:36`).
* `ida_inl` is `bus_space_read_4` (`sys/dev/raid/ida/idavar.h:40-41`),
  which returns `u_int32_t` (`sys/cpu/x86_64/include/bus_dma.h:130`,
  body at :154-162: `return (*(volatile u_int32_t *)(handle + offset));`).
* `-1` promotes to `0xFFFFFFFFFFFFFFFF`; the register value
  `0xFFFFFFFF` zero-extends to `0x00000000FFFFFFFF`. The compare is
  **false for every possible register value** — the empty-FIFO sentinel
  is dead code on all 64-bit DragonFly (it worked on 32-bit FreeBSD where
  `bus_addr_t == uint32_t`; the code is unchanged since FreeBSD 2001).

## Consequence chain (all verified on the guest)

1. `ida_v4_done()` returns `0xFFFFFFFF` for an empty reply FIFO **and**
   performs the `ida_outl(R_42XX_REPLY, 0)` "confirm read" write on the
   empty FIFO (harness log: `R_42XX_REPLY now 0x00000000`).
2. `ida_wait()` (`sys/dev/raid/ida/ida.c:457`):
   `while ((completed = ida->cmd.done(ida)) == 0)` — exits instantly with
   the bogus completion. This is the **polling path taken by every
   `ida_command()` during attach** (interrupts are disabled:
   `ida_attach()` → `cmd.int_enable(ida, 0)` at ida.c:242), i.e. the very
   first `CMD_GET_CTRL_INFO` at boot.
3. `ida_wait()` then calls `idahwqcbptov(ida, completed & ~3)`
   (ida.c:463); `ida_intr()` does the same at ida.c:483 — but note the
   interrupt variant (`while (... != 0)`) can never terminate once the
   FIFO drains: every empty read yields `0xFFFFFFFF` again → infinite
   `ignoring completion ffffffff` loop (ISR hang) whenever the wild deref
   does not fault first.
4. `idahwqcbptov()` (ida.c:140-148) computes
   `hwqcb = hwqcbs + (0xFFFFFFFC - hwqcb_busaddr)` and dereferences
   `hwqcb->qcb` (offset 0x110 in `struct ida_hardware_qcb`,
   idavar.h:76-81). With `hwqcbs` DMAP-mapped and `hwqcb_busaddr` its
   32-bit physical address, the wild address is deterministic:
   **DMAP + 0xFFFFFFFC (physical 4 GB − 4)** — unmapped on every host
   with < 4 GB RAM.

## Reproduction evidence

* Baseline run 1 (`panic.firstboot.txt`, serial console): harness mode 2 —
  prints `sentinel DEAD`, `ida_v4_done() = 0x00000000ffffffff (empty FIFO
  reported as a completion!)`, the empty-FIFO confirm-write side effect,
  then `panic` with the harness frame in the trace (`trap 0xc`).
* Baseline run 2 (`panic.txt`, serial console, fresh boot): full
  `Fatal trap 12` record:
  ```
  fault virtual address  = 0xfffff801625a310c
  fault code             = supervisor read data, page not present
  Stopped at df5061_fire_sysctl+0xb5: movq 0x110(%rax,%rdx,1),%r13
  ```
  Arithmetic check: `0xfffff801625a310c == hwqcbs(0xfffff80118498000) +
  (0xFFFFFFFC − hwqcb_busaddr(0xb5ef5000)) + 0x110` — **exact match**,
  the faulting instruction is the `hwqcb->qcb` load inside the verbatim
  `idahwqcbptov()`.
* Fixed run (`run.log`, `fix_run.log`): `(uint32_t)completed ==
  0xffffffff` variant returns 0, leaves the register untouched, no fault;
  module unloads cleanly.

## Exploitability (honest ceiling)

Wild-address **read** feeding a pointer compare; no write primitive, no
copyout of the garbage. On < 4 GB hosts: page fault panic (boot-time on
real boards — the driver is in GENERIC: `device ida`,
`X86_64_GENERIC:132`). On ≥ 4 GB hosts: garbage `qcb` pointer →
`qcb->state` read (mapped) → `!= QCB_ACTIVE` → `ida_intr` spins forever
(ISR-level hang + console flood). Both are kernel DoS. Gated on Compaq
SMART-2/4x00-class PCI hardware being present — severity Low per rubric
(specific hardware), but the code defect itself is unconditional on
64-bit.

## Why not false-positive / already-fixed

* Types verified in-tree and on the guest (`bus_addr_t` uint64 vs
  `bus_space_read_4` u_int32_t).
* Consumers verified at ida.c:457-467, 482-491; `ida_v4_done` is wired to
  every v4 board via `ida_v4_access` (ida_pci.c:138-144) and `board_id[]`
  (ida_pci.c:158-167).
* No intermediate masking anywhere between register read and compare.

## Fix

`fix.diff` — mask the compare to the register width:
`if (completed == (bus_addr_t)0xffffffffU)`.
Validated: patched `ida_pci.c` rebuilt clean as `ida.ko` with `-Werror`
(`fix_build.log`); fixed logic re-run on the guest with correct behavior
(`fix_run.log`). `git apply --check` passes against the pristine tree.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
