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

```c
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 <ARAT>, 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.
