DF-5588 / verdict.json
{ "finding_id": "DF-5588", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "verdict": "sdt_probe()s kenv branch (acpi_sdt.c:71-77) installs hint.acpi.0.rsdp as the ACPI table root with ZERO validation - not the 8-byte RSD PTR signature that even the scan path checks (acpi_sdt.c:130), not checksum/Length/Revision, and not the address itself. Verified live on the KVM rig three ways: (R2) an RSDP with signature de ad be ef de ad be ef and wrong checksum is accepted and its RSDT->MADT chain consumed (RSDP in kenv hint, madt_lapic_probe lapic_count=3 on a 6-vCPU guest, crafted IOAPIC configured); (R3) the same invalid RSDP plus DF-5430s bsp_omit MADT panics the boot at madt_lapic_pass2 - the whole crafted-table family (DF-5430..5433, DF-5583) hangs off this single unvalidated if; (R4) hint.acpi.0.rsdp=0x800000000000 with no blob planted at all panics immediately (vm_fault: fault on nofault entry) because pmap_mapdev_attr (pmap.c:6192-6219) writes the raw 64-bit address into PTEs and qemu64s 40 phys bits make bit 47 reserved - one loader.conf line bricks the boot. Boot-owner-gated (loader.conf/loader prompt), so Medium like DF-1113, not an unprivileged path. Pass-1 boundary respected: DF-1114 covered the SCANNED RSDPs missing checksum/Length; DF-1113 the unbounded table Length; the kenv branch is a separate acceptance with less validation than either.", "exploit_chain": "", "evidence": "run.nosig_control.boot.log: ACPI SDT: RSDP in kenv hint + madt_lapic_probe: lapic_count=3 with deadbeef RSDP signature (acceptance proof); run.nosig_bsp_omit.boot.log / panic.txt: panic: assertion \"arg.bsp_found\" failed in madt_lapic_pass2 at acpi_madt.c:375 reached through the invalid RSDP; run.bogus_addr.panic.log: panic: vm_fault: fault on nofault entry, addr: 0xfffff8004f062000 directly after ACPI SDT: RSDP in kenv hint (hint 0x800000000000, no blob); fix_run.log: patched kernel prints sdt_probe: kenv RSDP failed validation, falling back to firmware scan, RSDP found in BIOS mem, lapic_count=6 (real firmware MADT), bsp panic GONE", "kernel_refs": [ "sys/platform/pc64/acpica/acpi_sdt.c:71", "sys/platform/pc64/acpica/acpi_sdt.c:74", "sys/platform/pc64/acpica/acpi_sdt.c:109", "sys/platform/pc64/acpica/acpi_sdt.c:130", "sys/platform/pc64/x86_64/pmap.c:6192", "sys/platform/pc64/x86_64/pmap.c:6201", "sys/platform/pc64/x86_64/pmap.c:6208", "sys/platform/pc64/acpica/OsdEnvironment.c:72", "sys/platform/pc64/acpica/acpi_madt.c:375" ], "poc_changes": "No seed PoC. gen_rsdp_inject.py authored from scratch (DF-5430s chain builder extended): scenarios parameterize the RSDP signature (valid RSD PTR vs de ad be ef de ad be ef) and v1 checksum (wrong 0xAA) over a fixed RSDT->MADT chain, plus the DF-5430 bsp_omit MADT payload and the no-blob bogus-address variant.", "attempts": 8, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (with-src snapshot; QEMU/KVM -m 4096 -smp 6, qemu64 model, 40 phys addr bits)", "runtime_sec": 6300, "guest_dirty": 0, "build_cmd": "host: cd findings/poc/DF-5588 && ./build.sh (python3 gen_rsdp_inject.py {valid_control,nosig_control,nosig_bsp_omit})", "run_cmd": "vm.sh reset with-src; vm.sh run_root printf \"hint.acpi.0.rsdp=0x40000000\\nboot_verbose=YES\\n\" >> /boot/loader.conf; sync; vm.sh down; QEMU_EXTRA=\"-device loader,file=/tmp/opencode/df5588/nosig_bsp_omit.bin,addr=0x40000000,force-raw=on\" vm.sh up 150; vm.sh log [R4: same but loader.conf hint=0x800000000000 and no -device loader]", "code_hash": "c8625720153632d23f0f460199977a60ed90b879b1ff7a6bbe93409d6c45b2ba", "notes": "Also observed while fixing (cross-file, NOT filed here): (1) OsdEnvironment.c:72 AcpiOsGetRootPointer is a SECOND unvalidated consumer of the same hint - on the patched kernel ACPICA still consumed the crafted chain and faulted at AcpiTbLoadNamespace+0x36, and hangs on a zeroed hint; the complete fix needs validation in both acceptance points (matches the existing acpi.c unvalidated RSDP kenv lead). (2) A valid-signature chain with no FADT/DSDT makes the stock kernel fault at AcpiTbLoadNamespace+0x36 during acpi0 attach - contrib ACPICA behavior, for the acpica-family lead. (3) Pre-fix, a hint pointing at zeroed RAM is accepted, prints RSDT paddr == 0 and the boot wedges before SMP. Guest left clean (reset with-src verified).", "recommended_fix": "In sdt_probe()s kenv branch require rsdp_paddr < ptoa(Maxmem) before mapping and validate the RSDP signature + Revision<=2 before installing it as the table root; on failure unmap and fall back to the firmware scan (fix.diff); apply the same validation in AcpiOsGetRootPointer (OsdEnvironment.c).", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "fix.diff (Maxmem bound + signature/revision validation + firmware-scan fallback) applied to the guest /usr/src copy, kernel rebuilt clean (358s, 0 errors) and installed. Patched-kernel runs: (a) invalid-RSDP + bsp_omit blob -> sdt_probe: kenv RSDP failed validation, falling back to firmware scan -> real firmware RSDT -> madt_lapic_probe: lapic_count=6 and the bsp_found panic is GONE (fix_run.log); (b) zeroed-hint boot that previously wedged pre-SMP now completes 6-CPU SMP bring-up. fix_patched_reproduced=false because end-to-end boot on the patched kernel still does not complete: the same unvalidated hint is separately consumed by AcpiOsGetRootPointer (OsdEnvironment.c:72) whose ACPICA walk faults (AcpiTbLoadNamespace+0x36) on the crafted chain or hangs on the zeroed hint - a residual in a different file that needs the same validation; the audited files own behavior is verified fixed.", "fix_evidence": "findings/poc/DF-5588/fix.diff (git apply --check -p0 passes); fix_run.log lines: sdt_probe: kenv RSDP failed validation, falling back to firmware scan, ACPI SDT: RSDP in BIOS mem, madt_lapic_probe: lapic_count=6, absence of panic: assertion arg.bsp_found; fix_build.log (build banner, 358.21 real, 0 errors); residual analysis VERDICT.md section 6" } |