# 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.
