# DF-4903 — VERDICT

**status: untested · reproduced: 0 · impact: leak (kernel-heap info leak, 4 bytes, unpriv-readable sysctl) · confidence: likely**

## Narrative

Pass-2 audit of `sys/dev/powermng/powernow/powernow.c` found that the
"current frequency" plumbing leaks an uninitialised kernel-heap word when
the CPU's live fid/vid does not match any decoded PST entry:

* `k8_get_curfreq()` returns `-1` on mismatch (powernow.c:223).
* `powernow_init()` passes that `-1` straight into
  `k8_powernow_setperf(unsigned int freq)` (powernow.c:227, call at :392),
  where it becomes `0xFFFFFFFF`.
* Every `state_table[i].freq >= freq` comparison (powernow.c:246)
  compares an `int` against that unsigned value — all false — so `i`
  exits the loop equal to `n_states` with `fid=vid=0`.
* The tail `cpuspeed = cstate->state_table[i].freq;` (powernow.c:310-311)
  then reads `state_table[n_states]`: for `n_states < 16` that entry was
  never initialised (`kmalloc` without M_ZERO at powernow.c:366; decode
  fills only `n_states` entries, powernow.c:143-162).
* `cpuspeed` is world-readable at `machdep.powernow.frequency.current`
  (powernow.c:337, registered :417-420).  DragonFly only restricts sysctl
  *writes* (`kern_sysctl.c:1446`), so any local user can read the leak.

Not DF-1960: DF-1960 is the "PSB present but no PST matches ⇒ whole table
uninitialised" case.  DF-4903 is a different root cause — the table is
properly decoded; the `-1` sentinel type-confuses the unsigned match loop
and indexes one past the initialised prefix.

## Verification attempts

1. **Guest gate check** — the audit guest is `QEMU Virtual CPU version
   2.5+` (Intel-class).  `kldload powernow` → console:
   `module_register_init: MOD_LOAD (powernow, ...) error 2` — the CPUID
   0x80000007 gate (powernow.c:361-363) refuses the module, no sysctls
   are created, and no MSR/PSB code executes.  Runtime reproduction of an
   AMD-K8-only path is impossible on this guest ⇒ honest
   `untested` (kernel side).
2. **Logic harness** (`sim_tailread.c`) — exact replica of the struct
   layout, the `-1` sentinel, the unsigned comparison, and the tail
   read; heap poisoned with `0xA5` to emulate `kmalloc(M_DEVBUF)` reuse.
   Compiled with guest `cc 8.3 [DragonFly]` and run as `uid=1001(maxx)`:

   ```
   k8_get_curfreq() = -1   (boot cfid/cvid not in table)
   cpuspeed after init = 0xa5a5a5a5 (-1515870811)
   LEAK CONFIRMED (logic): uninitialised heap word read at
   state_table[n_states=3] landed in the world-readable
   sysctl machdep.powernow.frequency.current
   ```

   This proves the code path and the type confusion concretely; it is a
   userland simulation and is **not** counted as a kernel reproduction.

## Exploit chain

Passive 4-byte heap-info leak, once per module load (or per any future
setperf caller passing an out-of-table frequency).  Value is residual
slab data from a prior `M_DEVBUF` allocation; not attacker-positioned
reliably ⇒ info value limited; no escalation path from this primitive
alone.

## Fix validation

Not run — kernel path not executable on the Intel/QEMU guest
(`fix_status: not_testable`).  `fix.diff` is authored from line-accurate
root-cause knowledge: bail out of `k8_powernow_setperf()` when the match
loop ran off the end, and only call it from `powernow_init()` with a
validated frequency.

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