# DF-3954 VERDICT

**Verdict: INCONCLUSIVE at kernel level (no AMD GPU on guest) — source-certain
u32-index underflow producing a wild read at +0xBFFFFFFF4 (~48 GiB); the
verbatim expression faults in the userland simulation exactly as predicted.**

## Root cause, traced

1. **Zero-level states parse successfully.** kv_parse_power_table
   (kv_dpm.c:2665-2699): for each state, the inner loop
   `for (j = 0; j < power_state->v2.ucNumDPMLevels; j++)` —
   `clock_array_index = idx[j]; if (clock_array_index >=
   clock_info_array->ucNumEntries) continue; if (k >=
   SUMO_MAX_HARDWARE_POWERLEVELS) break;` — calls
   `kv_parse_pplib_clock_info()` (which sets `ps->num_levels = index + 1`,
   kv_dpm.c:2620) only when at least one valid entry exists. With
   `ucNumDPMLevels == 0` or all-invalid indexes, `k` stays 0 and
   `num_levels` keeps its kzalloc value 0.

2. **The type makes it a wild read, not an off-by-one.** `struct kv_ps`
   (kv_dpm.h:79-87) has `u32 num_levels` AFTER
   `struct kv_pl levels[SUMO_MAX_HARDWARE_POWERLEVELS]` (levels is the FIRST
   member). `levels[num_levels - 1]` computes the index in u32:
   0xFFFFFFFF, then widens to pointer arithmetic:
   0xFFFFFFFF × 12 = 0xBFFFFFFF4 ≈ 48 GiB — far outside any mapping.

3. **Sinks.** kv_set_valid_clock_range (kv_dpm.c:1721 in the
   vddc-table branch, :1745-1747 in the sclk_voltage_mapping branch)
   evaluates `new_ps->levels[new_ps->num_levels - 1].sclk` on every power
   state change of such a state; kv_dpm_get_sclk (kv_dpm.c:2886) evaluates
   the same for `pi->requested_rps` once that state was picked — and
   `radeon_dpm_pick_power_state` (radeon_pm.c:950-1051) matches purely on
   `ps->class`/`class2` bits from the VBIOS, never on `num_levels`.

4. **Reachability ordering.** kv_dpm_get_sclk runs from
   dce8_bandwidth_update (cik.c:9287/9327, KV is DCE8) gated only on
   `pm_method == PM_METHOD_DPM && dpm_enabled`; the requested-state copy
   (`kv_update_requested_ps`) has already happened by then via the first
   successful compute_clocks, so `num_levels == 0` (not ps_priv NULL) is what
   faults — the DF-3953 NULL window and this bug are distinct failure modes
   of the same parse weakness.

## Harness

Replicates `struct kv_pl`/`struct kv_ps` layouts (with `_Static_assert(
sizeof(struct kv_pl) == 12)`), sets `num_levels = 0` exactly as the parse
does, and executes the verbatim `ps->levels[ps->num_levels - 1].sclk` against
a guard-page-backed object: child dies SIGSEGV; analysis mode prints the
kernel-equivalent displacement `0xbfffffff4 (48.00 GiB)`.

## Why Medium and not higher

Wild *load* feeding clock comparisons; no write primitive, no direct info
leak channel, hardware+VBIOS-gated trigger, boot/mode-set-time panic (DoS).
Same class/severity precedent as pass-1 DF-1306/1307 (VBIOS-index OOB reads,
Medium) and amdgpu twin DF-3888 (Medium).

## Fix validation

fix.diff: (a) kv_parse_power_table skips-and-logs states that produced
`k == 0`, (b) belt-and-braces `num_levels == 0` early-outs in
kv_set_valid_clock_range and kv_dpm_get_sclk. Not built on the guest (no AMD
GPU) — `fix_status: not_testable`; guards are trivially sufficient by
inspection.

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