# DF-5041 VERDICT — REPRODUCED (boot-time kernel panic), fix validated

**Finding:** `sys/dev/acpica/acpi_pcib.c` uses a `_PRT` `Source` ACPI object as
a PCI interrupt link device with **no NULL check and no driver-class check**,
type-confusing an arbitrary attached device's softc with
`struct acpi_pci_link_softc` (or NULL-dereferencing when the namespace node has
no device_t).

## Root cause (path:line)

* `sys/dev/acpica/acpi_pcib.c:111-115` — `prt_attach_devices()` resolves
  `entry->Source` to *any* namespace handle and takes `acpi_get_device()`
  of it with no validation beyond non-NULL.
* `sys/dev/acpica/acpi_pcib.c:118-123` — force-attaches that device and, if
  *any* driver claims it, proceeds.
* `sys/dev/acpica/acpi_pcib.c:126-127` — hands it to
  `acpi_pci_link_add_reference()`.
* `sys/dev/acpica/acpi_pci_link.c:620-631` — `acpi_pci_link_lookup()` reads
  `device_get_softc(dev)` as `struct acpi_pci_link_softc`
  (`int pl_num_links` @0, `struct link *pl_links` @8) and walks
  `pl_links[i].l_res_index`.  For an `acpi_hpcib_softc` victim this reads
  `pl_num_links` = low 32 bits of the `device_t` and `pl_links` = the
  `ACPI_HANDLE` — a wild pointer; a zero match returns a **forged
  `struct link *`** and `add_reference()` then writes
  `link->l_references++` / `pci_link_interrupt_weights[link->l_irq]++`
  through it.
* `sys/dev/acpica/acpi_pcib.c:280-286` — `acpi_pcib_route_interrupt()` passes
  `acpi_get_device(lnkdev)` (may be NULL for nodes with no device_t) straight
  into `acpi_pci_link_route_interrupt()` → `device_get_softc(NULL)` deref;
  and if the lookup misses, `sys/dev/acpica/acpi_pci_link.c:1054-1055`
  **panics** ("apparently invalid index") on legitimate hardware with a
  slightly-wrong table.
* Context: ACPICA accepts any UINT32 `Pin` and any `SourceIndex`
  (`sys/contrib/.../rscreate.c:452,539`) — the kernel side must not assume a
  link device.

Both FreeBSD and DragonFly carry the pattern; DF is the audited tree.

## Reproduction (guest: DragonFly 6.5-DEVELOPMENT #0, X86_64_GENERIC, QEMU i440fx)

1. `ssdt_prtbad.asl` compiled with guest `/usr/sbin/iasl` (20251212) →
   191-byte SSDT.
2. `QEMU_EXTRA="-acpitable file=ssdt_prtbad.aml" ./vm.sh up 300` on the stock
   kernel: **ssh never came up**; serial log (`run.log`) shows

   ```
   pcib1: <ACPI Host-PCI bridge> on acpi0
   pcib0: apparently invalid index 0            <- lookup on the REAL host bridge softc
   pcib1: failed to force attach of \_SB_.HPET  <- benign branch
   Fatal trap 12: page fault while in kernel mode
   fault virtual address = 0xfffffffffffffff8
   Stopped at acpi_pci_link_lookup+0x32: movl (%rax),%ecx
   ```

   i.e. the third `_PRT` entry (`\_SB.PCI0.S08`, attached by an unrelated
   driver) type-confused its softc and the kernel faulted inside
   `acpi_pci_link_lookup` during boot attach.  Boot is bricked until the
   table is removed — a firmware-controlled persistent boot DoS, with a
   write primitive (`l_references++`) on the heap whenever the confused walk
   happens to find a zero `l_res_index`.

3. Control boot without the SSDT: normal boot (`boot.log.baseline-clean`).

## Fix validation

* `fix.diff` (authored after reproduction, applied only inside the guest's
  `/usr/src` copy): after the force-attach in `prt_attach_devices()`, reject
  any child whose devclass is not `pci_link`; in `acpi_pcib_route_interrupt()`
  reject NULL / non-`pci_link` sources with a message instead of calling into
  the pci_link code.
* `make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel` →
  kernel `#1 Sat Sep 26 09:37:24 UTC 2026`.
* Same SSDT, patched kernel (`fix_run.log`):

  ```
  pcib1: <ACPI Host-PCI bridge> on acpi0
  pcib1: PRT source \_SB_.PCI0 is not a PCI interrupt link
  pcib1: failed to force attach of \_SB_.HPET
  pcib1: PRT source \_SB_.PCI0.S08_ is not a PCI interrupt link
  pci1: <ACPI PCI bus> on pcib1
  ```

  No panic; ssh came up; system fully operational.  Legitimate PRT handling
  (real LNKA-LNKD link devices on the stock QEMU DSDT) is unaffected because
  those force-attach to the `pci_link` driver and pass the devclass check.
  Baseline reproduced / patched not reproduced ⇒ **fix_status = fixed**.

## Impact assessment

Trigger is **boot-time AML** (`_PRT`): requires crafted or buggy firmware /
host-controlled VM tables — an attacker who can already control DSDT is
effectively ring-0 already.  Realistic impact: persistent boot DoS (panic
loop) and firmware-directed heap corruption from buggy-BIOS field reports.
Hence severity **Low**, CWE-476/CWE-843.  No unprivileged-guest-userland
reachability: `acpi_pcib_attach` runs only during boot attach.

Guest restored to the clean snapshot (`vm.sh reset with-src`, stock kernel #0)
after validation.

## Exploit chain

Not applicable beyond the demonstrated boot panic: privilege boundary of the
bug is firmware→kernel, not user→kernel.

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