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

Unbounded FID/VID pending-bit spin and non-converging phase-2 ramp loop; success returned without completion verification

Summary

The pending-bit busy-wait has no bound, the phase-2 ramp loop terminates only if hardware actually moves, and the function returns 0 unconditionally: hardware that never completes (stuck bit, FID cap, consuming-but-ignoring VMM) wedges the cpuhelper CPU while the initiator blocks forever holding pd_serialize - system-wide hard hang driven unattended by powerd or root sysctl. Linux powernow-k8 bounds its wait and bails (reference design). Fault-preconditioned hardening; bounded-wait + ETIMEDOUT + convergence-verify fix compile-validated.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5623 Β· 13 files
FileTypeDescriptionSize
README.md β€” 4.1 KB ↓ raw
VERDICT.md β€” 3.2 KB ↓ raw
pstprobe.c β€” 3.1 KB view raw
Makefile β€” 55 B ↓ download
build.sh β€” 142 B view raw
run.sh β€” 109 B view raw
build.log β€” 5.6 KB view raw
run.log β€” 475 B view raw
env.txt β€” 1.3 KB view raw
fix.diff β€” 2.5 KB view raw
fixbuild.log β€” 2.0 KB view raw
manifest.json β€” 1.5 KB view raw
verdict.json β€” 3.8 KB view raw

DF-5623 β€” Unbounded FID/VID transition spin + no completion verification in the AMD-0F cpufreq shim

File: sys/platform/pc64/x86_64/cpufreq_machdep.c (pass 2, GLM 5.3) Class: CWE-835 loop with unreachable exit condition + CWE-754 improper check for unusual/exceptional conditions Severity: Info (hardening; hang preconditioned on misbehaving/non-converging K8 FID/VID hardware or a consuming-but-ignoring VMM; trigger actor is root or firmware β€” no unprivileged path) Status: static analysis certain on the loop structure; not triggerable on the audit guest (AMD-0F gate live-proven OFF β€” see DF-5622's pstprobe harness).

What the bug is

Two unbounded loops and a missing success check in amd0f_set_fidvid():

  1. AMD0F_WAIT_FIDVID_CHG (cpufreq_machdep.c:53-56):

c #define AMD0F_WAIT_FIDVID_CHG(status) \ do { \ (status) = rdmsr(AMD0F_MSR_FIDVID_STATUS); \ } while (AMD0F_STA_PENDING(status))

A busy-wait on the K8 FID/VID_STATUS pending bit (MSR 0xc0010042 bit 31) with no iteration/time bound. Used at 5 sites (cpufreq_machdep.c:102, 112, 138, 146, 157). If the voltage regulator / power management unit never completes the transition (stuck pending bit β€” failing hardware, or a VMM that consumes WRMSR(0xc0010041) but leaves STATUS untouched), the CPU spins forever at kernel priority.

  1. Phase-2 VCO ramp loop (cpufreq_machdep.c:126-142):

c while (abs(vco_fid - vco_cfid) > 2) { ... AMD0F_WRITE_FIDVID(val, cvid, ...); AMD0F_WAIT_FIDVID_CHG(status); cfid = AMD0F_STA_CFID(status); ... vco_cfid = AMD0F_FID2VCO(cfid); }

Termination assumes each write moves cfid toward fv->fid by 2. Hardware that rejects/ignores the FID write (max-FID cap, thermal limit, VMM without FID emulation) leaves cfid unchanged β†’ infinite loop.

  1. No completion verification: after phases 2-3 the function returns 0 unconditionally (cpufreq_machdep.c:161) without checking that cfid == fv->fid && cvid == fv->vid. A silently-ignored final write is reported to the caller (acpi_pst_amd0f_set_pstate, acpi_pstate_machdep.c:387) as success, so the P-state bookkeeping (dom->pd_state, sysctls, powerd) desynchronizes from hardware.

Execution context (why it is a hard wedge, not a thread hang)

Transitions are dispatched via cpuhelper onto the target CPU (acpi_pst_set_pstate, sys/dev/acpica/acpi_cpu_pstate.c:1452-1465) while the initiating CPU holds dom->pd_serialize and blocks on the cpuhelper reply (acpi_cpu_pstate.c:994-996, cpuhelper_domsg). A cpuhelper handler that never returns strands the helper CPU at kernel priority and the initiator forever β€” with hw.acpi.cpu.pstate.global (all domains) this wedges every CPU that owns a domain. powerd (root, default-enabled adaptive mode) triggers transitions continuously, so the hang fires unattended once the hardware misbehaves.

Reference: Linux powernow-k8 bounds its transition wait (transition_latency-scaled do...while with explicit error bail-out) and reports failures instead of looping/spinning indefinitely.

Reproduce the introspection evidence (this guest)

Same harness as DF-5622 (pstprobe.c): proves the amd0f code is compiled into the running GENERIC kernel and that its CPU gate is OFF on this guest (Intel vendor, APMI EDX=0, acpi_pst_md_probe()==NULL), so the hang path cannot be exercised here. On real K8 hardware the trigger is any P-state request while FID/VID transitions do not complete (failing VRM; or under a VMM that models MSR 0xc0010041 as writable-but-inert).

./build.sh && ./run.sh     # introspection (see run.log / build.log)

Fix

fix.diff (compile-validated against the guest's kernel headers β€” see fixbuild.log): - bound AMD0F_WAIT_FIDVID_CHG at 8e7 spins and return ETIMEDOUT at all 5 call sites when the pending bit never clears; - bound the phase-2 ramp loop at 64 iterations (return ETIMEDOUT); - kprintf a diagnostic when the final FID/VID did not converge before returning 0.

VERDICT.md
↓ download raw

DF-5623 VERDICT β€” unbounded FID/VID wait + no convergence check

Status: not_reproduced (on this guest) β€” loop-structure analysis certain; trigger hardware absent. Impact class if triggered: permanent kernel wedge (DoS) of the affected CPU(s); no memory-corruption primitive.

Reproduced?

No. Same hardware gate as DF-5622, live-proven with the same KLD harness (pstprobe.c, see ../DF-5622/run.log): the guest CPU is vendor 0x8086 (Intel), APMI(0x80000007) EDX=0, and acpi_pst_md_probe() returns NULL β€” the acpi_pst_amd0f ops table is never selected, so amd0f_set_fidvid cannot execute on this guest under any configuration reachable from the standard snapshot (QEMU/KVM does not implement the K8 FID/VID MSR pair 0xc0010041/0xc0010042). The function's entry rdmsr (cpufreq_machdep.c:78) would raise #GP here before reaching any loop β€” the #GP-on-CPUID-lying-VMM class is already filed as DF-5484 for the sibling file and is deliberately not re-filed here.

Why the finding is solid anyway (trace)

  • The two unbounded constructs are macros/loops in the file itself (cpufreq_machdep.c:53-56 and 126-142) β€” no input validation anywhere in the file bounds their duration; termination is purely a hardware property (pending-bit clear / cfid convergence).
  • Execution context makes the wedge system-wide: cpuhelper dispatch onto the target CPU (acpi_cpu_pstate.c:1452-1465) + initiator blocked in lwkt_serialize_enter/cpuhelper reply (acpi_cpu_pstate.c:994-996) β€” both CPUs are lost; the global sysctl / powerd walk every domain.
  • The unconditional return 0 (cpufreq_machdep.c:161) with no cfid/cvid == fv->fid/fv->vid check desynchronizes state bookkeeping on silently-ignored writes (CWE-754).

Threat model honesty: the trigger actor must be root (sysctl, kern_sysctl.c:1446 SYSCAP_NOSYSCTL_WR gate) or firmware/powerd acting automatically, and the fault condition belongs to the hardware/VMM. Hence Info (hardening), not a security-bars DoS. No uid0 chain exists for this class.

Fix validation

fix.diff authored post-verification (never applied to sys/): - git apply --check clean against the pristine tree; - both patched TUs compiled with the guest's kernel toolchain (cc -Werror -D_KERNEL ... -c cpufreq_machdep.c β†’ .ko relocatable) β€” see fixbuild.log (RC=0 for both DF-5622- and DF-5623-patched variants). Runtime A/B (fix_status) is not_testable: the baseline hang cannot be produced on this guest (hardware gate above), so a patched-kernel run has no baseline to compare against.

Artifacts

  • pstprobe.c, Makefile β€” introspection harness (shared with DF-5622)
  • build.log, run.log β€” harness build + live gate proof (copies)
  • fixbuild.log β€” compile validation of BOTH fix.diffs on the guest
  • fix.diff β€” bounded spins + ETIMEDOUT + convergence diagnostic
  • env.txt, manifest.json, verdict.json

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.diff (bounded spin macro + ETIMEDOUT at call sites + nloop-bounded phase-2 loop + convergence kprintf) is git-apply-clean and compiles warning-free with the guest's kernel toolchain (fixbuild.log RC=0). Runtime A/B not testable: the baseline hang requires non-converging AMD-0F FID/VID hardware absent from this guest.

fixbuild.log (cc -Werror ... -c cpufreq_machdep.c RC=0 for the DF-5623-patched variant); git apply --check APPLY_CHECK_OK; fix.diff hunks @ -53,4 +53,7 / -70-71 / -102/-112/-138/-146/-157 waits / -126 loop / -155-161 convergence.
↓ fix.diffper-fix-DF-5623

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: pstprobe gate lines (vendor/Apmi/probe-NULL/symbol addresses); build.log: harness -Werror build; fixbuild.log RC=0 for the patched TU; VERDICT.md traces both loops and the cpuhelper/serializer wedge mechanics with path:line.

PoC changes

No seed PoC existed. Reuses the DF-5622 pstprobe.c KLD introspection harness (safe: no MSR access) - on this guest the only honest evidence class is live reachability introspection, since exercising the loops requires AMD-0F hardware with non-converging FID/VID.

Verified recommended fix

Bound AMD0F_WAIT_FIDVID_CHG (bail out with ETIMEDOUT at all 5 call sites on stuck pending), bound the phase-2 ramp loop, and verify cfid/cvid convergence before returning 0.

Verdict

Loop-structure analysis certain, hang not triggerable on the audit guest. AMD0F_WAIT_FIDVID_CHG (cpufreq_machdep.c:53-56, used at :102/:112/:138/:146/:157) busy-waits on the FID/VID_STATUS pending bit with no bound, and the phase-2 VCO ramp loop (:126-142) terminates only if hardware actually moves cfid toward the target; success is returned unconditionally (:161) without verifying cfid/cvid reached fv. A stuck-pending or write-ignoring K8 (failing VRM, or a VMM consuming WRMSR 0xc0010041 but leaving STATUS inert) wedges the cpuhelper CPU at kernel priority while the initiator blocks forever holding dom->pd_serialize (acpi_cpu_pstate.c:994-996, :1452-1465) - a system-wide hard hang reachable unattended via powerd or the (root-only) pstate sysctls. Live KLD introspection (pstprobe.c, shared with DF-5622) proves the path is gated OFF on this guest (vendor Intel, APMI EDX=0, acpi_pst_md_probe()=NULL) though compiled into stock GENERIC, and the entry rdmsr (cpufreq_machdep.c:78) would #GP here first (DF-5484 class). Hardware/fault-preconditioned and root/firmware-initiated only -> Info hardening; no memory-corruption primitive.