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

corepower_probe family-0x06 model whitelist is a skip-guard, not a reject: non-family-06 Intel CPUIDs pass probing (attach then runs a raw rdmsr) β€” live-reproduced hardening gap

Summary

The whitelist is wrapped in if(family==0x06) with no else-reject: a family-0x0f CPUID passes probe on MSR availability alone and attach runs a RAW rdmsr. LIVE-REPRODUCED on the guest (Family=0xf): 'corepower0 ... on cpu5' + 'attach returned 6', no panic, clean unload (the VMM services the MSR). The exact gating shape that produced the reproduced DF-4735 coretemp #GP. Explicit-reject + rdmsr_safe fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4928 Β· 9 files
FileTypeDescriptionSize
trigger.sh β€” 991 B view raw
build.sh β€” 121 B view raw
run.log β€” 877 B view raw
env.txt β€” 511 B view raw
fix.diff β€” 1.1 KB view raw
VERDICT.md β€” 3.5 KB ↓ raw
README.md β€” 2.7 KB ↓ raw
manifest.json β€” 1.0 KB view raw
verdict.json β€” 3.6 KB view raw

DF-4928 β€” corepower probe family-gating fall-through (Info, hardening)

File: sys/dev/powermng/corepower/corepower.c Class: incorrect CPU gating β†’ probe passes on non-family-06 CPUs; raw rdmsr() on the attach path where rdmsr_safe() was available. Severity: Info (no demonstrated security impact β€” every subsequent MSR read remains gated by corepower_try()/rdmsr_safe; live-verified benign). Verified live: yes β€” on the QEMU/KVM audit guest whose CPU reports Origin="GenuineIntel" Family=0xf Model=0x6b.

The issue

corepower_probe() (corepower.c:154-205) wraps the model whitelist in

    if (cpu_family == 0x06) {
        switch (cpu_model) { ... default: return (ENXIO); }
    }

For a CPU whose family is not 0x06 (this guest: family 0xf) the switch is skipped β€” probe falls through to the only remaining gate, corepower_try(MSR_RAPL_POWER_UNIT) (rdmsr_safe). If that MSR read happens to succeed (real family-0xf Netburst hardware does not implement RAPL, but hypervisors commonly service unknown MSRs β€” QEMU/KVM does here), probe succeeds and corepower_attach() runs on a CPU that cannot have the feature, executing the raw rdmsr() at corepower.c:273 before failing with ENXIO at :331-332 (sc_have_sens == 0).

Observed live (run.log):

CPU: QEMU Virtual CPU version 2.5+ ;  Origin="GenuineIntel"  Id=0x60fb1  Family=0xf  Model=0x6b
corepower0: <CPU On-Die Power Usage Estimation> on cpu5
device_probe_and_attach: corepower0 attach returned 6     <- ENXIO from attach
(no sensors - as expected)

The same skip-guard shape exists in corepower_attach() (:226-271) β€” there it is harmless because sc_have_sens stays 0 β†’ ENXIO.

Why this matters even though it is benign today: this is the exact pattern that produced the reproduced #GP family in coretemp (DF-4735) β€” probe gating that trusts one MSR check and attach/refresh paths that then use raw rdmsr(). In corepower every post-probe read is either rdmsr_safe()-verified (each sensor MSR at :289-329) or, for MSR_RAPL_POWER_UNIT at :273, verified by probe's corepower_try() β€” so no #GP is reachable; the finding records (a) the inverted-looking family guard and (b) the defense-in-depth miss, with a two-line fix.

Reproduce (on the audit guest, as root)

sh trigger.sh        # kldload corepower; capture dmesg; kldunload; uptime

Success criterion: dmesg shows the probe/attach pair above on a family-0xf CPU, no sensors attach, no panic, guest alive after unload.

Files

  • trigger.sh / build.sh (no-op) / run.log / env.txt
  • fix.diff β€” reject non-family-06 in probe; rdmsr_safe at attach:273
  • VERDICT.md, manifest.json, verdict.json
VERDICT.md
↓ download raw

DF-4928 VERDICT β€” corepower family-gating fall-through (Info)

Bottom line

Reproduced live on the audit guest; impact: none (by design of the finding). On the QEMU/KVM guest (CPUID Origin="GenuineIntel" Family=0xf Model=0x6b), corepower_probe() accepts the device because the family-0x06 model whitelist is a skip-guard, not a reject (corepower.c:165-197), leaving rdmsr_safe(MSR_RAPL_POWER_UNIT) (via corepower_try, :199) as the only gate β€” which QEMU passes since it services the MSR. corepower_attach() then executes the raw rdmsr(MSR_RAPL_POWER_UNIT) at corepower.c:273 before returning ENXIO at :331-332 (sc_have_sens == 0). No sensors attach, no #GP, kldload and kldunload are clean, guest stays up (run.log).

Why it is only Info

  • Every sensor MSR is individually verified with rdmsr_safe() at attach (corepower_try at :289-:329) before the refresh path's raw rdmsr() (:393, :402) can ever touch it.
  • MSR_RAPL_POWER_UNIT at :273 is verified by probe's corepower_try() on the same package β€” an MSR readable via rdmsr_safe cannot #GP when read raw one call later.
  • The failure mode on any real non-RAPL Intel CPU is a clean ENXIO.

The risk recorded here is the pattern (this is the shape that produced the reproduced coretemp #GP, DF-4735): a gating typo plus raw MSR reads one refactor away from a panic. The fix makes the intent explicit.

Everything else checked in this pass-2 audit of corepower.c (all clean)

  • identify/per-package dedup (:116-152): node->child_no is the count of children at a topology level (subr_cpu_topology.c:122-133), so child_no == 0 is a degenerate-node guard, not an inverted chip filter β€” initial suspicion of an inverted gating killed.
  • attach/refresh/detach lifetime: sensor_task_register2 runs callbacks holding the per-CPU thr->lock (kern_sensors.c:300-339) and sensor_task_unregister2 takes the same lock before clearing running β€” after corepower_detach returns, no callback can be running; the deinstall-then-unregister order at :346-347 is safe. Live kldunload clean.
  • Energy math (:352-410): no division by zero (secs is the literal 1), the 32-bit wraparound delta is bounded ≀ 0xFFFFFFFF (the energy > a branch implies res = 2^32 - energy + a ≀ 2^32 - 1), so the u64β†’u32 parameter narrowing at :409 cannot truncate. Units fields are 4-5 bits (__BITS64), 1 << 31 for a hypothetical energy_units=31 fits uint32_t (pedantic int-shift nit, not reachable on real silicon, not filed).
  • sysctl strings: all ksnprintf with sizeof bounds (:287, :389).
  • Attach failure paths: ENXIO before any registration, or with no installed sensordev (sysctl install defers while sensdev->oid == NULL, kern_sensors.c sensor_sysctl_install); sensor_task_register2 cannot return NULL (M_WAITOK), so sensor_task_unregister2(NULL) is unreachable.

Fix validation

fix.diff (reject cpu_vendor_id != CPU_VENDOR_INTEL || family != 0x06 in probe; rdmsr_safe at :273) β€” two-line hardening, dry-run-applyable; no kernel rebuild was performed for this Info finding (fix_status: not_testable, per contract only memory-corruption findings mandate a single-fix kernel build).

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

Two-line hardening (fix.diff, applies clean via patch --dry-run). No kernel rebuild/validation performed: Info-severity finding, and per contract a single-fix kernel build is mandated only for reproduced memory-corruption findings. The patched behavior is trivially predictable (probe returns ENXIO for family != 6 before any attach attempt).

fix.diff hunks at corepower.c:159 and :275; baseline behavior in run.log.
↓ fix.diffper-fix-DF-4928

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: CPU line 'Origin="GenuineIntel" Id=0x60fb1 Family=0xf Model=0x6b' followed by kldload -> 'corepower0: <CPU On-Die Power Usage Estimation> on cpu5' + 'device_probe_and_attach: corepower0 attach returned 6', '(no sensors - as expected)', clean kldunload, uptime OK. env.txt: guest uname + hw.model + dev_idmax.

PoC changes

Authored from scratch (Info finding; no prior sketch). Trigger is a pure shell sequence: kldload corepower as root, capture dmesg/sysctl, kldunload, uptime.

Verified recommended fix

Reject non-Intel/non-family-06 CPUIDs explicitly at the top of corepower_probe() and use rdmsr_safe() for MSR_RAPL_POWER_UNIT in corepower_attach() (fix.diff).

Verdict

Reproduced live on the DragonFly 6.5-DEVELOPMENT audit guest (QEMU/KVM, CPUID Origin=GenuineIntel Family=0xf Model=0x6b): corepower_probe() accepts a non-family-06 CPU because the model whitelist at corepower.c:165-197 is a skip-guard (the switch is simply not entered for family != 6) rather than a reject, leaving rdmsr_safe(MSR_RAPL_POWER_UNIT) via corepower_try() (:199) as the only gate - which this VMM passes since it services the MSR. attach() then executes the raw rdmsr() at :273 and returns ENXIO at :331-332 (sc_have_sens==0). dmesg pair 'corepower0: on cpu5' / 'device_probe_and_attach: corepower0 attach returned 6' captured in run.log; no sensors, no #GP, kldload/kldunload clean, guest alive. Impact none: every sensor MSR is individually rdmsr_safe-verified at attach before the refresh path's raw reads (:289-329 gate :393/:402), so no panic path is reachable - this is a hardening finding recording the gating shape (the coretemp DF-4735 #GP family pattern) plus the raw-rdmsr-where-rdmsr_safe-was-available miss at :273.