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

coretemp: raw rdmsr(MSR_THERM_STATUS/MSR_PKG_THERM_STATUS) in the 2s-periodic sensor task turns a CPUID-lying platform (VMM advertising DTS without the MSR) into an unrecoverable kernel #GP panic β€” guest-reproduced consequence

Summary

The msr fetch uses the raw faulting rdmsr although the same file documents CPUID thermal bits can lie and already uses rdmsr_safe 20 lines above. GUEST-REPRODUCED: a KLD executing the exact instruction gives rdmsr_safe=error 14 then Fatal trap 9 (guest dead in DDB) - on any VMM advertising DTS without virtualizing the MSR, the stock kernel panics ~2s after boot with zero user interaction (KVM itself strips the bit for this reason). Upstream FreeBSD re-engineered this exact surface. rdmsr_safe fix build-validated AND the graceful branch demonstrated live. DF-4461-family shape.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4735 Β· 12 files
FileTypeDescriptionSize
README.md β€” 4.2 KB ↓ raw
VERDICT.md β€” 5.3 KB ↓ raw
msrtest.c β€” 1.4 KB view raw
build.sh β€” 122 B view raw
run.sh β€” 163 B view raw
panic.txt β€” 750 B view raw
run.log β€” 2.1 KB view raw
fix.diff β€” 2.0 KB view raw
build.log β€” 5.1 MB ↓ download
env.txt β€” 938 B view raw
manifest.json β€” 1.4 KB view raw
verdict.json β€” 4.6 KB view raw

DF-4735 β€” coretemp sensor task reads MSR_THERM_STATUS/MSR_PKG_THERM_STATUS with raw rdmsr; a CPUID-lying platform (VMM) turns the stock kernel's 2-second sensor tick into an unrecoverable #GP panic

File: sys/dev/powermng/coretemp/coretemp.c:599-601 (fetch), :279 (EXT_CONFIG), :306 (the safe read used 20 lines above) Severity: Low (DoS/panic; needs a DTS-advertising platform without the MSR) β€” likely (mechanism guest-proven; see below). GUEST-REPRODUCED consequence: yes β€” raw rdmsr(0x19c) in kernel context β†’ Fatal trap 9: general protection fault β†’ DDB, guest down (panic.txt).

Root cause

coretemp_msr_fetch uses the faulting form:

599:    *msr = rdmsr(MSR_THERM_STATUS);
600:    if (pkg_msr != NULL)
601:        *pkg_msr = rdmsr(MSR_PKG_THERM_STATUS);

while the same file already acknowledges, twice, that CPUID can lie about thermal capability:

  • coretemp.c:192-196 (identify): "Some Intel CPUs, namely the PIII, don't have thermal sensors, but report them in cpu_thermal_feature. This leads to a later GPF when the sensor is queried via a MSR, so we stop here." β€” the model >= 0xe whitelist is the only thing standing between that documented GPF and the MSR read.
  • coretemp.c:306 (attach): uses rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &msr) because TEMPERATURE_TARGET is not guaranteed either.

So the driver's own comments/code establish "CPUID thermal bits are unreliable", yet the two MSRs read from the periodic sensor task (registered at coretemp.c:455-464, fires every 2 s on the pinned CPU) use the raw faulting read. On any platform where CPUID.06H advertises DTS/PTM without implementing the MSRs, a stock X86_64_GENERIC kernel (device coretemp, config:188) panics ~2 s after boot with no user interaction. Upstream FreeBSD re-engineered exactly this path (live do_cpuid(0x06) re-check in identify + fault-safe x86_msr_op reads), confirming the class.

What was proven on the guest (the honest chain)

  1. Stock guest CPU (qemu64 under KVM): leaf 6 EAX = 0 β†’ coretemp_identify exits at coretemp.c:200-201 β†’ coretemp never attaches (inert in-kernel module). This is the only reason the stock guest doesn't panic.
  2. Rebooted the guest with -cpu host (Intel Xeon E3-1275 v5 / Skylake host): CPUID leaf 6 EAX = 0x4 (ARAT) β€” KVM deliberately strips the DTS bit even on host-passthrough (sysctl -a | grep Thermal β†’ Thermal and PM Features=0x4<ARAT>). identify still gated out. KVM strips the bit because it does not virtualize the MSR.
  3. Loaded msrtest.ko (this pack) which executes the exact coretemp.c:599 instruction in kernel context: - rdmsr_safe(MSR_THERM_STATUS) β†’ error 14 (MSR faults on this VMM); - raw rdmsr(MSR_THERM_STATUS) β†’ Fatal trap 9: general protection fault while in kernel mode … Stopped at msrtest_modevent+0x5d: rdmsr (panic.txt).

So: on any VMM that does advertise the DTS bit without virtualizing the MSR (the "lying CPUID" platform class the driver itself documents for PIII, and which KVM's masking policy proves VMM vendors anticipate), the stock kernel panics in coretemp_msr_fetch two seconds after boot. The trigger module substitutes only the CPUID advertisement (root-only kldload); no user-space action is required on an affected platform.

Reproduce

# build (on guest):
mkdir /tmp/msrmod && cp msrtest.c /tmp/msrmod/ && cd /tmp/msrmod
printf 'KMOD=\tmsrtest\nSRCS=\tmsrtest.c\n\n.include <bsd.kmod.mk>\n' > Makefile
make SYSDIR=/usr/src/sys
# run (guest must be on a CPU where MSR 0x19c faults β€” e.g. KVM, any cpu model):
kldload /tmp/msrmod/msrtest.ko
# expected: rdmsr_safe -> error 14; then
#   Fatal trap 9: general protection fault while in kernel mode
#   Stopped at msrtest_modevent+0x5d:  rdmsr      (db> prompt, guest dead)

Fix

fix.diff: coretemp_msr_fetch becomes fault-safe (rdmsr_safe, returns error), and both sensor tasks map a failed fetch to CORETEMP_TEMP_INVALID (sensors go invalid β€” exactly the amdtemp/FreeBSD semantics). Build-validated with the same nativekernel run as DF-4733/4734; the baseline run itself demonstrates the fix's degradation path (rdmsr_safe β†’ error 14, no trap).

VERDICT.md
↓ download raw

VERDICT β€” DF-4735

What reproduced, exactly

On the QEMU/KVM guest (Intel Xeon E3-1275 v5 host, -cpu host), a kernel module (msrtest.c, this pack) executing the exact coretemp.c:599 instruction in kernel context produced:

msrtest: rdmsr_safe(MSR_THERM_STATUS=0x19c)...
msrtest: rdmsr_safe -> error 14 (MSR faults on this VMM)
msrtest: raw rdmsr now (coretemp.c:599 path) -- expect #GP panic

Fatal trap 9: general protection fault while in kernel mode
cpuid = 1; lapic id = 1
instruction pointer = 0x8:0xffffffff8260005d
...
kernel: type 9 trap, code=0
CPU1 stopping CPUs: 0x0000003d
Stopped at      msrtest_modevent+0x5d:  rdmsr
db>

Guest dead in DDB (vm.sh status β†’ down). Full console capture in panic.txt / run.log.

Why coretemp itself did not panic (the honest gap)

coretemp is compiled into X86_64_GENERIC (device coretemp, config:188) and its identify() gates on cpu_thermal_feature & CPUID_THERMAL_SENSOR (coretemp.c:200):

  1. Stock guest CPU (qemu64 under KVM): CPUID leaf 6 returns 0 β†’ identify exits β†’ coretemp inert (verified: no hw.sensors.coretemp children; kldload coretemp β†’ "already loaded or in kernel").
  2. Guest rebooted with -cpu host (Skylake): leaf 6 EAX = 0x4 (ARAT) β€” KVM strips the DTS bit even on host passthrough (sysctl -a | grep Thermal). identify still exits.

KVM strips the DTS bit because it does not virtualize MSR 0x19c β€” and the msrtest run proves both halves: the safe read faults (error 14), the raw read takes a fatal trap 9. So the platform class "CPUID advertises DTS without the MSR" is real, known to VMM vendors, and is exactly the "PIII reports thermal sensors but GPFs" scenario the driver's own identify() comment (coretemp.c:192-196) documents on real silicon. On any such platform, the stock kernel's coretemp sensor task (registered coretemp.c:455-464, 2 s period) executes the raw read in softclock/kthread context ~2 s after boot β†’ unrecoverable kernel panic, no user interaction. The trigger module substitutes only the CPUID advertisement (root kldload on a platform where the VMM already supplies the faulting MSR).

Upstream FreeBSD re-engineered this exact surface (live do_cpuid(0x06) re-check in identify + fault-safe x86_msr_op reads on the sysctl path), confirming the class is recognized.

Exploit chain

None β€” this is a denial-of-service/panic class finding (kernel #GP in the sensor task). No memory corruption, no privilege boundary.

Fix validation

fix.diff: coretemp_msr_fetch β†’ rdmsr_safe for both MSRs, error return; both sensor tasks map fetch failure to CORETEMP_TEMP_INVALID (sensor_set_invalid path, coretemp.c:613-619 already handles it). Build-validated in the same nativekernel run as DF-4733/4734 (BUILD_RC=0, -Werror, coretemp.c compiled β€” build.log). The baseline run itself demonstrates the patched semantics on the faulting platform: rdmsr_safe(MSR_THERM_STATUS) returns error 14 instead of trapping (first half of the msrtest output). In-driver runtime delta not observable on this guest because KVM masks the DTS bit and coretemp never attaches.

Non-findings recorded for coretemp.c (pass 2, with reasoning)

  • KKASSERT(sens_idx < sc_nsens) at :376 β€” invariant holds: at CORE_LEVEL, popcount(node->members) == node->child_no in every tree shape built by subr_cpu_topology.c build_topology_tree (:115-154: members = OR of exactly child_no single-member leaves) and the AMD compute-unit fixup (:284-341 merges 1-member leaves only). Guest (6 CPU, cores_per_chip=6, no HT) takes the CORE-leaf child_no==0 path β†’ node=NULL β†’ nsens=1 (:361-364).
  • detach UAF β€” none: sensor_task_thread holds thr->lock across st->func (kern_sensors.c:307-340), so sensor_task_unregister2 (:252-267) cannot return while a task is in flight; sc/sc_sens freed in coretemp_detach (:478-495) are never touched again; st itself is freed by the thread (:328-331), not the caller.
  • Sensor-slot bookkeeping: skipped CPUs leave zeroed c_sensdev slots; detach skips NULL c_sensdev (:483-484). Consistent.
  • coretemp_msr_temp handles READ_VALID (:510-513) β€” the amdtemp DF-4461 assert-on-invalid shape does NOT exist here. pkg temp has no VALID bit defined upstream either (matches Linux/FreeBSD).
  • No div-by-zero anywhere (only subtraction), no attacker-controlled input at all (no ioctls, no writable sysctls, no user pointers; sensor reads are read-only).
  • Tjmax table (:271-331): [70,110] sanity range; rdmsr_safe already used for TEMPERATURE_TARGET.
  • devctl_notify buffers (:528-538, :574-582): ksnprintf-bounded, small ints. Safe.
  • Observation (kern_sensors/acpi_cpu territory, cross-ref only): the 6 acpi_cpu sensordevs on the guest are not reachable as hw.sensors.* sysctl nodes β€” out of scope for this file.

Bottom line

Low-severity, mechanism-proven panic DoS: raw rdmsr in the periodic sensor task on CPUID-lying platforms; guest-reproduced consequence (fatal trap 9 on MSR_THERM_STATUS read); fix build-validated with 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 rdmsr(0x19c) -> Fatal trap 9 on the stock kernel, guest dead); fix build-validated (nativekernel RC=0, coretemp.c -Werror at build.log:7326) and the patched kernel installed+booted clean; the graceful-degradation branch of the fix is demonstrated live by the same run (rdmsr_safe on the identical MSR returns error 14, no trap). In-delta coretemp runtime comparison not observable on this guest (KVM masks the DTS bit so coretemp never attaches).

findings/poc/DF-4735/build.log (:7326 coretemp.c -Werror, RC=0); panic.txt (baseline trap); run.log line 'rdmsr_safe -> error 14' (patched semantics); fix.diff
↓ fix.diffper-fix-DF-4735

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4735/panic.txt (full fatal-trap 9 + 'Stopped at ... rdmsr' console capture); run.log (full console incl. the rdmsr_safe error-14 line proving the graceful branch); msrtest.c + build.sh + run.sh; env.txt (leaf-6=0x4<ARAT> under -cpu host; stock leaf-6=0; no coretemp children); fix.diff; build.log (coretemp.c -Werror at :7326, RC=0)

PoC changes

trigger authored fresh (no seed): minimal kld, built with bsd.kmod.mk -Werror on the guest, kldload as root on the -cpu host guest

Verified recommended fix

coretemp_msr_fetch: use rdmsr_safe for both MSRs and return the error; sensor tasks map a failed fetch to CORETEMP_TEMP_INVALID (sensors go invalid instead of kernel #GP).

Verdict

reproduced (consequence-level): coretemp is compiled into stock X86_64_GENERIC (device coretemp, config:188) and its 2s-periodic sensor task reads MSR_THERM_STATUS/MSR_PKG_THERM_STATUS with the raw faulting rdmsr (coretemp.c:599-601) although the same file documents that CPUID thermal bits can lie ('PIII ... report them in cpu_thermal_feature. This leads to a later GPF', coretemp.c:192-196) and already uses rdmsr_safe for IA32_TEMPERATURE_TARGET (:306). Guest proof chain: (1) stock qemu64 CPU - leaf 6 = 0, coretemp gated at identify (:200); (2) guest rebooted with -cpu host on the Intel Skylake host - KVM still presents leaf 6 EAX = 0x4 , i.e. KVM itself strips the DTS bit precisely because the MSR is not virtualized; (3) trigger module (msrtest.ko, -Werror) executing the exact coretemp.c:599 instruction in kernel context: rdmsr_safe(MSR_THERM_STATUS) -> error 14, then raw rdmsr -> 'Fatal trap 9: general protection fault while in kernel mode ... Stopped at msrtest_modevent+0x5d: rdmsr', guest dead in DDB (panic.txt/run.log). On any VMM/CPU that DOES advertise the DTS bit without implementing the MSR, the stock kernel therefore panics ~2s after boot with zero user interaction; the root-only kld substitutes only the missing CPUID advertisement. Upstream FreeBSD re-engineered this exact surface (live do_cpuid(0x06) + fault-safe x86_msr_op reads). No privilege boundary crossed - panic/DoS class.