β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4452

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4452 Β· 3 files
FileTypeDescriptionSize
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 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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Fix 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.
↓ fix.diffper-fix-DF-4452

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.