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)
PoC verification
Evidence pack
findings/poc/DF-5623 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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():
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.
- 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.
- 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.
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 nocfid/cvid == fv->fid/fv->vidcheck 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 guestfix.diffβ bounded spins + ETIMEDOUT + convergence diagnosticenv.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_testablefix.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.
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.
No comments yet.