# DF-4451 — VERDICT

**Status: reproduced** (boot-time kernel memory-safety violation, guest-verified)
**Impact: heap OOB write on production (INVARIANTS-free) kernels; boot panic on
stock GENERIC.** Crafted-ACPI precondition (same threat bar as DF-2389/2390).

## What was run

1. **Control boot** (no SSDT): stock kernel from the `with-src` snapshot,
   DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026
   (X86_64_GENERIC), boots clean; 5 pci_link devices attach
   (`pci_link0..4` = LNKA..LNKD, LNKS).
2. **Trigger build**: `ssdt_lnkx_v2.asl` compiled with the guest's base
   `/usr/sbin/iasl` (version 20251212) → `ssdt_lnkx.aml` (666 bytes).
   Injected with `QEMU_EXTRA="-acpitable file=ssdt_lnkx.aml" ./vm.sh up`
   (vm.sh passes `$QEMU_EXTRA` through to qemu-system-x86_64; two-line
   infrastructure hook added to vm.sh, default-empty).
3. **Trigger boot, stock GENERIC kernel (INVARIANTS on)**: the ACPI bus
   pre-parses `_CRS` (acpi.c:1908 → eval #1, returns BUF1), the counting
   walk gets eval #2 (BUF1 → `pl_num_links = 1`, `pl_links` = kmalloc of
   ONE 104-byte `struct link`), then `link_add_crs` gets eval #3 which
   returns BUF2 (128 IRQ descriptors) → walks 128 links into the 1-element
   array. At `link_index = 1` the kernel panics at its own boundary
   assertion:

   ```
   panic: link_add_crs: array boundary violation
   link_add_crs() at link_add_crs+0x18a
   AcpiWalkResourceBuffer() at AcpiWalkResourceBuffer+0x60
   AcpiWalkResources() at AcpiWalkResources+0x9f
   acpi_pci_link_attach() at acpi_pci_link_attach+0x3d1
   device_doattach() at device_doattach+0x3af
   ```

   (`panic.txt`, full serial log `panic-inv-assert-boot.log`.) This proves
   the overrun is reached at exactly `sys/dev/acpica/acpi_pci_link.c:235-237`.

4. **Calibration boot** (`ssdt_lnkx_v1.asl`, threshold 1): both walks saw the
   same BUF2 → 128 in-bounds links, no panic; the verbose dump
   (`verbose-benign-boot.log`) shows `pci_link5` with links 0..127 — direct
   evidence that (a) the driver consumes stateful `_CRS` content verbatim and
   (b) `_CRS` of a link device is evaluated ≥3 times per boot by independent
   consumers (acpi.c:1908, acpi_pci_link.c:465, acpi_pci_link.c:504).

5. **Production-style kernel (INVARIANTS removed — config NOINV4451)**:
   `X86_64_GENERIC` minus `options INVARIANTS`, built with
   `make nativekernel KERNCONF=NOINV4451`. With the same SSDT the
   KASSERT/KKASSERT family compiles to no-ops (`sys/sys/systm.h:117-119`),
   so `link_add_crs` performs the writes:
   * **v3 SSDT (8192 extra descriptors ≈ 850 KB overwrite walk)**: the
     machine hard-**resets** — no panic, no trap, no `db>` (the overwrite
     destroyed state the trap machinery itself needs) — and because the
     SSDT is injected at every boot it **crash-loops**: 16 reset cycles
     captured in `run-noinv-boot.log`.
   * **v2 SSDT (128 extra descriptors ≈ 13 KB overwrite)**: the system
     **boots and runs normally** — the corruption is completely silent
     (`run-noinv-128-stealth-boot.log`, system up, uptime 42 s at
     inspection). Nothing in any log indicates 13 KB of kernel heap was
     just overwritten.

## Primitive characterization

* **What**: contiguous linear heap overwrite, stride `sizeof(struct link)`
  (104 bytes), 9 controlled-ish bytes per stride (`res_index` = 1,2,3…,
  `crs_type` = 3 or 15 (IRQ/EXTIRQ descriptor type), `l_irq` = first IRQ
  number in the descriptor mask or 255).
* **Where**: immediately past a `kmalloc(n·104, M_PCI_LINK, M_WAITOK|M_ZERO)`
  allocation in the DF kernel heap.
* **How far**: fully attacker-chosen — the crafted `_CRS` decides the number
  of IRQ descriptors (hundreds to tens of thousands; AML buffer of a few KB
  is enough for megabytes of overwrite).
* **Boot-time, pre-authentication** (during `device_doattach` of the link
  device).

Turning this into uid=0 would additionally require controlling the heap
layout at that early boot stage (grooming which objects sit after `pl_links`)
and useful field values; the per-stride controlled bytes are limited, and the
write happens once at boot before userland exists, so weaponization is
nontrivial. The vulnerability class (heap OOB write from firmware data) and
its reliable DoS/corruption impact are proven; privesc weaponization left
unclaimed. On INVARIANTS kernels (stock GENERIC as shipped) the same input is
a deterministic boot panic.

## False-positive check

Not a false positive: the stock kernel's own KASSERT text fired at the exact
cited line with the crafted table loaded (`panic.txt`), and the count/add
divergence is by construction (stateful `_CRS`, `_PRS` deliberately benign at
1 descriptor so the KNOWN DF-2389 path — `_PRS` count > `_CRS` count — was
NOT the trigger; no DPF descriptors exist in either buffer, so DF-2390's
mechanism was not involved either).

## Fix validation

`fix.diff` adds real bounds guards to the four unbounded cursor walks
(`link_add_crs`, `link_add_prs`, `acpi_pci_link_srs_from_crs`,
`acpi_pci_link_route_irqs`); the same diff also bounds the DF-4452 reads in
`acpi_pci_link_choose_irq`. Applied to the guest's `/usr/src` copy, kernel
`NOINV4451` rebuilt, same SSDT re-run: see `fix-validation.log` +
`fix_run.log` / `fix_build.log`.

## 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.
