# DF-3953 VERDICT

**Verdict: INCONCLUSIVE at kernel level (no AMD GPU on guest) — source-certain
NULL-page dereference; userland arithmetic simulation of both sinks faults
exactly as predicted (SIGSEGV at NULL+64).**

## How the bug was established

1. **boot_ps is conditionally assigned.** `kv_parse_pplib_non_clock_info()`
   (kv_dpm.c:2598-2601) sets `rdev->pm.dpm.boot_ps = rps` only when
   `rps->class & ATOM_PPLIB_CLASSIFICATION_BOOT`. A PowerPlayInfo table whose
   states carry other classifications parses successfully
   (`kv_parse_power_table` returns 0 at kv_dpm.c:2714) with `boot_ps == NULL`.

2. **The error channel that should catch this is dead.**
   `radeon_pm_init_dpm()` (radeon_pm.c:1448) calls the `radeon_dpm_init`
   macro — `rdev->asic->dpm.init(rdev)` = `kv_dpm_init()` — **without
   examining its return value**, then unconditionally calls
   `radeon_dpm_setup_asic()` and `radeon_dpm_enable()` (= `kv_dpm_enable`).

3. **Sinks.** On functioning hardware the SMC calls at the start of
   `kv_dpm_enable()` succeed, so the function reaches its last statement
   (kv_dpm.c:1277): `kv_update_current_ps(rdev, rdev->pm.dpm.boot_ps)` →
   `kv_get_ps(NULL)` (kv_dpm.c:239-244) loads `rps->ps_priv` from
   `NULL + 64` → kernel page fault → panic. Identically at kv_dpm.c:1326 in
   `kv_dpm_disable()`, reached from suspend (radeon_pm.c:1260) and from
   `radeon_pm_fini_dpm` on detach (radeon_pm.c:1694, gated only by the legacy
   `num_power_states > 1` counter), and again on resume through
   `radeon_pm_resume_dpm` → `radeon_dpm_enable` (radeon_pm.c:1321-1322).

4. **Second sink of the same NULL family (verified in harness child2).**
   `kv_dpm_get_sclk()` (kv_dpm.c:2878-2887) dereferences
   `kv_get_ps(&pi->requested_rps)` — `ps_priv` remains NULL until the first
   successful `kv_dpm_pre_set_power_state`. If
   `radeon_dpm_pick_power_state()` finds no match it returns NULL
   (radeon_pm.c:1051) and `radeon_pm_compute_clocks_dpm` returns early
   without setting it (radeon_pm.c:1075-1078); the next modeset's
   `dce8_bandwidth_update` (cik.c:9287/9327, gated only on `dpm_enabled`)
   calls `radeon_dpm_get_sclk` → `requested_state->levels[0]` with
   `requested_state == NULL` → panic.

## What the harness proves (and does not prove)

- Proves: the exact index/offset arithmetic (`offsetof(struct radeon_ps,
  ps_priv) = 64`), that the expressions as written in kv_dpm.c fault on a
  NULL `rps` (children die SIGSEGV at the predicted addresses), and the
  call-chain wiring (every hop verified in source with line cites).
- Does not prove: an on-target kernel panic — that requires KV/BT/ML APU
  hardware with a crafted VBIOS. The QEMU guest has only a virtio VGA device
  (`pciconf -lv`: vgapci0 1af4:1111) and no radeon.ko loaded.

## Why not uid0

NULL-page read at a fixed small offset; the faulting value is never used as a
write target or control flow beyond the copy in `kv_update_current_ps`
(`pi->current_rps = *rps` also faults first, at NULL+0). Impact ceiling:
panic/DoS. Not exploitable for privilege escalation.

## Fix validation

`fix.diff` authored against the read-only `sys/` tree (guards in kv_dpm.c +
note for radeon_pm.c:1448). Not built/tested: no AMD hardware on the guest
(`fix_status: not_testable`). The guard is trivially sufficient by
inspection: both NULL sinks receive `boot_ps` only after the check.

## Family context

amdgpu kv_dpm.c twin = DF-3889 (Medium, same verdict shape). radeon-wide
twins: DF-3586 (si_dpm), DF-3623 (ci_dpm), DF-3754 (ni_dpm). Distinct from
pass-1 DF-1305/1306/1307 (different indices, different sinks).

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