# DF-5668 VERDICT — AcpiOsGetRootPointer(): second unvalidated kenv RSDP consumer (pass 2)

**Status: REPRODUCED (certain). Impact: panic (deterministic boot-time
kernel OOB read / boot brick; boot-owner trust boundary → Medium). File:
sys/platform/pc64/acpica/OsdEnvironment.c:59-81, acceptance at :72-73.**

## 1. Root cause

`AcpiOsGetRootPointer()` (OsdEnvironment.c:59-81) has two RSDP discovery
paths:

1. **kenv hint** (:72-73): `kgetenv_ulong("hint.acpi.0.rsdp")` → cached in
   `acpi_root_phys` and returned. **No validation of any kind** — not the
   address, not the `"RSD PTR "` signature, not the checksum, not Revision.
2. Firmware scan (:75): `AcpiFindRootPointer` → every candidate passes
   `AcpiTbValidateRsdp` (tbxfroot.c:211-243: signature + v1 checksum +
   extended checksum for rev ≥ 2).

The validated path and the unvalidated path live in the same function; the
kenv branch skips everything the scan branch enforces.

This is the **second, independent acceptance** of the same loader hint.
The first (native walk, `acpi_sdt.c:71`) is DF-5588; that fix does not
cure this one — observed live in DF-5588's own fix validation
(fix_run.log tail: fault at `AcpiTbLoadNamespace+0x36` on the crafted
chain; late-boot freeze on the zeroed hint) and re-proven here on the
stock kernel.

## 2. Who consumes the unvalidated pointer, and what trusts it

* `AcpiInitializeTables` (tbxface.c:257) → `AcpiTbParseRootTable`
  (tbutils.c:376): maps the RSDP (:392), reads `Rsdp->Revision`
  (:410) and `XsdtPhysicalAddress`/`RsdtPhysicalAddress` (:416/:423)
  directly. `AcpiTbValidateRsdp` is **never called on this path** — stock
  ACPICA trusts the host-OS-supplied root pointer. The RSDT/XSDT gets a
  length+checksum check (tbutils.c:460-466) but no signature check.
* `acpi_probe` (acpi.c:462-478): reads the cached pointer, maps the RSDP,
  derefs `Revision`/`XsdtPhysicalAddress`, maps the RSDT header — no
  checks at all. Its device description (`acpi0: <DFBSD >`) is printed
  straight from the crafted RSDT's OEM id — a visible acceptance marker.
* `acpi_attach` → `AcpiLoadTables` (acpi.c:553) → `AcpiTbLoadNamespace`
  (tbxfload.c:207, body :255+): line 275 computes
  `&AcpiGbl_RootTableList.Tables[AcpiGbl_DsdtIndex]` and line 279-280
  derefs `Table->Signature` against `ACPI_SIG_DSDT` **before any FADT
  presence check**. With a FADT-less chain, `AcpiGbl_DsdtIndex` is still
  `ACPI_INVALID_TABLE_INDEX` = 0xFFFFFFFF (acglobal.h:171; only
  `AcpiTbParseFadt`, tbfadt.c:490, ever sets it) →
  `Tables[0xFFFFFFFF].Signature` → OOB read ~200 GB past the array →
  kernel page fault. In-kernel consequence: ACPICA additionally *executes*
  crafted DSDT/SSDT AML in supervisor mode (boot-owner-equivalent
  primitive; AML can only be stopped by not feeding it invalid roots).

## 3. Reproduction (stock kernel, DragonFly 6.5-DEVELOPMENT #0 Jul 2 2026)

Proven planted-ACPI rig (DF-5430..5433/5583/5588): loader.conf
`hint.acpi.0.rsdp=0x40000000` + QEMU `-device loader,file=blob,
addr=0x40000000,force-raw=on`. The planted MADT is the family control
MADT (BSP+2 APs+real IOAPIC), so the native walk survives and boot
reaches acpi0 attach where ACPICA's own walk of the SAME hint faults.

* **S1 — invalid-signature chain** (`nosig_fadtless.bin`: signature
  `de ad be ef de ad be ef`, wrong v1 checksum 0xAA, correct-checksummed
  RSDT→MADT, no FADT):
  `ACPI SDT: RSDP in kenv hint` → `madt_lapic_probe: lapic_count=3` →
  `acpi0: <DFBSD >` (acpi_probe consumed crafted RSDT) →
  **`Fatal trap 12: page fault while in kernel mode … Stopped at
  AcpiTbLoadNamespace+0x36: cmpl $0x54445344,0x14(%rbx)`**
  (0x54445344 = 'DSDT'; fault VA 0xfffff8208d6e27f4, supervisor read,
  page not present) — run.S1_nosig.boot.log, panic.txt. Identical to the
  fault DF-5588 observed on their patched kernel (their fix_run.log):
  the two acceptances are independent and this one needs its own fix.
* **S2 — FULLY valid chain** (`valid_fadtless.bin`: genuine
  `"RSD PTR "`, correct v1 checksum, correct RSDT/MADT checksums,
  still FADT-less): **identical fatal trap** (run.S2_validfadtless.boot.log).
  Consequence: RSDP-level validation alone cannot stop a deliberate
  boot-owner; the `AcpiGbl_DsdtIndex` guard in ACPICA is needed as well.
* **S3 — zeroed hint** (hint → 0x40000000, nothing planted): on the
  stock kernel the *native* acceptance wedges first, pre-SMP
  (`sdt_search_rsdt: RSDT paddr == 0`, serial frozen —
  run.S3_zeroed_wedge.boot.log). On a DF-5588-patched kernel the same
  config drives ACPICA's walk of the zeroed page (DF-5588 fix_run.log
  tail: late-boot freeze) — cured end-to-end in F3 below.

Attempts: 6 distinct boots (S1 twice — first run had a builder byte-shift
bug in the blob, fixed; S2; accidental S3; F1 twice — v1 of the fix only
degraded gracefully, v2 adds the firmware fallback; F3).

## 4. Why Medium (not higher)

The hint is set from `/boot/loader.conf` / the loader prompt — a
boot-owner (equivalent to root / boot-disk write / console). No
unprivileged in-guest path reaches it: `AcpiOsGetRootPointer` runs only
during cold boot (`acpi_Startup`/`acpi_probe`) and caches `acpi_root_phys`;
runtime kenv writes cannot influence it, and after boot it is never
consulted again. Direct impact: one loader.conf line → deterministic boot
brick (fatal trap / pre-SMP wedge), plus full control of ACPICA's table
namespace (arbitrary in-kernel AML execution) for the same boot-owner.
Consistent with the family precedent (DF-5588 Medium, DF-1113 Medium,
DF-1114 Low).

## 5. Fix validation

`fix.diff` (git apply --check -p0 clean against the read-only sys/ tree):

(a) **OsdEnvironment.c** — `acpi_validate_rsdp_paddr()`: reject the hint
unless `0 < paddr < ptoa(Maxmem)` (an out-of-range address faults through
`pmap_mapdev`'s reserved-bit PTEs the moment it is read — DF-5588's R4
mechanism, which applies to `AcpiOsMapMemory` at OsdMemory.c:85 just the
same) and the mapped bytes carry `"RSD PTR "`, `Revision <= 2`, a zero
v1 checksum and (rev ≥ 2) a zero extended checksum — exactly
`AcpiTbValidateRsdp`'s rules. On failure: log
`AcpiOsGetRootPointer: kenv RSDP failed validation, falling back to
firmware scan` and run `AcpiFindRootPointer` in **both** the no-hint and
invalid-hint cases (fix v1 fell back only on absent hint — rebuilt).
(b) **tbxfload.c** — guard `AcpiGbl_DsdtIndex == ACPI_INVALID_TABLE_INDEX`
→ `AE_NO_ACPI_TABLES` before indexing `Tables[]` (upstream-ACPICA
hardening: any fully-valid FADT-less chain — crafted or broken firmware —
otherwise OOB-reads; note `AcpiLoadTables` swallows the failure at
tbxfload.c:213, so the visible symptom is a graceful
`acpi0: Could not enable ACPI: AE_NO_ACPI_TABLES`).

Patched builds: `make -j8 nativekernel KERNCONF=X86_64_GENERIC` clean,
0 errors (fix_build.log; F3 build 18:23 UTC 2026).

Patched-kernel runs (exact S-scenario reruns):

* **F1** (invalid-sig chain): `AcpiOsGetRootPointer: kenv RSDP failed
  validation, falling back to firmware scan` → firmware RSDP 0xF59A0
  (located independently via the QEMU monitor; sig+checksum valid) →
  `acpi0: <BOCHS BXPC> [attached!]`, `machdep.acpi_root: 1005984`
  (= 0xF59A0), boot completes, **no panic** (fixrun.F1_nosig.boot.log).
* **F2** (fully-valid chain): RSDP validation passes (chain is genuinely
  valid), ACPICA consumes it, the tbxfload guard fires → `acpi0: Could
  not enable ACPI: AE_NO_ACPI_TABLES` → graceful attach failure, boot
  completes, **no panic** (fixrun.F2_validfadtless.boot.log).
* **F3** (fix.diff + DF-5588's validated sdt fix; zeroed hint, no blob):
  `sdt_probe: kenv RSDP failed validation…` **and**
  `AcpiOsGetRootPointer: kenv RSDP failed validation…` → firmware tables
  everywhere → `madt_lapic_probe: lapic_count=6`, `hw.ncpu=6`,
  `acpi0: <BOCHS BXPC> [attached!]` — the stock pre-SMP wedge
  (run.S3_zeroed_wedge.boot.log) is **gone end-to-end**
  (fixrun.F3_zeroedhint.boot.log).

Behavior change worth noting: a present-but-invalid hint (including a
typo or a hint of `0`) previously either crashed, wedged, or silently
poisoned ACPICA; it now degrades to the firmware tables — the same
state as having no hint at all.

## 6. Negative results (file-level, pass 2)

* `AcpiOsInitialize`/`AcpiOsTerminate` (OsdEnvironment.c:47-57): inert
  AE_OK stubs; callers in ACPICA init/shutdown — no state, no issue.
* `machdep.acpi_root` SYSCTL_ULONG CTLFLAG_RD (:44): world-readable
  exposure of the RSDP *physical address* only (firmware table location,
  not kernel memory) — negligible; not filed.
* `acpi_root_phys` cache: written only during cold boot (single-threaded
  identify/probe context), read-only afterwards; no runtime mutation
  path, no concurrency issue. The value-0 hint corner (cache never
  populated → ACPICA silently disabled, masking the firmware fallback)
  is subsumed by the main finding's fix.
* No copyin/copyout/uiomove, no ioctls, no locks, no length arithmetic
  in this file; u_long == ACPI_PHYSICAL_ADDRESS width on pc64 (no
  truncation).
* `hint.acpi.0.rsdp` consumers repo-wide: exactly two (acpi_sdt.c:71,
  OsdEnvironment.c:72 — both now covered by validated fixes).

## 7. Post-run state

Guest reset to the clean-source snapshot after validation
(fix kernels discarded); `sys/` never modified (fix.diff authored via
mechanical diff of pristine vs patched copies, `git apply --check` only).

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