perfbias: raw wrmsr/rdmsr(MSR_IA32_ENERGY_PERF_BIAS=0x1b0) in the cpuhelper path takes a fatal kernel #GP on CPUID-lying platforms (DF-4735 class), root-gated panic
Summary
The write runs behind only the CPUID.06H.ECX-bit-3 gate with raw accessors - on a CPU/VMM advertising the bit without the MSR (the DF-4735 lying-CPUID class) the first root sysctl write takes Fatal trap 9. GUEST-VERIFIED mechanism+consequence: the exact pair in kernel context traps while the _safe variants return error 14; compiled into GENERIC. wrmsr_safe+error-propagation fix validated on a rebuilt+booted single-fix kernel (patched handler returns EIO, no trap).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5155 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.8 KB | β raw | |
| VERDICT.md | β | 7.3 KB | β raw | |
| pbtest.c | β | 1.8 KB | view raw | |
| pbfix.c | β | 1.2 KB | view raw | |
| build.sh | β | 228 B | view raw | |
| run.sh | β | 205 B | view raw | |
| build.log | β | 5.4 KB | view raw | |
| run.log | β | 15.0 KB | view raw | |
| run.2.log | β | 1.4 KB | view raw | |
| panic.txt | β | 804 B | view raw | |
| env.txt | β | 1.4 KB | view raw | |
| fix.diff | β | 925 B | view raw | |
| fix_build.log | β | 5.6 MB | β download | |
| manifest.json | β | 1.7 KB | view raw | |
| verdict.json | β | 5.2 KB | view raw |
DF-5155 β perfbias: raw wrmsr/rdmsr(MSR_IA32_ENERGY_PERF_BIAS) panics on CPUID-lying platforms (DF-4735 class)
What this is
perfbias_set_handler() (sys/dev/powermng/perfbias/perfbias.c:178-179) writes and
reads MSR 0x1b0 with the raw, non-fault-safe accessors, gated only by
CPUID.06H.ECX bit 3 (CPUID_THERMAL2_SETBH, checked live in perfbias_identify(),
perfbias.c:102-103). On any CPU/VMM that advertises the CPUID bit without
implementing the MSR β the exact "lying CPUID" class proven for MSR 0x19c in
DF-4735 on this same guest β the raw wrmsr takes a fatal #GP in kernel mode:
Fatal trap 9: general protection fault while in kernel mode Stopped at pbtest_modevent+0xad: wrmsr db>
Reach of the stock compiled-in driver (device perfbias, X86_64_GENERIC:173):
root-only β sysctl -w machdep.perfbiasN.hint=0..15 (write needs
SYSCAP_NOSYSCTL_WR, kern_sysctl.c:1446-1447) and driver detach
(perfbias_set(sc, 0), perfbias.c:152). Severity: Low (privileged DoS), same
bucket/calibration as DF-4735 (coretemp).
Build
mkdir -p /tmp/pbmod && cp pbtest.c /tmp/pbmod/ && cd /tmp/pbmod printf 'KMOD=\tpbtest\nSRCS=\tpbtest.c\n\n.include <bsd.kmod.mk>\n' > Makefile make SYSDIR=/usr/src/sys
Run (on a platform where MSR 0x1b0 faults, e.g. this KVM guest with -cpu host)
kldload /tmp/pbmod/pbtest.ko
Expected
pbtest: rdmsr_safe -> error 14, val 0 pbtest: wrmsr_safe -> error 14 pbtest: raw wrmsr now (perfbias.c:178 path) -- expect #GP panic Fatal trap 9: general protection fault while in kernel mode Stopped at pbtest_modevent+0xad: wrmsr db>
Guest dead in DDB (vm.sh status -> down). Full console in run.log /
panic.txt.
Fix-path replica (post-fix semantics, loads harmlessly)
pbfix.c = the patched handler body (wrmsr_safe/rdmsr_safe + error return):
pbfix: patched handler returns error 14 (sysctl writer gets EIO, no trap)
Fix validation
fix.diff (git-apply-able, validated to apply exactly): wrmsr_safe/rdmsr_safe
+ error propagation through cpuhelper_replymsg -> perfbias_set -> sysctl.
Applied to guest /usr/src, single-fix kernel built with
make -j6 nativekernel KERNCONF=X86_64_GENERIC (-Werror, build.log), installed,
booted clean; pbfix.ko loaded on the patched kernel demonstrates the fixed
path returning error 14 where the baseline trapped (run.2.log).
Honest gap (same as DF-4735)
KVM masks CPUID.06H.ECX bit 3 on this guest (perfbias_identify never opens the
gate: no machdep.perfbias0.hint under -cpu host), so the stock driver path
cannot fire on this VMM. The trigger module executes the identical
instruction pair (perfbias.c:178-179) in kernel context on a platform where the
MSR provably faults β the consequence (fatal trap 9, unrecoverable) is
reproduced; only the CPUID advertisement is substituted by the root kldload.
VERDICT β DF-5155
What reproduced, exactly
On the QEMU/KVM guest (Intel Xeon E3-1275 v5 / Skylake host, -cpu host), a
kernel module (pbtest.c, this pack) executing the exact perfbias.c:178-179
instruction pair in kernel context produced:
pbtest: rdmsr_safe(MSR_IA32_ENERGY_PERF_BIAS=0x1b0)... pbtest: rdmsr_safe -> error 14, val 0 pbtest: wrmsr_safe(0x1b0, 7)... pbtest: wrmsr_safe -> error 14 pbtest: raw wrmsr now (perfbias.c:178 path) -- expect #GP panic Fatal trap 9: general protection fault while in kernel mode cpuid = 2; lapic id = 2 instruction pointer = 0x8:0xffffffff826000ad ... kernel: type 9 trap, code=0 CPU2 stopping CPUs: 0x0000003b stopped Stopped at pbtest_modevent+0xad: wrmsr db>
Guest dead in DDB (vm.sh status -> down). Full console capture in panic.txt
(extracted from run.log, the complete serial boot log).
The driver under audit
perfbias is compiled into the stock kernel (device perfbias,
sys/config/X86_64_GENERIC:173). perfbias_set_handler()
(sys/dev/powermng/perfbias/perfbias.c:172-184) runs in the per-CPU cpuhelper
thread of the target CPU (dispatched by perfbias_set, perfbias.c:186-196,
synchronous cpuhelper_domsg) and performs the raw pair:
wrmsr(INTEL_MSR_PERF_BIAS, hint); /* perfbias.c:178 */
hint = rdmsr(INTEL_MSR_PERF_BIAS); /* perfbias.c:179 */
The only gate on the whole surface is the live CPUID check in
perfbias_identify() (perfbias.c:99-103): vendor Intel, cpu_high >= 6, and
CPUID.06H.ECX bit 3 (CPUID_THERMAL2_SETBH, specialreg.h:217). There is no
model blacklist, no fault-safe accessor, no error path.
Call paths that reach the MSR pair:
1. sysctl -w machdep.perfbiasN.hint=0..15 β perfbias_sysctl
(perfbias.c:156-170) range-checks the hint (0..0xf, :166 β correct and
tight) and calls perfbias_set. Sysctl writes require privilege in
DragonFly (kern_sysctl.c:1446-1447: SYSCAP_NOSYSCTL_WR unless
CTLFLAG_ANYBODY; this oid is plain CTLFLAG_RW) β root-gated.
2. perfbias_detach() (perfbias.c:143-154) calls perfbias_set(sc, 0) β
module/device teardown, root-gated.
So the trigger requires root: severity Low (privileged local DoS / panic), bucket base:dev β deliberately the same calibration as DF-4735 (coretemp), which additionally fired unattended at boot; perfbias has no boot-time write.
Why perfbias itself did not panic on the guest (the honest gap)
Identical situation to DF-4735, verified live both ways:
- Stock qemu64 CPU: CPUID leaf 6 returns 0 -> identify exits; no
machdep.perfbias0.hint;kldload perfbias-> "already loaded or in kernel" (compiled in). -cpu host(Skylake): leaf 6 EAX = 0x4, and ECX bit 3 is masked (proof: perfbias_identifyrequires only vendor/leaf6/ECX-bit3; vendor is GenuineIntel andcpu_high >= 6under-cpu host, yet the device never attaches βsysctl machdep.perfbias0.hint-> unknown oid,dmesgcount 0). KVM masks exactly the CPUID bits whose MSRs it does not virtualize.- The pbtest run then proves both halves on this platform: the fault-safe
accessors return error 14 (the MSR is NOT virtualized under
-cpu host), and the raw pair β the identical instructions the cpuhelper thread would execute β takes the fatal trap 9.
On any VMM or CPU that DOES advertise CPUID.06H.ECX bit 3 without implementing
MSR 0x1b0 (the lying-CPUID class established by DF-4735 for leaf-6-advertised
MSRs on this very VMM, and anticipated by VMM vendors' masking policy), the
stock compiled-in driver therefore panics the kernel on the first
sysctl machdep.perfbiasN.hint=... write (or on driver detach). The trigger
module substitutes only the missing CPUID advertisement (root kldload).
Context difference, stated for honesty: the kld executes the pair in the kldload thread; the driver executes it in a cpuhelper thread. Both are ordinary kernel threads β the #GP class, the trap, and the fatal outcome are identical (kernel-mode #GP -> Fatal trap 9 -> DDB, all CPUs stopped).
Exploit chain
None β panic/DoS class finding, root-gated. No memory corruption, no privilege boundary crossed (root -> root panic).
Fix validation
fix.diff (generated with diff -u, validated to apply exactly to a pristine
tree): switch the handler to wrmsr_safe/rdmsr_safe and propagate the error
through cpuhelper_replymsg -> cpuhelper_domsg -> perfbias_set -> the
sysctl writer (gets EIO instead of a dead machine); sc_hint is only updated
on success. Applied to the guest's /usr/src (with-src snapshot), single-fix
kernel built with make -j6 nativekernel KERNCONF=X86_64_GENERIC
(perfbias.c compiled under -Werror, build.log), installed and booted clean
(#1 kernel, run.2.log).
Post-fix semantics demonstrated live on the same faulting platform:
pbfix.ko (the patched handler body, verbatim shape) on the patched kernel
prints wrmsr_safe -> error 14 + "patched handler returns error 14 (sysctl
writer gets EIO, no trap)" and the guest stays up β versus the baseline
module's fatal trap 9 on the same MSR one boot earlier. The first half of the
baseline run (rdmsr_safe/wrmsm_safe -> error 14) demonstrates the identical
graceful-degradation branch.
Non-findings recorded for perfbias.c (pass 2, with reasoning)
- Sysctl range:
hint < 0 || hint > 0xfrejected (perfbias.c:166) β the 0..15 HINTMASK is correctly enforced;sysctl_handle_inthandles the copyin/copyout. No huge/truncated value reaches the MSR. msg.ch_cbarg1isint(cpuhelper.h:53) while the handler reads it asuint64_t(perfbias.c:176): safe because the value is already clamped to 0..15 at the sysctl gate and detach passes literal 0 β no sign-extension / truncation path exists.- Detach ordering:
sysctl_ctx_freebeforeperfbias_set(sc, 0)(perfbias.c:148-152) β correct order: the sysctl surface is removed before the final MSR restore; no handler can start afterwards. The softc remains valid for the duration of detach (freed by the bus framework after return). Theoretical race of a sysctl write already in flight vs kldunload requires two privileged actors racing themselves β no security boundary. cpuhelper_initmsg/domsgis synchronous with the reply going tocurthread->td_msgport(perfbias.c:191-195) β stack msg lifetime is bounded by the call; no leak, no UAF.- Module load state: no global/module-static mutable state beyond the softcs; identify dedupes children (perfbias.c:96-97). Clean.
- No unprivileged input surface at all: the only user-facing control is the root-gated sysctl; the read side returns the cached int.
Bottom line
Low-severity, mechanism-proven panic DoS of the DF-4735 (lying-CPUID) class: raw wrmsr/rdmsr of MSR_IA32_ENERGY_PERF_BIAS in the cpuhelper path on platforms where CPUID.06H.ECX bit 3 lies; consequence reproduced on the guest (Fatal trap 9 at the raw wrmsr; safe accessors return error 14 on the same MSR); fix build-validated, patched kernel booted, and the graceful-degradation path demonstrated live.
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
fixedbaseline reproduced (raw wrmsr(0x1b0) -> Fatal trap 9 on the stock compiled-in-driver kernel, guest dead in DDB); fix applied to guest /usr/src, single-fix kernel built with make -j6 nativekernel KERNCONF=X86_64_GENERIC (RC=0, perfbias.c under -Werror, fix_build.log:17769), installed, and booted clean; pbfix.ko (the patched handler body, verbatim shape) loaded on the patched kernel on the same faulting platform returns error 14 to the caller with no trap and the guest stays up. In-delta perfbias runtime comparison not observable on this guest (KVM masks the SETBH bit so perfbias never attaches).
findings/poc/DF-5155/fix_build.log (RC=0; :17769 perfbias.c -Werror; :26109 perfbias.o); fix.diff (applies exactly); run.2.log (patched #1 kernel boot + pbfix 'returns error 14 ... no trap', guest up); baseline: panic.txt
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-5155/panic.txt (fatal trap 9 + 'Stopped at ... wrmsr' console capture); run.log (full serial log incl. the two error-14 lines proving the graceful branch); run.2.log (patched kernel #1 boot + pbfix.ko no-trap run); pbtest.c/pbfix.c + build.sh + run.sh; env.txt (leaf-6 gating on both cpu models); fix.diff (validated to apply exactly); fix_build.log (RC=0)
PoC changes
trigger authored fresh (no seed): minimal kld built with bsd.kmod.mk -Werror on the -cpu host guest; pbfix.c replicates the patched handler verbatim for the post-fix demonstration
Verified recommended fix
perfbias_set_handler: use wrmsr_safe/rdmsr_safe; propagate the error via cpuhelper_replymsg -> perfbias_set -> sysctl (writer gets EIO); update sc_hint only on success.
Verdict
reproduced (consequence-level): perfbias is compiled into stock X86_64_GENERIC (device perfbias, config:173) and its cpuhelper-dispatched handler executes the raw pair wrmsr(0x1b0)/rdmsr(0x1b0) (perfbias.c:178-179) gated only by the live CPUID.06H.ECX bit 3 check in perfbias_identify (:99-103) - no model blacklist, no fault-safe accessor. Guest proof chain on the KVM/Skylake host: (1) stock qemu64 - leaf 6 = 0, gated; (2) QEMU_EXTRA='-cpu host' - KVM still masks the SETBH bit (vendor Intel, cpu_high>=6, yet no machdep.perfbias0.hint, dmesg count 0) exactly because it does not virtualize MSR 0x1b0; (3) trigger module (pbtest.ko, -Werror) executing the exact perfbias.c:178-179 instructions in kernel context: rdmsr_safe(0x1b0) -> error 14, wrmsr_safe -> error 14, then raw wrmsr -> 'Fatal trap 9: general protection fault while in kernel mode ... Stopped at pbtest_modevent+0xad: wrmsr', guest dead in DDB (panic.txt/run.log). On any VMM/CPU that DOES advertise ECX bit 3 without implementing the MSR (the DF-4735 lying-CPUID class, demonstrated for leaf-6 MSRs on this very VMM), the stock compiled-in driver therefore panics on the first root sysctl write to machdep.perfbiasN.hint (write needs SYSCAP_NOSYSCTL_WR, kern_sysctl.c:1446) or on driver detach (:152). Root-gated: Low severity, privileged local DoS; no privilege boundary crossed. Fix build-validated (nativekernel RC=0, perfbias.c -Werror at fix_build.log:17769), patched kernel installed and booted clean, and the post-fix handler shape (pbfix.ko) demonstrably returns error 14 to the sysctl writer on the same faulting platform instead of trapping.
No comments yet.