Wild out-of-bounds read in acpi_pci_link_choose_irq(): unbounded _PRS extended-IRQ values and FADT SciInterrupt used as index into pci_link_interrupt_weights[256]
Summary
link_add_prs stores raw UINT32 extended-IRQ values with no range validation; PCI_INTERRUPT_VALID is only '!= 255', so values >=256 (or >=2^31, negative ints passing the signed compare and making 1<<pos_irq UB) reach the 256-entry array index, and the SCI fallback indexes with the 16-bit FADT SciInterrupt unguarded. 0x7FFFFFF0 computes a non-canonical address -> fatal kernel fault during PCI routing; moderate values read adjacent .bss feeding a weight compare and a bootverbose console print (weak disclosure). Injection blocker on guest documented (_PRT lives in QEMU built-in DSDT; no loader DSDT-override in this tree); code trace complete. Bound-at-both-sites fix (shared file with DF-4451, compile-validated).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4452 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.0 KB | β raw | |
| verdict.json | β | 3.3 KB | view raw | |
| fix.diff | β | 2.7 KB | view raw |
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 intolink->l_irqs[]with no range validation (acpi_pci_link.c:358-367):link->l_irqs[i] = ext_irqs[i]whereext_irqs[i]is anyUINT32an extended IRQ descriptor in_PRScarries.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_INTERRUPTSguard used at:1097is absent here).
Impact
- Wild OOB kernel read. A crafted
_PRSvalue such as0x7FFFFFF0computesweights + 0x1FFFFFFC0(~+8 GB) β a non-canonical/kernel-unmapped address β fatal kernel page fault during boot PCI routing (DoS). - Moderate values read adjacent
.bsskernel memory; the read value only feeds the weight comparison and, underbootverbose, 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.
Fix verification
not_testableFix guards are part of the DF-4451 fix.diff and compile in the validated NOINV4451 kernel build, but the runtime trigger (PRT-referenced link) cannot be injected on this guest, so no baseline/patched runtime comparison was possible.
findings/poc/DF-4452/fix.diff; DF-4451 fix_build.log shows the same patched file compiling into NOINV4451.
Confirmed kernel references
Detail
Evidence (decisive lines)
README.md (root-cause citations), fix.diff (bounds guards at choose_irq loop + SCI fallback). Key lines: acpi_pci_link.c:125 (array [256]), :358-367 (unbounded ingest), :1010-1019 (OOB index), :1026-1028 (unbounded SCI index), sys/bus/pci/pcivar.h:334-335 (VALID = != 255).
PoC changes
No runnable PoC produced: SSDT-based injection (the DF-4451 technique) cannot reach choose_irq() because the path requires a _PRT source reference and the guest's PRT is owned by QEMU's DSDT (SSDT redefinition = namespace collision).
Verified recommended fix
Reject _PRS IRQ values outside [0, NUM_ACPI_INTERRUPTS) at ingest and bound the SciInterrupt index before pci_link_interrupt_weights[] reads (see fix.diff).
Verdict
Wild OOB read in acpi_pci_link_choose_irq(): _PRS extended-IRQ values (raw UINT32, stored unbounded at acpi_pci_link.c:358-367) are used as an index into the 256-entry pci_link_interrupt_weights[] array at acpi_pci_link.c:1015, and the FADT SciInterrupt (16-bit) indexes it unguarded at :1027-1028. PCI_INTERRUPT_VALID is only '!= 255' (sys/bus/pci/pcivar.h:335), so values >= 256 (or negative via signed conversion) pass every gate. Could not be exercised at runtime on this guest because reaching choose_irq() requires a _PRT entry referencing the crafted link, the guest's _PRT lives in QEMU's built-in DSDT, and an injected SSDT cannot redefine an existing namespace name (the SSDT load would be rejected wholesale); this DragonFly tree has no loader DSDT-override support (no acpi_dsdt tunable in sys/boot). Code trace is complete: a value like 0x7FFFFFF0 computes a non-canonical address and faults the kernel during PCI routing (boot DoS); moderate values leak adjacent .bss content only into a weight comparison and, under bootverbose, a console print. Not a false positive: the defect is visible by inspection with exact line citations; it is simply not runtime-injectable on this guest's fixed QEMU DSDT.
No comments yet.