# VERDICT — DF-5483

## Reproduced? YES — arbitrary firmware-chosen IO-port write by the stock
## kernel driver, unattended at boot; demonstrated as a boot-time machine
## poweroff (DoS)

### The chain, every link observed on the guest

1. **Table injection** (the proven madt-batch vector, DF-5430..5433):
   `hint.acpi.0.rsdp=0x40000000` in loader.conf +
   `-device loader,file=pstate_*.bin,addr=0x40000000,force-raw=on`. The
   planted chain (RSDP→RSDT→[FACP→FACS+DSDT, APIC]) is accepted by
   sdt_probe() with zero signature/checksum validation. The DSDT defines
   `\_SB.C000..C005`, each with `_PCT` (two SystemIO GAS buffers) and `_PSS`
   (one P-state, control value = cval). dmesg confirms adoption:
   `ACPI: DSDT 0x0000000040001000 ... (v02 TST DSDT ...)`.

2. **_PCT parse + port allocation, no identity validation**: 
   `acpi_pst_alloc_resource` (acpi_cpu_pstate.c:1495-1518) →
   `acpi_bus_alloc_gas` (acpi.c:1251-1293) → `bus_alloc_resource_any`
   reserves whatever port the firmware named (only "is it free in rman"
   is checked). bootverbose dmesg: `cpu_pst0: control reg 1 80` /
   `control reg 1 f9` / `control reg 1 100` across runs (SpaceId=1
   SystemIO + the named port).

3. **check_csr accepts any SystemIO GAS whose width arithmetic parses**:
   `acpi_pst_intel_check_csr` (acpi_pstate_machdep.c:430-485) verifies
   only pr_res != NULL and `acpi_pst_md_gas_verify` (:585-606) checks
   only BitOffset/width arithmetic. Nothing validates *which* port.

4. **The unattended boot transition**: `acpi_pst_postattach`
   (acpi_cpu_pstate.c:1206-1221) ends with
   `acpi_pst_global_set_pstate(acpi_pstate_start)` — executed at every
   boot with no user involvement → `acpi_pst_intel_set_pstate`
   (acpi_pstate_machdep.c:520-536) → `acpi_pst_md_res_write` (:631-653)
   → `bus_space_write_N(ctrl->pr_bt, ctrl->pr_bh, reg, pstate->st_cval)`.

### The three runs

| run | tables | outcome |
|-----|--------|---------|
| control (run.control_output.txt) | ctrl=port 0x80 (8-bit), status=0x81, cval=0xAA | boots; `px_dom0.available: 3000`, `px_dom0.select: 3000`, `px_global: 3000`; **uid 1001 user** reading `px_dom0.members` got `cpu0(3000)` — i.e. the kernel *read* port 0x81 for an unprivileged user and the sysctl output encodes the comparison (`(3000)` vs `(*)`) — a world-readable 1-bit-per-read oracle over a firmware-chosen port; root `sysctl -w hw.acpi.cpu.px_global=3000` returned 0 → the write re-fires on demand |
| **poweroff** (run.poweroff_console.log) | ctrl=port **0x604 (PM1a_CNT, 16-bit)**, cval=**0x2000 (SLP_EN | SLP_TYP=S5)** | `vm.sh up` → TIMEOUT, `vm.sh status` → down. Console ends at the postattach lines `cpu5: ioport allocation failed for control register` / `ACPI: domain1 P-State configuration check failed`; the surviving domain0's single member (cpu0, the one successful port-0x604 reservation) executes `outw(0x604, 0x2000)` microseconds later → QEMU ACPI S5 → machine off. No SeaBIOS banner follows; the machine is simply off. |
| nomd (run.nomd_control.log) | same control blob, no CPUID substitution | no P-state md → no transition, no px sysctls — differential control proving the write comes from this driver |

Also observed (noted, not filed — different file): the first blob iteration
accidentally encoded the port at GAS Address+4 (0x8000000000);
`acpi_bus_alloc_gas` happily *reserved* "IO port 0x8000000000" (rman accepts
64-bit IO starts) and the write would have truncated to port 0x0000 via the
16-bit out instructions — silent truncation of an out-of-range port address,
covered by this finding's fix.diff.

### Preconditions / threat model

Crafted ACPI tables reach the guest exactly as in the madt batch: host-level
QEMU flags (physical/host compromise — game over anyway), or anything that
can edit the guest bootloader environment (`/boot/loader.conf` kenv is root
on the guest). This is the standard firmware-ABI trust posture the audit
family files: the kernel treats DSDT/_PSS/_PCT as trusted TCB input while
the proven injection vector makes that input attacker-reachable. There is
**no unprivileged-user trigger for the write**; the read oracle
(`px_dom0.members`, CTLFLAG_RD) is unprivileged but only over a
firmware-chosen port.

### Impact ceiling

* Demonstrated: unattended boot-time arbitrary-port write with fully
  firmware-controlled 16/32-bit value → machine poweroff (DoS).
* Ceiling (not demonstrated): a single writable port per _PCT. Port 0xcf9
  (reset) and 0x92 (fast reset) are ignored by this QEMU machine model, but
  on real hardware both reset the box; the PCI config pair 0xcf8/0xcfc needs
  two writes to be useful (only ctrl is written; status is read-only), so
  one-shot PCI config corruption requires luck with the stale 0xcf8 value.
  Ports 0x60/0x64 (KBC) and 0x70/0x71 (CMOS/NMI) are usually rman-owned and
  therefore rejected. Realistic class: hardware pokes + reboot/poweroff DoS
  from crafted tables — the IO-space twin of DF-5432 (which reached
  arbitrary-physical MMIO and was filed Medium). IO ports cannot directly
  write kernel RAM, hence Low here.

### fix.diff

Authored (fix.diff): explicit 16-bit IO-space range validation of the _PCT
control/status GAS in `acpi_pst_intel_check_csr` — kills the silent
truncation class and the 0x8000000000 reservation. It does NOT (and cannot,
without a port whitelist upstream will not accept) prevent in-range ports
such as 0x604; the real systemic defense is validating ACPI table provenance
at sdt_probe (tracked by the madt-batch findings). Not rebuild-validated:
the demonstrated DoS is by-design reachability of an in-range port, so the
patch would not change the poweroff run; fix_status=not_testable.

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