DF-4451 / verdict.json
{ "finding_id": "DF-4451", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "Heap out-of-bounds write in link_add_crs (and the same unbounded-cursor pattern in link_add_prs, acpi_pci_link_srs_from_crs and acpi_pci_link_route_irqs) caused by temporal divergence of _CRS/_PRS evaluations: acpi_pci_link_attach sizes pl_links[] from a counting walk (acpi_pci_link.c:465) but the populating walks index pl_links[link_index] with no runtime bound - only KASSERT/KKASSERT, which are no-ops on INVARIANTS-free production kernels (sys/sys/systm.h:117-119). The ACPI spec does not require _CRS/_PRS to be pure; on this system _CRS of a PNP0C0F link is evaluated at least 3x per boot by independent consumers (acpi.c:1908 bus pre-parse, the count walk, the add walk) and again at route/resume time (acpi_pci_link.c:727). A crafted SSDT with a stateful _CRS (1 IRQ descriptor for the counting evaluations, then N) reproduces: stock INVARIANTS GENERIC panics at its own boundary assertion panic: link_add_crs: array boundary violation (panic.txt); a production-style kernel (X86_64_GENERIC minus INVARIANTS) performs the writes silently - 128 extra descriptors = ~13 KB overwrite with the system booting normally (stealth, run-noinv-128-stealth-boot.log), and 8192 extra descriptors = ~850 KB overwrite hard-resetting the machine into a crash loop with no panic or DDB possible (16 reset cycles, run-noinv-boot.log). Attacker controls the overwrite length (descriptor count) and, per 104-byte struct link stride, ~9 bytes per stride (res_index counter, crs_type, l_irq). Boot-time, pre-authentication, firmware-data precondition (crafted ACPI in the booted image - same bar as DF-2389/2390, but a distinct mechanism: neither static _CRS/_PRS mismatch nor DPF inconsistency; single well-formed DPF-free buffer whose method returns different valid content on successive evaluations). uid=0 weaponization not claimed: the write fires once during early boot device attachment before userland exists and with limited per-stride control. fix.diff adds real bounds guards to all four walks; validated on the rebuilt production-style kernel with the exact crash-looping input: clean boot, guard fires 8191x, no panic.", "exploit_chain": "crafted SSDT (stateful _CRS on PNP0C0F link) in the booted VM image -> QEMU -acpitable -> ACPICA loads SSDT -> acpi bus evaluates _CRS (acpi.c:1908) -> pci_link attach count walk sizes pl_links=1 (104-byte kmalloc) -> link_add_crs add walk receives N-descriptor buffer -> pl_links[1..N-1] written OOB (stride 104, ~9 controlled bytes: res_index, crs_type, l_irq) -> attacker-chosen length; on INVARIANTS kernels: boot panic; on production kernels: silent heap corruption (128 desc) or hard-reset crash loop (8192 desc). uid=0 chain not completed (single boot-time write, limited field control).", "evidence": "panic.txt (stock-kernel panic at the exact cited line); run-noinv-boot.log (16 crash-loop resets, production kernel, 8192-descriptor trigger); run-noinv-128-stealth-boot.log (silent ~13 KB corruption, system boots and runs); verbose-benign-boot.log (pci_link5 dump showing the driver consuming stateful _CRS content); fix_run.log (patched kernel: boots clean, _CRS returned more IRQ resources than counted x8191); fix-validation.log (summary); VERDICT.md (full narrative)", "kernel_refs": [ "sys/dev/acpica/acpi_pci_link.c:235", "sys/dev/acpica/acpi_pci_link.c:465", "sys/dev/acpica/acpi_pci_link.c:504", "sys/dev/acpica/acpi_pci_link.c:727", "sys/dev/acpica/acpi_pci_link.c:766", "sys/dev/acpica/acpi_pci_link.c:896", "sys/dev/acpica/acpi.c:1908", "sys/sys/systm.h:117" ], "poc_changes": "No seeded PoC existed (pass-2 new finding). Built from scratch: ASL SSDT with a counter-based stateful _CRS; had to account for one pre-attach _CRS evaluation by the acpi bus (acpi.c:1908) - the first version (threshold 1) made both pci_link walks see the same 128-descriptor buffer (benign, verified via boot-verbose link dump), so the threshold was moved to 2 to place the divergence between the count and add walks. iasl is available in the DragonFly base system (/usr/sbin/iasl, v20251212). vm.sh needed a 2-line QEMU_EXTRA env passthrough (default-empty) to inject -acpitable.", "attempts": 4, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Sun Sep 20 07:48:59 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/NOINV4451 x86_64 (production-style build of X86_64_GENERIC minus INVARIANTS; baseline panic on stock X86_64_GENERIC #0 Thu Jul 2 06:02:54 UTC 2026)", "runtime_sec": 2100, "guest_dirty": 0, "build_cmd": "iasl ssdt_lnkx_v3.asl (on the guest, /usr/sbin/iasl; see build.sh)", "run_cmd": "QEMU_EXTRA=\"-acpitable file=ssdt_lnkx.aml\" dfbsd-qemu/vm.sh up (see run.sh)", "code_hash": "2a3de249b9587ce360cb9242cda216f0d3b1ec42e97c0388b8c6906312d86e5b", "notes": "Distinct from knowns: DF-2389 (static _PRS>_CRS count mismatch, link_add_prs sink), DF-2390 (DPF structural inconsistency), DF-1630 (assert no-ops). This finding is temporal divergence of a single well-formed buffer, with attach-time (link_add_crs), route-time (srs_from_crs read + route_irqs l_routed write at :905) and resume-time sinks; the route/resume sinks are code-certain but were not runtime-injectable on this guest (would need a PRT entry referencing the crafted link; QEMUs DSDT owns _PRT and SSDT cannot redefine it). Guest reset to with-src snapshot after validation.", "recommended_fix": "Replace the KASSERT/KKASSERT-only bounds in link_add_crs/link_add_prs/srs_from_crs/route_irqs with runtime guards that skip (and log) resources beyond pl_num_links (fix.diff, validated).", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applied to the guests /usr/src, kernel NOINV4451 (production-style) rebuilt and installed. Baseline with the exact v3 SSDT: hard-reset crash loop. Patched with the identical input: clean boot to multiuser, pci_link5 attaches with its single counted link, guard message printed exactly 8191 times, no panic/trap. The bad behavior is gone.", "fix_evidence": "fix_run.log (patched boot, grep -c returned more IRQ resources than counted = 8191); fix_build.log (kernel build+install); fix-validation.log (side-by-side summary)", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Sun Sep 20 08:20:07 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/NOINV4451 x86_64" } |