# DF-4452 — wild out-of-bounds read in acpi_pci_link_choose_irq() via unbounded extended-IRQ values from _PRS

File: `sys/dev/acpica/acpi_pci_link.c` (pass 2)

## Root cause

* `link_add_prs()` stores raw 32-bit extended-IRQ values into `link->l_irqs[]`
  with **no range validation** (`acpi_pci_link.c:358-367`):
  `link->l_irqs[i] = ext_irqs[i]` where `ext_irqs[i]` is any `UINT32` an
  extended IRQ descriptor in `_PRS` carries.
* `PCI_INTERRUPT_VALID(x)` is only `(x) != 255` (`sys/bus/pci/pcivar.h:334-335`)
  — it does *not* bound the value below 256.
* `acpi_pci_link_choose_irq()` then uses those values as an **array index**:

  ```c
  for (i = 0; i < link->l_num_irqs; i++) {          /* :1010 */
          pos_irq = link->l_irqs[i];
          if (pos_irq < NUM_ISA_INTERRUPTS &&        /* :1012 signed compare  */
              (pci_link_bios_isa_irqs & 1 << pos_irq) == 0)
                  continue;
          pos_weight = pci_link_interrupt_weights[pos_irq];  /* :1015 OOB */
  ```

  `pci_link_interrupt_weights` is `[NUM_ACPI_INTERRUPTS]` = `[256]`
  (`acpi_pci_link.c:125`). Any `_PRS` extended IRQ value ≥ 256 (or ≥ 2^31,
  which becomes a negative `int` and passes the signed `< 16` compare at
  `:1012`, additionally making `1 << pos_irq` undefined behavior) indexes far
  outside the array.

* Same pattern at the SCI fallback (`:1026-1028`):
  `pos_irq = AcpiGbl_FADT.SciInterrupt; pos_weight = pci_link_interrupt_weights[pos_irq];`
  — the FADT SCI interrupt is a 16-bit field; values ≥ 256 index OOB with no
  check (the `< NUM_ISA_INTERRUPTS` guard used at `:1097` is absent here).

## Impact

* Wild OOB kernel read. A crafted `_PRS` value such as `0x7FFFFFF0` computes
  `weights + 0x1FFFFFFC0` (~+8 GB) — a non-canonical/kernel-unmapped address →
  fatal kernel page fault during boot PCI routing (DoS).
* Moderate values read adjacent `.bss` kernel memory; the read value only
  feeds the weight comparison and, under `bootverbose`, is printed to the
  console (`:1036-1038`, "Picked IRQ %u with weight %d") — a weak kernel
  memory disclosure channel.

## Reachability

`acpi_pci_link_choose_irq()` is reached from `acpi_pci_link_route_interrupt()`
(`:1044-1084`), called from `acpi_pcib_route_interrupt()`
(`sys/dev/acpica/acpi_pcib.c:285`) when a `_PRT` entry routes a PCI interrupt
through a link device whose `_PRS` was parsed. Precondition: firmware-supplied
ACPI with an extended-IRQ `_PRS` carrying an out-of-range interrupt number on
a PRT-referenced link (crafted DSDT/SSDT — same bar as DF-2389).

## Why no runtime PoC on this guest

Reaching `choose_irq()` requires a `_PRT` entry pointing at the crafted link
device. The audit guest's `_PRT` lives in QEMU's built-in DSDT
(`\_SB.PCI0._PRT`); an injected SSDT cannot redefine it (namespace collision —
the SSDT load would be rejected wholesale), and this DragonFly tree has no
loader DSDT-override support (`sys/boot` has no acpi_dsdt tunable). The
DF-4451 SSDT injection technique therefore cannot drive this path. Code trace
is complete and cited above; see VERDICT.md.
