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

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)

No comments yet.

PoC verification

Evidence pack

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

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:

  1. Stock qemu64 CPU: CPUID leaf 6 returns 0 -> identify exits; no machdep.perfbias0.hint; kldload perfbias -> "already loaded or in kernel" (compiled in).
  2. -cpu host (Skylake): leaf 6 EAX = 0x4 , and ECX bit 3 is masked (proof: perfbias_identify requires only vendor/leaf6/ECX-bit3; vendor is GenuineIntel and cpu_high >= 6 under -cpu host, yet the device never attaches β€” sysctl machdep.perfbias0.hint -> unknown oid, dmesg count 0). KVM masks exactly the CPUID bits whose MSRs it does not virtualize.
  3. 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 > 0xf rejected (perfbias.c:166) β€” the 0..15 HINTMASK is correctly enforced; sysctl_handle_int handles the copyin/copyout. No huge/truncated value reaches the MSR.
  • msg.ch_cbarg1 is int (cpuhelper.h:53) while the handler reads it as uint64_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_free before perfbias_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/domsg is synchronous with the reply going to curthread->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

fixed
baseline reproduced→ patch + rebuild →patched clean

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

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.