β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4905

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4905 Β· 6 files
FileTypeDescriptionSize
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) β€” 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).

Clamp the walk to the BIOS window before every pst dereference and before invoking the decoder β€” see fix.diff.

VERDICT.md
↓ download raw

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_testable
baseline no→ patch + rebuild →patched clean

Fix 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
↓ fix.diffper-fix-DF-4905

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.