_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)
PoC verification
Evidence pack
findings/poc/DF-5041 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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()resolvesentry->Sourceto any namespace handle and takesacpi_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 toacpi_pci_link_add_reference().sys/dev/acpica/acpi_pci_link.c:620-631βacpi_pci_link_lookup()readsdevice_get_softc(dev)asstruct acpi_pci_link_softc(int pl_num_links@0,struct link *pl_links@8) and walkspl_links[i].l_res_index. For anacpi_hpcib_softcvictim this readspl_num_links= low 32 bits of thedevice_tandpl_links= theACPI_HANDLEβ a wild pointer; a zero match returns a forgedstruct link *andadd_reference()then writeslink->l_references++/pci_link_interrupt_weights[link->l_irq]++through it.sys/dev/acpica/acpi_pcib.c:280-286βacpi_pcib_route_interrupt()passesacpi_get_device(lnkdev)(may be NULL for nodes with no device_t) straight intoacpi_pci_link_route_interrupt()βdevice_get_softc(NULL)deref; and if the lookup misses,sys/dev/acpica/acpi_pci_link.c:1054-1055panics ("apparently invalid index") on legitimate hardware with a slightly-wrong table.- Context: ACPICA accepts any UINT32
Pinand anySourceIndex(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)
ssdt_prtbad.aslcompiled with guest/usr/sbin/iasl(20251212) β 191-byte SSDT.QEMU_EXTRA="-acpitable file=ssdt_prtbad.aml" ./vm.sh up 300on 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.
- 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/srccopy): after the force-attach inprt_attach_devices(), reject any child whose devclass is notpci_link; inacpi_pcib_route_interrupt()reject NULL / non-pci_linksources 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
fixedSingle-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)
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.
No comments yet.