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

Unsigned underflow in Phase-1 voltage ramp writes malformed MSR 0xc0010041 (reserved bits 13-31 garbage, wrong VID)

Summary

uint32_t val holds cvid-(1<<mvs) int arithmetic - when negative it wraps and the (val>0) clamp is dead code for unsigned types; WRITE_FIDVID shifts the garbage into the MSR (reserved bits set, VID=0x1d instead of 0) - #GP panic or misprogrammed voltage control. Harness-verified arithmetic. Signed-val one-line fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4904 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 1.9 KB ↓ raw
sim_vidunderflow.c β€” 1.9 KB view raw
build.sh β€” 106 B view raw
run.sh β€” 99 B view raw
build.log β€” 52 B view raw
run.log β€” 340 B view raw
env.txt β€” 314 B view raw
fix.diff β€” 660 B view raw
manifest.json β€” 914 B view raw
verdict.json β€” 2.2 KB view raw

DF-4904 β€” Unsigned underflow in Phase-1 voltage ramp writes malformed MSR 0xc0010041

File: sys/dev/powermng/powernow/powernow.c:231,260-262 Severity: Low Β· Bucket: base:dev Β· Confidence: likely (type bug certain; hardware impact conditional)

Root cause

uint32_t val;                                    /* powernow.c:231 */
while (cvid > vid) {                             /* powernow.c:260 */
        val = cvid - (1 << cstate->mvs);         /* powernow.c:261 */
        WRITE_FIDVID(cfid, (val > 0) ? val : 0, 1ULL);  /* powernow.c:262 */

val is uint32_t; cvid - (1 << mvs) is computed in int and goes negative whenever the current VID is smaller than one MVS step (e.g. cvid=1, mvs=2 β‡’ βˆ’3). Assignment wraps to 0xFFFFFFFD, so the intended clamp (val > 0) ? val : 0 can never fire (unsigned values are always β‰₯ 0, and 0xFFFFFFFD > 0 is true). WRITE_FIDVID (powernow.c:76-78) then shifts that word << 8 into MSR MSR_AMDK7_FIDVID_CTL (0xc0010041): bits 13-31 (reserved + control fields) receive garbage and the programmed VID is 0x1d instead of 0.

On K8 silicon, writing reserved bits of FID_VID_CTL either #GPs (kernel panic) or misprograms the voltage-change control logic. The path runs whenever a downward voltage transition begins with cvid < (1 << mvs) β€” reachable at module load via the DF-4903 route (powernow_init:392 β†’ setperf drives vid toward 0) or whenever root writes a lower target to machdep.powernow.frequency.target (powernow.c:323-335; sysctl writes are privilege-gated, kern_sysctl.c:1446).

Harness (logic-level, run on guest as uid=1001 β€” run.log)

cvid=1 vid=0 mvs=2  ->  cvid-(1<<mvs) = -3
stored in uint32_t val     = 0xfffffffd
(val > 0) as evaluated     = TRUE   <-- underflow slips through
WRMSR(0xc0010041) value    = 0x000000fffffffd0a
  VID field (bits 8-12)    = 0x1d
  bits 13-31 (reserved)    = 0x7ffff  <-- garbage written

Verification status

untested at kernel level: the MSR write requires AMD K8 (guest is Intel/QEMU; module gate refuses, MOD_LOAD error 2 β€” env.txt).

Do the arithmetic in signed and clamp before the unsigned narrowing β€” val = cvid - (1 << mvs); WRITE_FIDVID(cfid, (val >= 0 && cvid > (1 << mvs)) ? ... β€” see fix.diff (makes val int).

Build / run

cc -Wall -O2 -o sim_vidunderflow sim_vidunderflow.c && ./sim_vidunderflow
VERDICT.md
↓ download raw

DF-4904 β€” VERDICT

status: untested Β· reproduced: 0 Β· impact: dos (malformed MSR write / #GP panic on K8) Β· confidence: likely

Narrative

k8_powernow_setperf() Phase 1 lowers the core voltage in steps of 1 << cstate->mvs (powernow.c:260-266). val is declared uint32_t (powernow.c:231) but cvid - (1 << cstate->mvs) is int arithmetic: when the current VID is below one step (cvid=1, mvs=2 β‡’ βˆ’3), the value wraps to 0xFFFFFFFD on assignment. The clamp (val > 0) ? val : 0 (powernow.c:262) is dead code for unsigned types β€” 0xFFFFFFFD > 0 β€” so WRITE_FIDVID (powernow.c:76-78) composes (1ULL<<32)|(1<<16)|(0xFFFFFFFD<<8)|cfid = 0x000000fffffffd0a and writes it to MSR 0xc0010041: VID field gets 0x1d instead of 0, and bits 13-31 (reserved/control) get pure garbage.

Effect on K8 silicon: #GP on wrmsr (kernel panic at CPL 0) or misprogrammed FID/VID transition control. Reachable (a) at module load through the DF-4903 route (powernow_init:392 drives vid→0), or (b) when root writes a lower frequency to machdep.powernow.frequency.target (writes are privilege-gated, kern_sysctl.c:1446).

Verification

Kernel-level: impossible on this guest β€” QEMU/Intel CPU, module gate at powernow.c:358-363 refuses (MOD_LOAD error 2, env.txt). Logic-level: sim_vidunderflow.c run as uid=1001 reproduces the exact arithmetic and prints the malformed MSR word (run.log). Not counted as reproduction.

Fix

int32_t val makes the existing clamp effective (fix.diff, applies clean). Fix build/validation not run (needs K8).

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: path needs AMD K8 hardware. fix.diff (make val int32_t so the existing clamp works) applies cleanly with patch(1).

fix.diff: checking file powernow.c / DF4904_APPLIES_CLEAN
↓ fix.diffper-fix-DF-4904

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (uid=1001 harness output showing underflow 0xfffffffd slipping through (val>0) and the malformed MSR word), sim_vidunderflow.c, VERDICT.md

PoC changes

harness authored fresh; WRITE_FIDVID macro replicated exactly from powernow.c:76-78

Verified recommended fix

Declare val as int32_t (signed) so the existing (val > 0) ? val : 0 clamp catches the underflow.

Verdict

Type-level bug is certain: val is uint32_t (powernow.c:231), so cvid - (1<<mvs) underflows to ~4e9 whenever cvid < (1< 0) clamp at powernow.c:262 can never catch it; WRITE_FIDVID shifts the garbage into reserved bits 13-31 of MSR 0xc0010041 (harness in run.log shows the composed 0x000000fffffffd0a word). On AMD K8 silicon this either raises #GP (kernel panic) or misprograms the voltage-transition control logic. Kernel reproduction impossible on the Intel/QEMU audit guest (module CPUID gate refuses, MOD_LOAD error 2).