Reachable KKASSERT panic on firmware-supplied _PSS control values: endpoint P-states are never validated and vid>=rvo is never checked before amd0f_set_fidvid asserts
Summary
The attach validator only kprintf's endpoint entries (never rejects) and its RVO check is vacuous (mvs==0 hard-enforced), so a _PSS endpoint with vid<rvo hits KKASSERT(fv->vid >= xsit->rvo) - a live panic() on the stock INVARIANTS GENERIC kernel, firing unattended at the first powerd transition or root sysctl select. Firmware/hypervisor-input boot-time DoS on AMD K8 0F silicon; architecturally impossible on the audit rig (live-proven: Intel vendor, APMI EDX=0, amd0f probe NULL). EINVAL-instead-of-assert + endpoint-envelope-reject fix authored.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5622 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 5.6 KB | β raw | |
| VERDICT.md | β | 5.1 KB | β raw | |
| pstprobe.c | β | 3.1 KB | view raw | |
| Makefile | β | 55 B | β download | |
| build.sh | β | 155 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 | β | 697 B | view raw | |
| fixbuild.log | β | 2.0 KB | view raw | |
| manifest.json | β | 1.6 KB | view raw | |
| verdict.json | β | 4.4 KB | view raw |
DF-5622 β Reachable KKASSERT panic on firmware-supplied _PSS control values (AMD-0F FID/VID shim)
File: sys/platform/pc64/x86_64/cpufreq_machdep.c (pass 2, GLM 5.3)
Class: CWE-617 reachable assertion + CWE-20 improper input validation
Severity: Low (firmware-preconditioned local panic; no unprivileged trigger path)
Status: static trace certain; not reproducible on the audit guest (requires AMD
family-0F CPU with APMI FID/VID bits; guest is Intel-vendor under KVM β live-proven
below).
What the bug is
amd0f_set_fidvid() (cpufreq_machdep.c:67) programs K8 FID/VID MSRs from a
struct amd0f_fidvid/struct amd0f_xsit pair built entirely from the ACPI _PSS
package control value (st_cval) by acpi_pst_amd0f_set_pstate()
(sys/platform/pc64/acpica/acpi_pstate_machdep.c:370-388).
The attach-time validator acpi_pst_amd0f_check_pstates()
(acpi_pstate_machdep.c:288-368) validates FID/VID ranges only for the middle
entries (i = 1..npstates-2, lines 332-347, EINVAL only when
hw.acpi.cpu.pstate.strigent_check is on). The first (max) and last (min)
entries are checked with kprintf only β never rejected (lines 310-331), and
no entry anywhere is checked for vid >= rvo, even though
amd0f_set_fidvid() asserts exactly that:
sys/platform/pc64/x86_64/cpufreq_machdep.c:91-95
if ((fv->fid & ~0x1) > (cfid & ~0x1) || cvid > fv->vid) {
KKASSERT(fv->vid >= xsit->rvo); /* <-- panics */
} else {
KKASSERT(cvid >= xsit->rvo); /* <-- panics */
}
KKASSERT is compiled into the stock GENERIC kernel: options INVARIANTS
is present in sys/config/X86_64_GENERIC:56 (verified live in the guest at
/usr/src/sys/config/X86_64_GENERIC), and sys/sys/systm.h:98-101 defines
KKASSERT as a panic() under INVARIANTS.
rvo comes from AMD0F_PST_CTL_RVO(st_cval) (2 bits, 0..3,
acpi_pstate_machdep.c:381) and passes validation for any value: the only
check is rvo_mv % mvs_mv != 0 (acpi_pstate_machdep.c:361) which is always
false when mvs == 0 (mvs==0 is hard-enforced at acpi_pstate_machdep.c:353).
Trigger (preconditions)
- CPU: AMD family 0F (K8) with APMI FID/VID bits β
acpi_pst_amd_probe()returns theacpi_pst_amd0fops table (acpi_pstate_machdep.c:173-175). - Firmware supplies a
_PSSwhose entry 0 (or last entry) hasctl.VID < ctl.RVO(e.g. vid=0, rvo=3) β unchecked per above. - Any transition to that state: first P-state transition at attach (via
acpi_pst_postattach),powerdat boot (adaptive mode), or root writinghw.acpi.cpu.pstate.domainX.select/hw.acpi.cpu.pstate.global(root-only per sys/kern/kern_sysctl.c:1446SYSCAP_NOSYSCTL_WR).
Both assert branches are reachable: the then branch fires whenever the
target state's vid < rvo on a frequency increase (or when current vid is
numerically above target); the else branch fires on a decrease when the
hardware's current cvid (K8s typically boot at max voltage, i.e. low cvid
value) is below rvo.
Result: panic("assertion \"fv->vid >= xsit->rvo\" failed in amd0f_set_fidvid ...")
on the stock kernel, unattended at boot with default powerd.
Why the audit guest cannot exercise it (live-proven)
The guest is QEMU Virtual CPU version 2.5+, vendor GenuineIntel
(family 0xf/model 0x6b), KVM. acpi_pst_md_probe() gates on vendor first
(acpi_pstate_machdep.c:150-153 β acpi_pst_intel_probe(), no EST bit β NULL).
The KLD harness pstprobe.c in this pack calls the real kernel function
acpi_pst_md_probe() (global symbol) and dumps the gates:
pstprobe: cpu_vendor_id=0x8086 (AMD=0x1022 INTEL=0x8086) family=0xf model=0x6b pstprobe: APMI(0x80000007) edx=0 (amd0f needs (edx & 0x06) == 0x06) pstprobe: acpi_pst_md_probe() = NULL: no P-state machdep; acpi_pst_amd0f ops NEVER selected -> amd0f_set_fidvid UNREACHABLE on this CPU pstprobe: symbols in running kernel: amd0f_set_fidvid=0xffffffff80bda6c0 amd0f_get_fidvid=0xffffffff80bdaac0 amd0f_fidvid_limit=0xffffffff80bdab00
i.e. the amd0f helpers are compiled into the running GENERIC kernel
(alive code β this file is NOT part of the dead #ifdef notyet legacy CPU
attachment, sys/bus/pci/x86_64/legacy.c:233-347), but their execution requires
AMD-0F hardware that this guest cannot present (QEMU/KVM does not implement
the K8 FID/VID MSR pair 0xc0010041/0xc0010042; the harness deliberately never
calls the amd0f_* functions β their first instruction is rdmsr(0xc0010042)
which raises #GP on this CPU; that #GP class is already filed as DF-5484 for
the sibling file).
The trigger itself, on suitable hardware/a suitably configured VMM, is a
crafted SSDT _PSS (e.g. Name (_PSS, Package (0x02) { Package (0x06) { 2000, 89000, 10, 10, 0x40c00a00, 0x40c00a00 }, ... })
β ctl encoding IRD=1|IRT=0|RVO=3|PLLTIME=1|MVS=0|VST=10|VID=0|FID=40)
passed with -acpitable/-bios or flashed firmware.
Reproduce the introspection evidence (this guest)
./build.sh # builds pstprobe.ko against /usr/src + /usr/obj kernel (bsd.kmod.mk) ./run.sh # kldload + dmesg (see run.log / build.log for captured output)
Fix
Return an error instead of asserting on firmware data (see fix.diff):
validate vid >= rvo (and the cvid >= rvo downgrade case) with return
EINVAL, and harden the attach-time validator in
sys/platform/pc64/acpica/acpi_pstate_machdep.c to reject endpoint entries
whose FID/VID are out of the MSR-derived [fv_min, fv_max] envelope instead
of kprintf-only, so a malicious/buggy _PSS is rejected at attach rather
than panicking (or silently programming out-of-spec FID/VID on non-INVARIANTS
kernels) at first transition.
DF-5622 VERDICT β reachable KKASSERT panic on firmware _PSS data (amd0f_set_fidvid)
Status: not_reproduced (on this guest) β static path CERTAIN, trigger hardware absent. Impact class if triggered: kernel panic (DoS) β no memory-corruption primitive.
Reproduced?
No β and it is architecturally impossible on the audit guest, which I prove live
rather than assert. What I did verify on the guest (see run.log):
- The amd0f helpers are compiled into the running stock GENERIC kernel
(
nm /boot/kernel/kernelβamd0f_set_fidvid@ 0xffffffff80bda6c0,amd0f_get_fidvid,amd0f_fidvid_limit; KLDkldstat+ symbol addresses via the harness). The file is live code (sys/platform/pc64/conf/files:149standard), not part of the dead#ifdef notyetlegacy CPU attachment (sys/bus/pci/x86_64/legacy.c:233-347) that DF-5572 covers. - The running kernel is built from
X86_64_GENERIC(uname build path/usr/obj/usr/src/sys/X86_64_GENERIC) and that config hasoptions INVARIANTSat line 56 βKKASSERTis a livepanic()(sys/sys/systm.h:98-101), not a no-op. - The live KLD harness
pstprobe.ccalls the real exported kernel functionacpi_pst_md_probe()and dumps the gating facts: guest CPU vendor = 0x8086 (Intel), APMI(0x80000007) EDX = 0, probe returns NULL. Both gates foracpi_pst_amd0f(vendor==AMD and(APMI_edx & 0x06) == 0x06, acpi_pstate_machdep.c:150-175) are OFF.amd0f_set_fidvidtherefore cannot execute on this guest. QEMU/KVM does not implement K8 FID/VID MSRs (0xc0010041/0xc0010042) in any configuration this harness can reach; even calling the global symbol directly would #GP at the entryrdmsr(cpufreq_machdep.c:78) β the DF-5484 class, already filed for the sibling file β before ever reaching the asserts at cpufreq_machdep.c:92/94.
Why the finding is still certain (line-by-line trace)
Attacker-controlled (firmware) value flow into the asserted expressions:
_PSSpackage βacpi_pstates[i].st_cval(sys/dev/acpica/acpi_cpu_pstate.c:585) β attach-time validationacpi_pst_amd0f_check_pstates()(acpi_pstate_machdep.c:288-368):- entry i==0 (max state): FID/VID vs MSR-derived limits β kprintf only, never EINVAL (acpi_pstate_machdep.c:310-320);
- entry i==npstates-1 (min state): kprintf only (lines 321-331);
- middle entries: EINVAL only if
acpi_pst_stringent_check(default 1, but a boot tunable, and irrelevant to the endpoints) β lines 332-347; mvs != 0β hard EINVAL (line 353) β mvs is always 0 at runtime;rvo_mv % mvs_mvcheck (line 361) is vacuous for mvs==0 (25rvo % 25 == 0 for every rvo β 0..3) β rvo is never constrained*.- Transition (first
acpi_pst_postattachtransition,powerd, or root sysctl write β writes needSYSCAP_NOSYSCTL_WR, sys/kern/kern_sysctl.c:1446) βacpi_pst_amd0f_set_pstate()(acpi_pstate_machdep.c:370-388) packsfv.fid/fv.vidandxsit.rvo/mvs/vst/pll_time/irtstraight fromst_cvalβamd0f_set_fidvid(). - cpufreq_machdep.c:91: frequency-increase (or vid-lowering) branch evaluates
KKASSERT(fv->vid >= xsit->rvo)β panics for e.g. endpoint entry with vid=0, rvo=3. cpufreq_machdep.c:94:KKASSERT(cvid >= xsit->rvo)fires on a downgrade when hardware sits at cvid < rvo (K8 boots at max voltage = low cvid value). Both arepanic()under the stock INVARIANTS kernel.
No bounds/type error exists in the value flow (fid 6 bits, vid 5 bits, rvo 2
bits β all masked at extraction), so on a non-INVARIANTS kernel the same
malformed table instead programs slightly out-of-sequence VID steps (bounded
loops, clamped by cvid > 0 guards) β the kernel-safety impact is exactly the
assert-panic; there is no memory-corruption primitive (no arrays, no pointers,
no copyout in the entire file).
Threat model honesty
The only actor who can supply a malformed _PSS is firmware/BIOS or the
hypervisor owner (host). A guest unprivileged user has no path: the sysctl
surface is root-only and can only select among already-attached states. Hence
Low severity, and no uid0 escalation chain exists for this bug class.
Fix
fix.diff (authored post-verification, never applied to sys/): converts both
asserts to return EINVAL. Fix validation (fix_status) is not_testable:
the baseline panic cannot be produced on this guest (hardware gate above), so
no patched-kernel A/B run is meaningful.
Artifacts
pstprobe.c,Makefile,build.sh,run.shβ live-introspection KLD harnessbuild.logβ full untrimmed successful KLD build (-Werror, gcc DF 6.5)run.logβ kldload + dmesg + INVARIANTS config lineenv.txtβ guest environmentfix.diffβ validated-by-inspection one-hunk fixmanifest.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 (KKASSERT->EINVAL in cpufreq_machdep.c:91-95) is git-apply-clean against the pristine sys/ tree and the patched TU compiles warning-free with the guest's kernel toolchain (fixbuild.log, RC=0). Full A/B fix validation not testable: the baseline panic requires AMD family-0F hardware plus a crafted _PSS, which this Intel-vendor KVM guest cannot present (live-proven; see VERDICT.md).
fixbuild.log (cc -Werror ... -c cpufreq_machdep.c, RC=0); git apply --check output APPLY_CHECK_OK recorded in VERDICT.md; fix.diff hunk @@ -84,13 +84,17 @@.
Confirmed kernel references
- sys/platform/pc64/x86_64/cpufreq_machdep.c:91-95
- sys/platform/pc64/x86_64/cpufreq_machdep.c:78
- sys/platform/pc64/acpica/acpi_pstate_machdep.c:310-331
- sys/platform/pc64/acpica/acpi_pstate_machdep.c:353-365
- sys/platform/pc64/acpica/acpi_pstate_machdep.c:370-388
- sys/sys/systm.h:98-101
- sys/config/X86_64_GENERIC:56
- sys/kern/kern_sysctl.c:1446
Detail
Evidence (decisive lines)
run.log lines: 'pstprobe: cpu_vendor_id=0x8086 ... APMI(0x80000007) edx=0' + 'acpi_pst_md_probe() = NULL ... UNREACHABLE' + 'amd0f_set_fidvid=0xffffffff80bda6c0'; env.txt: X86_64_GENERIC line 56 options INVARIANTS (running kernel's config); VERDICT.md full line-by-line trace; fix.diff git-apply-clean and compile-validated on the guest (fixbuild.log RC=0).
PoC changes
No seed PoC existed. Authored pstprobe.c KLD harness: calls the real exported acpi_pst_md_probe() (pure CPUID/global reads, deliberately zero MSR access) to live-prove the reachability gates and dump the compiled-in amd0f symbol addresses; avoided calling amd0f_* directly because their entry rdmsr(0xc0010042) would #GP on this Intel-vendor guest (DF-5484 class) before reaching the asserted lines.
Verified recommended fix
Replace both KKASSERTs in amd0f_set_fidvid() with return-EINVAL validation, and hard-reject (EINVAL) _PSS endpoint entries with FID/VID outside the MSR-derived [fv_min,fv_max] envelope in acpi_pst_amd0f_check_pstates() instead of kprintf-only.
Verdict
Static path certain, not triggerable on the audit guest. amd0f_set_fidvid() KKASSERTs (cpufreq_machdep.c:92/94) on values taken directly from the ACPI _PSS control value; the attach-time validator acpi_pst_amd0f_check_pstates() only kprintfs (never rejects) the first/last P-state entries (acpi_pstate_machdep.c:310-331) and never checks vid>=rvo (the rvo_mv%mvs_mv check at :361 is vacuous for the hard-enforced mvs==0), so a _PSS endpoint with vid<rvo panics the stock INVARIANTS GENERIC kernel (X86_64_GENERIC:56; KKASSERT==panic per sys/sys/systm.h:98-101) on any transition (powerd/boot/sysctl). Live KLD introspection (pstprobe.c) proves the guest cannot reach it: vendor=0x8086/GenuineIntel family 0xf, APMI 0x80000007 EDX=0, acpi_pst_md_probe()==NULL, while nm shows amd0f_set_fidvid @0xffffffff80bda6c0 compiled into the running kernel. No unprivileged trigger path exists (sysctl writes gated by SYSCAP_NOSYSCTL_WR, kern_sysctl.c:1446); the attacker is firmware/host, hence Low. No memory-corruption primitive: the file has no pointers, arrays, or copyout.
No comments yet.