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

_PRT Source device used as a PCI interrupt link device with no NULL/driver validation: type-confused softc read (and NULL device deref) panic at boot

Summary

The _PRT Source resolves to whatever namespace object matches and is handed to acpi_pci_link_lookup with no NULL/devclass check - the victim softc is read as struct acpi_pci_link_softc (pl_num_links<-low 32 bits of device_t, pl_links<-the ACPI_HANDLE at +8) and walked OOB from a wild pointer; add_reference even writes through a forged struct. REPRODUCED on guest: crafted SSDT naming \\_SB.PCI0/HPET as PRT Source bricks boot (Fatal trap 12 in acpi_pci_link_lookup, ssh never up); devclass+NULL-guard fix validated on a single-fix kernel (boots clean with rejections). Firmware/VM-table-controlled trigger; FreeBSD-inherited.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5041 Β· 14 files
FileTypeDescriptionSize
README.md β€” 3.1 KB ↓ raw
VERDICT.md β€” 5.1 KB ↓ raw
ssdt_prtbad.asl β€” 2.0 KB ↓ download
ssdt_prtbad.aml β€” 191 B ↓ download
build.sh β€” 273 B view raw
run.sh β€” 201 B view raw
build.log β€” 302 B view raw
run.log β€” 10.4 KB view raw
panic.txt β€” 355 B view raw
fix.diff β€” 2.1 KB view raw
fix_build.log β€” 723 B view raw
fix_evidence.txt β€” 237 B view raw
env.txt β€” 151 B view raw
baseline-boot-tail.log β€” 12.9 KB view raw

DF-5041 β€” acpi_pcib.c: _PRT Source device used as PCI link device without NULL/driver validation β†’ boot-time kernel panic (type confusion / NULL deref)

File: sys/dev/acpica/acpi_pcib.c Severity: Low (firmware/AML-controlled trigger at boot) Β· Verified: reproduced (fatal trap 12 at boot) Β· Fix: validated on a patched kernel

What it is

A 191-byte SSDT (ssdt_prtbad.aml) that declares a second ACPI PCI host bridge (\_SB.PCIX, PNP0A03, bus 1) whose _PRT names, as interrupt link Source, ACPI devices that are not PCI interrupt link devices:

  • \_SB.PCI0 β€” the real host bridge (acpi_hpcib_softc)
  • \_SB.HPET β€” HPET (probe fails β†’ benign path)
  • \_SB.PCI0.S08 β€” ISA-bridge namespace node (attached by an unrelated driver on this guest)

At boot acpi_pcib_attach() walks the _PRT and hands those devices to acpi_pci_link_add_reference(); acpi_pci_link_lookup() then reads their softc as struct acpi_pci_link_softc (pl_num_links ← low 32 bits of a device_t at offset 0, pl_links ← ACPI_HANDLE at offset 8) and walks pl_links[i].l_res_index β€” an OOB read from a wild pointer, plus a link->l_references++ write through any forged match.

Build (on the host, uses the guest's iasl)

scp -F dfbsd-qemu/config findings/poc/DF-5041/ssdt_prtbad.asl dfbsd:/tmp/
dfbsd-qemu/vm.sh run_root 'cd /tmp && iasl ssdt_prtbad.asl'
scp -F dfbsd-qemu/config dfbsd:/tmp/ssdt_prtbad.aml dfbsd-qemu/ssdt_prtbad.aml

Run (baseline, stock kernel)

dfbsd-qemu/vm.sh down
cd dfbsd-qemu && QEMU_EXTRA="-acpitable file=ssdt_prtbad.aml" ./vm.sh up 300 || true
tail -25 boot.log

Expected

Stock kernel β€” boot dies with:

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

ssh never comes up (boot bricked). Full log: run.log, extract: panic.txt.

Patched kernel (fix.diff applied, single-fix build) β€” same SSDT boots fine:

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

ssh comes up; see fix_run.log / fix_evidence.txt.

Files

file what
ssdt_prtbad.asl / .aml the malicious SSDT (ASL source + compiled)
build.sh / run.sh exact commands
build.log iasl compile output
run.log FULL baseline boot log (panic)
panic.txt decisive lines
fix.diff the verified fix (never applied to the repo sys/)
fix_build.log patched-kernel build/install evidence
fix_run.log / fix_evidence.txt patched-kernel boot with same SSDT
env.txt guest uname / iasl version
VERDICT.md full narrative
manifest.json / verdict.json machine-readable

sha256: asl 579e1f3f…3a6d0, aml 643f5913…d4b10145, fix.diff 6ca71c41…5c18b08.

VERDICT.md
↓ download raw

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.

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

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Single-fix kernel (fix.diff applied to acpi_pcib.c only, nativekernel build #1 Sat Sep 26 09:37:24 UTC 2026): identical SSDT injection boots to ssh with 'PRT source ... is not a PCI interrupt link' rejections instead of the fatal trap 12; legitimate LNKA-LNKD link routing unaffected. Baseline panic reproduced / patched panic absent -> fixed.

fix_run.log lines 119-123; fix_evidence.txt; fix_build.log (installkernel of the patched build, uname kernel #1)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Sat Sep 26 09:37:24 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

firmware(crafted DSDT/SSDT _PRT Source) -> acpi_pcib_attach -> prt_walk_table -> prt_attach_devices -> acpi_pci_link_add_reference(non-link device) -> acpi_pci_link_lookup type-confused softc read/wild pointer walk -> fatal trap 12 at boot (persistent boot DoS; l_references++ write primitive when the confused walk matches SourceIndex 0). No user->root chain: privilege boundary is firmware->kernel.

Evidence (decisive lines)

run.log lines 119-140 (pcib1 attach, 'apparently invalid index 0', 'failed to force attach of \\_SB_.HPET', Fatal trap 12, fault VA 0xfffffffffffffff8, Stopped at acpi_pci_link_lookup+0x32); panic.txt extract; fix_run.log lines 119-123 (same SSDT, patched kernel: 'PRT source \\_SB_.PCI0 is not a PCI interrupt link', boot completes, ssh up); fix.diff; env.txt

PoC changes

No seed PoC existed; authored from scratch. iasl rejects String at _PRT index 2, so Source entries use inline namepath References (rscreate.c converts to pathnames); iasl also needs External() declarations for the cross-namespace targets.

Verified recommended fix

In acpi_pcib.c, require _PRT Source devices to be attached pci_link devices (NULL + devclass check) before calling acpi_pci_link_add_reference()/acpi_pci_link_route_interrupt(); see fix.diff.

Verdict

Boot-time kernel panic reproduced: a crafted SSDT _PRT (second PNP0A03 host bridge whose Source entries name non-link ACPI devices) drives acpi_pcib.c:126 acpi_pci_link_add_reference() / acpi_pcib.c:285 acpi_pci_link_route_interrupt() into acpi_pci_link_lookup(), which reads an arbitrary attached device's softc as struct acpi_pci_link_softc (pl_num_links<-low32(device_t), pl_links<-ACPI_HANDLE) or dereferences a NULL device; stock kernel faults at boot (Fatal trap 12, acpi_pci_link_lookup+0x32, fault VA 0xfffffffffffffff8) and never reaches ssh; the same SSDT on the single-fix kernel boots cleanly with explicit rejection messages. Trigger is firmware/boot-time AML only (no unprivileged userland reachability), so severity is Low despite the memory-corruption class.