# DF-4905 — k8pnow_states() PST walk reads past the BIOS window with no bound

**File:** `sys/dev/powermng/powernow/powernow.c:174-198`
**Severity:** Low · **Bucket:** base:dev · **Confidence:** speculative (code certain; impact gated on BIOS/VM-firmware control)

## Root cause

The PSB scan walks the 0xe0000-0x100000 window in 16-byte steps
(powernow.c:174-176).  Once an `"AMDK7PNOW!"` signature is found, the
inner PST loop iterates `psb->n_pst` times (u8, up to 255) and advances

```c
p += sizeof(struct pst_s) + 2 * cstate->n_states;   /* powernow.c:196-197 */
```

**without any bounds check against `BIOS_START+BIOS_LEN`**.  Each step is
8 + 2*n_states ≤ 518 bytes; a corrupt or hostile PSB header (flash
corruption, VM firmware image, or a re-flashed BIOS) walks `p` up to
~132 KB past 0x100000.  `BIOS_PADDRTOVADDR(x) = (x) + KERNBASE`
(sys/platform/pc64/include/pc/bios.h:36), so the reads land in the
kernel's own physical load region (text/data) — mapped, so they do not
fault; they silently parse kernel memory as PST headers
(`pst->cpuid`/`fid`/`vid` comparisons at powernow.c:191-192).

Consequences:

* If the stray bytes happen to match `cpuid == cpusig && fid == maxfid
  && vid == maxvid`, `k8pnow_decode_pst()` is invoked on an arbitrary
  in-kernel pointer (powernow.c:193-194) — its `n_states` is then bounded
  only by DF-1961's missing check (heap OOB write up to 255 entries).
* Otherwise the reads stay internal (values are compared, not copied
  out) — no direct disclosure; this is an OOB-read robustness defect in
  the same BIOS-trust threat model as the known DF-1960/DF-1961 family.

The signature scan also has no early sanity check on `psb->n_pst` and no
PSB checksum validation (the PSB format defines none, but the walk bound
is still mandatory).

## Verification status

`untested` — reproducing requires controlling BIOS contents under an AMD
K8 CPU; the audit guest is Intel/QEMU and the module's CPUID gate
refuses (env.txt: `MOD_LOAD error 2`).

## Recommended fix

Clamp the walk to the BIOS window before every `pst` dereference and
before invoking the decoder — see `fix.diff`.
