# DF-5160 — VERDICT

**Status: reproduced (driver-level). Impact: panic (privileged/boot-time DoS,
no privilege boundary crossed). Severity: Low. Confidence: likely.**

## Root cause

`sys/dev/misc/aperf/aperf.c`:

- `aperf_sensor_task()` executes `aperf = rdmsr(MSR_APERF); mperf =
  rdmsr(MSR_MPERF);` (**aperf.c:159-160**) with the raw inline-asm `rdmsr`
  (`sys/cpu/x86_64/include/cpufunc.h:528-535`) that has no #GP handling — a
  faulting MSR in kernel mode is a fatal trap.
- The task is registered with period 1s per CPU (`aperf.c:131`), and the
  FIRST invocation already performs the raw reads (`aperf.c:157-161`), so the
  panic fires ~1s after attach — at boot for a compiled-in driver.
- The only gate is `do_cpuid(0x00000006, regs); if ((regs[2] & 1) == 0)
  return;` in `aperf_identify()` (**aperf.c:88-90**). This is the
  DF-4735/DF-5155 lying-CPUID class: CPUID is not the MSR. The in-tree
  sibling `perfbias` at least guards `cpu_high < 6 || cpu_vendor_id !=
  CPU_VENDOR_INTEL` before `do_cpuid(6)` (perfbias.c:99); aperf has no
  `cpu_high` check at all, so on CPUs whose max basic leaf < 6 the documented
  Intel "echo the highest implemented leaf" behavior can feed foreign ECX
  data into the bit test (secondary, platform-dependent).
- The driver already imports the sensors framework and could use
  `rdmsr_safe()` (declared `cpufunc.h:1004`) exactly as coretemp does for
  `IA32_TEMPERATURE_TARGET`.

Reachability: `aperf` is not in `X86_64_GENERIC` (LINT64:1670 only) — a
kernel with `device aperf` on a lying platform panics ~1s after boot with
zero user interaction; otherwise root can `kldload` it. No unprivileged
trigger exists.

## How it was reproduced (three runs, one guest)

Guest: DragonFly 6.5-DEVELOPMENT #0 (Jul 2 2026) X86_64_GENERIC,
KVM, 6 vCPUs (env.txt: qemu64 "Intel" family 0xf model 0x6b;
env.2.txt: `-cpu host` = Xeon E3-1275 v5 / Skylake).

1. **Stock-driver gate observation (both CPU models).** Stock `aperf.ko`
   built -Werror from the unmodified guest source loads but never attaches:
   KVM presents CPUID.06H ECX=0 on qemu64 *and* on `-cpu host`
   (approbe/run.2.log: `max basic leaf = 0x00000016`, `06H EAX=4 ECX=0`),
   while `rdmsr_safe(0xE7/0xE8)` returns **14 (EFAULT)** on both. That is,
   the bare Skylake has the APERF/MPERF CPUID bit; KVM strips it precisely
   because it does not virtualize the MSR — the exact policy DF-5155 proved
   for leaf-6 MSRs. The CPUID gate is therefore the only protection, and it
   is the thing that lies on the vulnerable-platform class.
2. **Trigger-level (run.log/panic.txt, qemu64).** `aptest.ko` prints the
   gate state, shows `rdmsr_safe` = 14 twice, then executes the verbatim
   `aperf.c:157-161` body: `Fatal trap 9: general protection fault while in
   kernel mode ... current thread = pri 6 (CRIT) ... Stopped at
   aptest_modevent+0xd2: rdmsr` → DDB, guest down. The CRIT priority matches
   the sensor task's `crit_enter()` context.
3. **Driver-level (run.2.log/panic2.txt, -cpu host).** `lie.ko`
   (DF-4735-documented technique) force-adds `aperf` children to the cpu
   devices — substituting ONLY the missing CPUID advertisement. The stock,
   unmodified driver attaches (`aperf0..aperf5: <CPU Frequency Sensor> on
   cpuN`) and its real task traps: **`Stopped at aperf_sensor_task+0x48:
   rdmsr`** → DDB, guest down. (Six per-CPU tasks fault within the same
   second; the concurrent DDB entry garbled the trap banner itself, but the
   stock function name in the stop line is decisive.)

## Fix validation (run.3.log)

`fix.diff` (git-apply-able, `git apply --check` clean; also `patch(1)`-applied
inside the guest against `/usr/src/sys/dev/misc/aperf/aperf.c` whose md5
`efc02589720a8f59311a7d396ed325b2` matches the audit tree):

1. adds `#include <machine/md_var.h>` and an `cpu_high < 6` guard before
   `do_cpuid(6)` in identify (echo-hardening, mirrors perfbias.c:99), and
2. replaces the raw pair with `rdmsr_safe()`, skipping the sample on fault.

The patched driver built clean under `-Werror`, was loaded and force-attached
via the same `lie.ko` on the same faulting platform: all six CPUs attached,
tasks fired every second for the whole run, **no trap**, guest alive,
`hw.sensors.cpuN.freq0: unknown (cpuN freq), UNKNOWN` — the intended degraded
behavior when the MSR faults. `apfix.ko` (patched body, verbatim shape)
prints `rdmsr_safe(MSR_APERF)=14 -> sample skipped, NO TRAP`. Baseline vs
patched on the identical setup: trap-in-stock-driver vs alive-with-patched-
driver.

## Negative results (classes hunted and killed)

- **div-by-zero (mperf==0):** explicitly guarded at aperf.c:183-184; the
  diffs are unsigned wraparound-safe (aperf.c:171-172).
- **detach UAF / task lifetime:** `sensor_task_unregister2()` takes the
  per-CPU `thr->lock` and clears `running` under it (kern_sensors.c:264-266);
  the task thread runs `st->func()` under the same lock (kern_sensors.c:307,
  334) and only re-enters a task after the `running` check at
  kern_sensors.c:328-331 — no window where `aperf_sensor_task` touches `sc`
  after `aperf_detach` returns (aperf.c:141-145 stops the task before
  detaching the sensor). No UAF.
- **attach error path:** `CPU_GET_SENSDEV` NULL-checked (aperf.c:118-120);
  `sensor_task_register2` allocates with M_WAITOK (kern_sensors.c:280) so it
  cannot return a dangling/failed pointer worth a check.
- **sysctl read races:** `sensor_set` writes aligned 64-bit value/status;
  torn reads are not a memory-safety issue on x86_64 and the sensor value is
  world-readable by design (envstat).
- **integer overflow:** `aperf_diff * (tsc_diff/1000)` (aperf.c:187) can at
  worst mis-report frequency; no memory effect.
- **SMP per-CPU pinning:** task is registered per-CPU via
  `sensor_task_register2(..., cpu)` (aperf.c:131) and runs on that CPU's
  sensor thread; MSRs are read on the owning CPU. Correct.

## Bottom line

Real, driver-level-reproduced fatal trap in shipped source, same
lying-CPUID class the project has already accepted twice (DF-4735, DF-5155),
with strictly weaker reachability (LINT64-only device, root-gated load) —
Low severity, fix authored and build/boot-validated end-to-end on the guest.

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