Raw rdmsr/wrmsr on fixed P-state MSRs (0x1a0/0x198/0x199, 0xc001006x) gated only by CPUID: fatal kernel #GP on CPUID-lying VMMs β DF-4735/DF-5155/DF-5160 class, first transition fires unattended at boot
Summary
All six fixed-MSR sites use raw accessors where the rest of the kernel uses rdmsr_safe/wrmsr_safe: the only gate is the CPUID bit, so a VMM advertising EST without virtualizing the MSR panics the kernel on the first unattended boot transition. On this very VMM wrmsr_safe(0x199) returns error 14 and a module executing the exact driver sequence took Fatal trap 9 at the wrmsr (guest dead). QEMU can't advertise EST here so the driver-path trigger needs a lying VMM (same calibration as DF-5155/5160). safe-accessor-conversion fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5484 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.9 KB | β raw | |
| VERDICT.md | β | 4.7 KB | β raw | |
| msrprobe.c | β | 2.4 KB | view raw | |
| msrprobe2.c | β | 1.9 KB | view raw | |
| rawpst.c | β | 2.2 KB | view raw | |
| run_probe.log | β | 687 B | view raw | |
| msrprobe2.log | β | 861 B | view raw | |
| panic.txt | β | 662 B | view raw | |
| panic_run_fullconsole.log | β | 14.9 KB | view raw | |
| build.sh | β | 117 B | view raw | |
| run.sh | β | 136 B | view raw | |
| env.txt | β | 741 B | view raw | |
| fix.diff | β | 3.0 KB | view raw |
DF-5484 β raw rdmsr/wrmsr P-state MSRs: fatal #GP on CPUID-lying platforms
sys/platform/pc64/acpica/acpi_pstate_machdep.c touches fixed MSRs with raw
rdmsr()/wrmsr() gated only by CPUID bits:
Intel: 0x1a0 (:506/:509/:511), 0x199 (:530/:533), 0x198 (:555) [gate: CPUID2_EST]
AMD: 0xc0010064..6b (:227), 0xc0010062 (:259), 0xc0010063 (:280)
[gate: family>=0x10 + APMI bit]
A #GP in kernel mode is a fatal trap. The first P-state transition happens
unattended at boot (acpi_pst_postattach -> acpi_pst_global_set_pstate),
again on every root px_global/px_domN.select sysctl write, and on every
type-0x80 _PPC notify fixup.
Build (on the guest)
sh build.sh # msrprobe.ko, msrprobe2.ko, rawpst.ko (bsd.kmod.mk)
Run (KVM -cpu host)
dfbsd-qemu/vm.sh down QEMU_EXTRA="-cpu host" dfbsd-qemu/vm.sh up 150 sh run.sh # kldload rawpst.ko
Expected (observed)
rawpst: safe probe: rdmsr_safe(0x199) -> error 0 val 0 rawpst: safe probe: wrmsr_safe(0x199, val) -> error 14 rawpst: raw driver sequence (pstate_machdep.c:530-533), cval=0x0e00 ... Fatal trap 9: general protection fault while in kernel mode ... Stopped at rawpst_modevent+0xb7: wrmsr db>
Guest dead in DDB; vm.sh status -> down. Full console: panic.txt /
panic_run_fullconsole.log. Safe-accessor inventory of all six MSR sites:
run_probe.log / msrprobe2.log.
Honest gap (DF-5155 methodology)
No QEMU config on this host can advertise CPUID2_EST (KVM: host Xeon E3-1275 v5 lacks CPUID.01H:ECX bit 7 and KVM_GET_SUPPORTED_CPUID does not virtualize it; TCG masks est as unimplemented), so the driver path with the md engaged is not constructible here; rawpst executes the driver's exact instruction pair instead. On any VMM that advertises EST without virtualizing PERF_CTL, the stock driver takes this trap at boot.
VERDICT β DF-5484
Reproduced? YES (panic primitive) β with the family-standard honest gap:
the driver path itself is unreachable on this VMM because no QEMU CPU
config can advertise CPUID2_EST; the exact driver instruction sequence
was executed in kernel context instead (DF-5155/DF-5160 methodology).
What the audited code does
sys/platform/pc64/acpica/acpi_pstate_machdep.c touches fixed MSRs with RAW
rdmsr()/wrmsr() β a #GP in kernel mode is a fatal trap (panic/DDB). The
only gating is CPUID bits read at probe time:
| site | MSR | raw op | gate |
|---|---|---|---|
| acpi_pst_amd1x_check_p_states1:227 | 0xc0010064..6b | rdmsr | vendor AMD, family β₯ 0x10, APMI bit (0x80000007) |
| acpi_pst_amd1x_set_pstate:259 | 0xc0010062 | wrmsr | same, plus per-state MSR-enabled check |
| acpi_pst_amd1x_get_pstate:280 | 0xc0010063 | rdmsr | same |
| acpi_pst_intel_init:506/:509/:511 | 0x1a0 | rd/wrmsr | vendor Intel, CPUID2_EST |
| acpi_pst_intel_set_pstate:530/:533 | 0x199 | rd/wrmsr | same |
| acpi_pst_intel_get_pstate:555 | 0x198 | rdmsr | same |
If the VMM (or CPU) advertises the feature bit without virtualizing the MSR,
the first P-state transition takes the fatal trap. That transition happens
(a) unattended at boot (acpi_pst_postattach β acpi_pst_global_set_pstate),
(b) on every sysctl hw.acpi.cpu.px_global=<freq> / px_domN.select write
(root), (c) on every ACPI type-0x80 _PPC notify fixup.
Evidence on this guest (KVM -cpu host, Skylake Xeon E3-1275 v5)
msrprobe2 (safe accessors only):
msrprobe2: cpu_vendor_id 32902 cpu_id 0x506e3 cpu_feature2 0xf7fab223 (CPUID2_EST clear) msrprobe2: rdmsr(0x1a0) -> error 0 val 0x1 msrprobe2: wrmsr(0x1a0,|EST) -> error 0 msrprobe2: rdmsr(0x1a0) -> error 0 val 0x10001 -> EST bit STUCK (init would succeed) msrprobe2: wrmsr_safe(0x199, 0x0e00) -> error 14 (raw wrmsr at pstate_machdep.c:533 would #GP panic)
i.e. on this very VMM: MISC_ENABLE (0x1a0) works, PERF_STATUS (0x198) works,
IA32_PERF_CTL (0x199) writes take #GP. rawpst then executed the exact
acpi_pst_intel_set_pstate sequence (:530-533):
rawpst: safe probe: rdmsr_safe(0x199) -> error 0 val 0 rawpst: safe probe: wrmsr_safe(0x199, val) -> error 14 rawpst: raw driver sequence (pstate_machdep.c:530-533), cval=0x0e00 -- expect Fatal trap 9 Fatal trap 9: general protection fault while in kernel mode cpuid = 4; lapic id = 4 instruction pointer = 0x8:0xffffffff826000b7 ... Stopped at rawpst_modevent+0xb7: wrmsr db>
Full console in panic_run_fullconsole.log / panic.txt. Guest dead in DDB,
vm.sh status -> down.
The honest gap
EST cannot be advertised on this QEMU under any acceleration: KVM drops it (host Xeon E3-1275 v5 does not set CPUID.01H:ECX bit 7 β server SKU β and KVM_GET_SUPPORTED_CPUID does not virtualize it; TCG masks it as unimplemented: "TCG doesn't support requested feature: CPUID.01H:ECX.est"). So the driver path with the md engaged is constructible only with a kld substituting the CPUID advertisement (estforce.ko in the DF-5483 pack does exactly this for the IO-port variant) β but under TCG the MSR 0x199 write is virtualized (no #GP), and under KVM the CPUID bit cannot be raised. Any VMM that advertises EST without virtualizing PERF_CTL (the DF-4735/DF-5155/ DF-5160 lying-CPUID class; several KVM versions expose EST on client SKUs of Intel hosts) gets the boot-time fatal trap. Identical situation and calibration to DF-5155 (perfbias, Low, root-gated/likely) and DF-5160 (aperf, Low, boot-time/likely).
Impact
Kernel panic / fatal trap 9 β DDB β system down. Unattended variant fires at boot whenever the tables provide _PSS/_PCT with Fixed-Hardware control (perfectly normal firmware). No memory corruption (a fault, not a write primitive). Severity Low (VMM/misconfigured-platform precondition), confidence likely.
fix.diff
Authored (fix.diff): all six raw sites converted to rdmsr_safe/wrmsr_safe with error propagation (check β EINVAL, init β EIO, set β error, get β NULL). With the fix, a lying VMM yields a dead P-state domain and a kprintf, not a fatal trap. fix_status=not_testable: rebuilding would change the driver, but the trigger configuration (EST advertised + PERF_CTL faulting) is not constructible on this VMM; the fixed behavior (return error 14, no trap) is exactly what the safe-probe halves of msrprobe2/rawpst demonstrated live in this pack.
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 authored (safe accessors + error propagation, all six sites) but not rebuild-validated: the trigger configuration (EST advertised AND PERF_CTL faulting) is not constructible on this VMM β under KVM the CPUID bit cannot be raised, under TCG the MSR is virtualized. The fixed code's behavior on this VMM is exactly the demonstrated safe-probe halves: rdmsr_safe/wrmsr_safe return error 14 with no trap (run_probe.log), which would leave a dead domain + kprintf instead of a fatal trap.
fix.diff; run_probe.log (safe-accessor outcomes for every MSR the diff touches); VERDICT.md 'fix.diff' section
Confirmed kernel references
- sys/platform/pc64/acpica/acpi_pstate_machdep.c:227
- sys/platform/pc64/acpica/acpi_pstate_machdep.c:259
- sys/platform/pc64/acpica/acpi_pstate_machdep.c:280
- sys/platform/pc64/acpica/acpi_pstate_machdep.c:506
- sys/platform/pc64/acpica/acpi_pstate_machdep.c:530
- sys/platform/pc64/acpica/acpi_pstate_machdep.c:533
- sys/platform/pc64/acpica/acpi_pstate_machdep.c:555
Detail
Exploit chain
VMM advertises CPUID.01H:ECX.EST (or AMD family>=0x10 + APMI bit) without virtualizing IA32_PERF_CTL/AMD P-state MSRs -> acpi_pst_md_probe selects the md -> first P-state transition at boot (or first px_global/px_domN.select sysctl write, or _PPC notify fixup) -> raw wrmsr(0x199, cval) at acpi_pstate_machdep.c:533 -> #GP in kernel mode -> Fatal trap 9 -> DDB -> system down
Evidence (decisive lines)
panic.txt (Fatal trap 9, 'Stopped at rawpst_modevent+0xb7: wrmsr'); panic_run_fullconsole.log (full serial); run_probe.log (wrmsr_safe(0x199) -> error 14 while 0x1a0/0x198 succeed under -cpu host); msrprobe2.log (EST-enable dance works, i.e. init passes, then the PERF_CTL write is the faulting step; TCG variant shows the MSR virtualized there); VERDICT.md
PoC changes
rawpst.c written fresh: replicates the exact driver instruction sequence with the value-oracle prints; msrprobe.c/msrprobe2.c probe all six MSR sites with safe accessors first.
Verified recommended fix
Convert all six raw MSR sites in acpi_pstate_machdep.c to rdmsr_safe/wrmsr_safe with error propagation (check -> EINVAL, init -> EIO, set -> error, get -> NULL) so a lying VMM kills only the P-state domain.
Verdict
Raw rdmsr/wrmsr on fixed P-state MSRs (0x1a0/0x198/0x199 Intel, 0xc001006[2-b] AMD) in acpi_pstate_machdep.c take a fatal kernel #GP on any VMM/CPU that advertises the gating CPUID bits without virtualizing the MSR β the DF-4735/DF-5155/DF-5160 lying-CPUID class. On this guest (KVM -cpu host, Skylake) wrmsr_safe(IA32_PERF_CTL=0x199) returns error 14 while rdmsr(0x198)/rdmsr-wrmsr(0x1a0) succeed: a module executing the exact acpi_pst_intel_set_pstate:530-533 instruction pair took Fatal trap 9 (GPF) at the wrmsr and left the guest dead in DDB (panic.txt). The driver path itself would fire unattended at boot (acpi_pst_postattach -> acpi_pst_global_set_pstate) on any firmware providing _PSS/_PCT Fixed-Hardware control; on THIS QEMU the driver path is unreachable because no CPU config can advertise CPUID2_EST (KVM host lacks the bit and KVM_GET_SUPPORTED_CPUID excludes it; TCG masks est as unimplemented) β the same honest-gap shape as DF-5155, where the trigger module substitutes only the missing CPUID advertisement. Panic primitive only (a fault, not a write primitive); severity Low, confidence likely.
No comments yet.