k8pnow_states() PST walk advances past BIOS_START+BIOS_LEN with no bounds check (kernel OOB read of physical 0x100000+ region)
Summary
The BIOS-provided n_pst/n_states drive the pointer walk with no window bound - a corrupt PSB walks up to ~132KB past 0x100000 into the kernel load region and parses text/data as PST headers. Same BIOS-trust boundary as DF-1960/61. Bound-the-walk fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4905 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.1 KB | β raw | |
| VERDICT.md | β | 2.0 KB | β raw | |
| env.txt | β | 314 B | view raw | |
| fix.diff | β | 925 B | view raw | |
| manifest.json | β | 724 B | view raw | |
| verdict.json | β | 2.4 KB | view raw |
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
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) β itsn_statesis 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.
DF-4905 β VERDICT
status: untested Β· reproduced: 0 Β· impact: none (OOB read gated on firmware control) Β· confidence: speculative (code certain)
Narrative
k8pnow_states() (powernow.c:167-202) trusts two BIOS-provided counters
after finding the "AMDK7PNOW!" signature: psb->n_pst (u8) and, per
iteration, pst->n_states (u8). The advance
p += sizeof(struct pst_s) + 2 * cstate->n_states (powernow.c:196-197)
is performed n_pst times with no check that p stays below
BIOS_PADDRTOVADDR(BIOS_START + BIOS_LEN). Maximum overshoot:
255 * (8 + 510) β 132 KB past physical 0x100000, which via
BIOS_PADDRTOVADDR(x) = (x) + KERNBASE (bios.h:36) is the kernel's own
text/data β reads succeed silently. On a fortuitous match of
cpuid/fid/vid (powernow.c:191-192), k8pnow_decode_pst() receives an
interior kernel pointer and an attacker-influenced n_states whose only
bound is the already-known DF-1961 defect (heap OOB write).
Threat model equals the known DF-1960/DF-1961 family: requires a corrupted or attacker-reflashed BIOS (or hostile VM firmware presenting K8 CPUID) β not reachable from the OS itself. Hence Low/speculative.
Verification
Not runtime-testable: the audit guest is Intel/QEMU; kldload powernow
prints MOD_LOAD error 2 (CPUID gate, powernow.c:361-363) and the scan
never executes (env.txt). No harness can exercise BIOS memory content
from the OS side; analysis pack only.
Fix
fix.diff: two window checks β before dereferencing pst and before
calling the decoder β bail out (return 0) when the header or the
2*n_states state array would cross the window end. Applies cleanly.
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
not_testableFix validation not run (needs K8 + firmware control). fix.diff bounds the walk with two window checks (before the pst deref and before the decode call) and applies cleanly with patch(1).
fix.diff: checking file powernow.c / DF4905_APPLIES_CLEAN
Confirmed kernel references
Detail
Evidence (decisive lines)
README.md (walk arithmetic + window math), env.txt (gate refusal), VERDICT.md
PoC changes
Analysis-only pack; no harness (defect needs BIOS-memory control on K8 hardware to trigger).
Verified recommended fix
Before each pst dereference and before invoking k8pnow_decode_pst, verify p + sizeof(struct pst_s) [+ 2*n_states] stays within BIOS_PADDRTOVADDR(BIOS_START+BIOS_LEN); bail (return 0) otherwise.
Verdict
Code-level defect is certain: after matching an AMDK7PNOW! signature, the PST walk (powernow.c:187-198) advances p by sizeof(pst)+2*n_states for psb->n_pst iterations with no bounds check against BIOS_START+BIOS_LEN; a corrupt/hostile PSB walks up to ~132KB past 0x100000 into the kernel physical load region (BIOS_PADDRTOVADDR(x)=x+KERNBASE, bios.h:36) and parses kernel text/data as PST headers. If stray bytes match cpuid/fid/vid, k8pnow_decode_pst() is handed an in-kernel pointer whose n_states is bounded only by the known DF-1961 missing check. Direct impact requires BIOS/firmware control under an AMD K8 CPU (same trust boundary as the known DF-1960/1961 family); the audit guest is Intel/QEMU and the module gate refuses at CPUID (MOD_LOAD error 2, env.txt), so runtime verification was impossible.
No comments yet.