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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5484 Β· 13 files
FileTypeDescriptionSize
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.md
↓ download raw

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_testable
baseline reproduced→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-5484

Confirmed kernel references

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.