# 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:

1. **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.
2. EBDA scan (acpi_sdt.c:79-94) — signature checked (acpi_sdt.c:130).
3. 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 - 36` keeps
  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)/entry` cannot
  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_map` rejects Length<36, and the
  walk only returns tables it successfully mapped, so consumer
  `KKASSERT(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 `AcpiOsTableOverride` DSDT 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.
