sdt_probe() accepts hint.acpi.0.rsdp with zero validation: total-acceptance injection vector for the crafted-ACPI family (DF-5430..5433/5583) and one-line boot brick
Summary
The kenv branch maps whatever 64-bit value the hint holds and consumes it as an RSDP with NO validation - not even the 'RSD PTR ' signature the scan path checks: a deadbeef-signature RSDP with wrong checksum was accepted and its RSDT->MADT chain consumed (lapic_count=3 on a 6-vCPU guest), the same invalid RSDP plus DF-5430's bsp_omit MADT panicked madt_lapic_pass2, and hint=0x800000000000 with no blob panics immediately at the rsdp->Revision read (reserved PTE bit -> vm_fault: fault on nofault entry) - one loader.conf line bricks the boot. The entire planted-ACPI family (DF-5430/5431/5432/5433/5583) hangs off this unvalidated if. Bound+signature+revision validation with firmware-scan fallback validated on a rebuilt kernel (falls back cleanly, lapic_count=6, bsp panic gone). Cross-file: OsdEnvironment.c:72 AcpiOsGetRootPointer is a second unvalidated consumer of the same hint (live fault at AcpiTbLoadNamespace+0x36). Boot-owner trust boundary.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5588 Β· 17 files| File | Type | Description | Size | |
|---|---|---|---|---|
| gen_rsdp_inject.py | β | 4.5 KB | view raw | |
| valid_control.bin | β | 768 B | β download | |
| nosig_control.bin | β | 768 B | β download | |
| nosig_bsp_omit.bin | β | 768 B | β download | |
| build.sh | β | 92 B | view raw | |
| run.sh | β | 326 B | view raw | |
| run.nosig_control.boot.log | β | 15.3 KB | view raw | |
| run.nosig_bsp_omit.boot.log | β | 7.7 KB | view raw | |
| run.bogus_addr.panic.log | β | 1.1 KB | view raw | |
| panic.txt | β | 436 B | view raw | |
| fix_run.log | β | 18.5 KB | view raw | |
| fix_build.log | β | 980 B | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| VERDICT.md | β | 7.9 KB | β raw | |
| env.txt | β | 963 B | view raw | |
| manifest.json | β | 1.7 KB | view raw | |
| verdict.json | β | 6.0 KB | view raw |
DF-5588 VERDICT β kenv RSDP acceptance in sdt_probe (pass 2)
Status: REPRODUCED (certain). Impact: panic (boot). File: sys/platform/pc64/acpica/acpi_sdt.c:71-77, deref at :109-115.
1. Root cause
sdt_probe() (SYSINIT SI_BOOT2_PRESMP SI_ORDER_FIRST, acpi_sdt.c:63-118)
has three RSDP discovery paths:
- kenv hint (acpi_sdt.c:71-77):
kgetenv_ulong("hint.acpi.0.rsdp")βpmap_mapdev(rsdp_paddr, PAGE_SIZE)βgoto found_rsdp. No validation of any kind β not the 8-byte "RSD PTR " signature, not the checksum, not Length, not Revision, and not the address itself. - EBDA scan (acpi_sdt.c:79-94) β signature checked (acpi_sdt.c:130).
- E0000-FFFFF scan (acpi_sdt.c:96-106) β signature checked.
At found_rsdp the mapped bytes are consumed wholesale
(acpi_sdt.c:109-115): rsdp->Revision picks RSDT-vs-XSDT and
RsdtPhysicalAddress/XsdtPhysicalAddress become the root of every
sdt_search() walk used by madt_probe (acpi_madt.c:96), fadt_probe
(acpi_fadt.c:95) and srat_probe (acpi_srat.c:75).
Pass-1 boundaries: DF-1114 filed the scanned RSDP (signature-only match
in sdt_rsdp_search, missing checksum/Length); DF-1113 filed the unbounded
table-Length KVA panic. Neither covers the kenv branch, which is a
separate acceptance that performs less validation than either (it skips
even the signature match DF-1114's path has). This finding files that gap
and demonstrates it live.
2. Why the pmap path faults on a bad address (R4)
pmap_mapdev_attr() (sys/platform/pc64/x86_64/pmap.c:6192-6219) allocates
KVA with kmem_alloc_nofault (pmap.c:6201) and writes raw pa into PTEs
(pmap.c:6208). With hint.acpi.0.rsdp=0x800000000000 on the qemu64 CPU
model (physical address bits = 40), PTE bits β₯ 40 are reserved β the first
read of the mapping (rsdp->Revision, acpi_sdt.c:109) takes a kernel #PF
on a nofault-entry KVA β panic: vm_fault: fault on nofault entry
(observed in run.bogus_addr.panic.log, immediately after the bootverbose
line "ACPI SDT: RSDP in kenv hint").
3. R1/R2 β acceptance is total (evidence)
- R1 baseline (valid "RSD PTR " + control MADT: BSP+2 APs+IOAPIC):
ACPI SDT: RSDP in kenv hint,madt_lapic_probe: lapic_count=3, IOAPIC configured from the crafted table. - R2 (signature =
de ad be ef de ad be ef, v1 checksum = 0xAA, same chain): identical βlapic_count=3, crafted IOAPIC consumed (run.nosig_control.boot.log). The 8 signature bytes and the checksum are never consulted on this path.
Guest has 6 vCPUs; a 3-CPU enumeration proves the crafted MADT (not the firmware one) was consumed.
4. R3 β the family keystone (evidence)
Same invalid-signature RSDP + DF-5430's bsp_omit MADT β
panic: assertion "arg.bsp_found" failed in madt_lapic_pass2
(run.nosig_bsp_omit.boot.log, panic.txt). I.e. every crafted-table bug in
DF-5430..5433 and DF-5583 is reachable through an RSDP that fails every
ACPI validity check β this if is the keystone of the planted-ACPI rig.
5. Threat model (why Medium, not higher)
The hint is set from /boot/loader.conf / the loader prompt β a boot-owner
(equivalent to root/boot-disk write or console access). No unprivileged
in-guest path reaches it (sdt_probe runs at SI_BOOT2_PRESMP; runtime kenv
cannot affect it). Consistent with DF-1113 (Medium, same trust boundary):
the direct impact is a deterministic boot brick / crafted-table injection
from one line, plus the enabling role for the whole family.
6. Fix validation + the cross-file residual
fix.diff (validated git apply --check -p0 against the read-only sys/
tree): bound rsdp_paddr < ptoa(Maxmem) before mapping (Maxmem is set in
getmemsize() during init386, machdep.c:2284/2899, i.e. before any
SYSINIT) and require signature + Revision <= 2 before accepting; on
failure unmap and fall through to the firmware scan.
Patched-kernel build: clean, 358 s, 0 errors (fix_build.log excerpt;
/usr/src copy, make -j8 nativekernel KERNCONF=X86_64_GENERIC).
Patched-kernel runs:
* invalid-RSDP + bsp_omit blob β sdt_probe: kenv RSDP failed validation,
falling back to firmware scan β RSDP not in EBDA β RSDP in BIOS mem
β madt_lapic_probe: lapic_count=6 (real firmware MADT) β the
bsp_found panic is GONE (fix_run.log).
* hint β zeroed RAM (the "typo" case): previously the zero RSDP was
accepted, RSDT paddr == 0 printed, and the boot wedged before SMP; on
the patched kernel it is rejected and full 6-CPU SMP bring-up proceeds.
Residual (separate file, do not fix here): AcpiOsGetRootPointer()
(sys/platform/pc64/acpica/OsdEnvironment.c:66-81) hands the same
unvalidated hint to ACPICA. Observed live: (a) patched kernel, crafted
chain β ACPICA consumed it and faulted at AcpiTbLoadNamespace+0x36
(fix_run.log tail); (b) patched kernel, zeroed hint β ACPICA table walk
hangs late in boot (serial log frozen after "ACPI: S? 0x0 ... F000FF53").
The complete remediation needs the same validation in that acceptance (or
one shared validator). This matches the orchestrator's existing
"acpi.c leads: unvalidated RSDP kenv" item; R4-style out-of-RAM hints would
also fault inside ACPICA's mapping until that sibling is patched, which is
why fix_patched_reproduced=false is reported for end-to-end boot while the
audited file's own behavior (R3 scenario) is verified fixed.
7. Negative results (classes checked and killed in this file)
- sdt_rsdp_search (acpi_sdt.c:120-135): scan bound
i < size - 36keeps the 36-byte struct inside the mapped window; 16-byte step is spec- compliant; signature checked. (Checksum gap = DF-1114, not re-filed.) - Walk array bounds (acpi_sdt.c:194-214, 255-274): entry sizes correct
(XSDT u64 / RSDT u32 via sizeof);
nent = (Length-36)/entrycannot overflow int (max 1073741814); entries at byte 36+8i/36+4i always lie inside the Length-sized mapping β no OOB relative to the mapping. - Mapping lifecycle (acpi_sdt.c:137-157): map/unmap size pairs are
consistent (same Length re-read from a still-mapped, boot-time-static
table); header probe map/unmap of 36 bytes matches pmap_unmapdev's
rounding; children mapped/unmapped one at a time; no refcounts to leak;
all not-found paths unmap.
sdt_sdth_maprejects Length<36, and the walk only returns tables it successfully mapped, so consumerKKASSERT(madt != NULL)-style asserts cannot fire. - Consumers all guard
sdt_search()==0(acpi_madt.c:97, acpi_fadt.c:96, acpi_srat.c:76-78) β no accidental page-0 mapping. ebda_addr(machdep.c:2275-2276, BIOS BDA word<<4) is bounded <1 MB; DF-1114-class firmware input only.- The
AcpiOsTableOverrideDSDT replacement (sys/dev/acpica/Osd/OsdTable.c:63-82) is loader-blob-fed, different file β cross-referenced, not filed here. - Bonus observation (contrib ACPICA, out of tree scope): a valid-RSDT chain
with no FADT/DSDT makes the stock kernel fault at
AcpiTbLoadNamespace+0x36(cmpl $0x54445344,0x14(%rbx)) during acpi0 attach β see run.valid/βnosig boot logs; reportable against the acpica-family lead, not acpi_sdt.c.
8. Reproduce summary
./build.sh 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=$PWD/nosig_bsp_omit.bin,addr=0x40000000,force-raw=on" vm.sh up 150 vm.sh log # -> panic: assertion "arg.bsp_found" ... (through an INVALID-signature RSDP) # R4: loader.conf hint.acpi.0.rsdp=0x800000000000, boot with no blob: # -> panic: vm_fault: fault on nofault entry
Guest left clean: vm.sh reset with-src after all runs (verified: Jul 2
snapshot kernel active, no hint in loader.conf, real-firmware lapic_count=6).
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
fixedfix.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 file's own behavior is verified fixed.
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
Confirmed kernel references
- 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
Detail
Evidence (decisive lines)
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
PoC changes
No seed PoC. gen_rsdp_inject.py authored from scratch (DF-5430's 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.
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).
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-5430's 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 qemu64's 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 RSDP's missing checksum/Length; DF-1113 the unbounded table Length; the kenv branch is a separate acceptance with less validation than either.
No comments yet.