# DF-3889 — verdict narrative

## Finding

`boot_ps` is set only inside `kv_parse_pplib_non_clock_info`
(kv_dpm.c:2672-2675):

```c
if (rps->class & ATOM_PPLIB_CLASSIFICATION_BOOT) {
        adev->pm.dpm.boot_ps = rps;
        kv_patch_boot_state(adev, ps);
}
```

Nothing validates that a BOOT-classified state exists after parsing. With a
crafted (or simply mis-built) VBIOS whose `PowerPlayInfo` state array contains
states but none with the BOOT classification bit, `adev->pm.dpm.boot_ps` stays
NULL (amdgpu_device is kzalloc'd), and `kv_dpm_init` succeeds — `num_ps > 0`,
`enable_dpm = true`, `dpm_enabled = true` after `kv_dpm_enable`.

Sinks:

1. **`kv_dpm_disable` :1400** — `kv_update_current_ps(adev, adev->pm.dpm.boot_ps)`
   → `kv_get_ps(rps)` at :371-373 does `struct kv_ps *ps = rps->ps_priv;` — a read
   at `NULL + offsetof(struct amdgpu_ps, ps_priv)` (= 40) → page fault → panic.
   Reached from `kv_dpm_hw_fini` (:3074-3085, driver detach / GPU fini) and
   `kv_dpm_suspend` (:3087-3100, every S3 suspend) whenever `dpm_enabled`.
2. `kv_dpm_sw_init` :3026 — `current_ps = requested_ps = boot_ps` assigns NULLs
   into the framework (benign by itself; `amdgpu_dpm_change_power_state_locked`
   handles a NULL `current_ps` in `kv_check_state_equal` via the `cps == NULL`
   check at :3250-3259, and `amdgpu_dpm_print_power_state` is only called with
   `amdgpu_dpm == 1`, non-default).
3. `kv_dpm_suspend` :3096 — same NULL assignment.

This is the amdgpu `kv_dpm.c` instance of the boot_ps family filed for
si_dpm/ci_dpm/ni_dpm (DF-3586, DF-3623, DF-3646, DF-3754); pass 1 on this file
did not cover it (DF-1268/1269 are different bugs).

## Root cause (kernel references)

- `sys/dev/drm/amd/amdgpu/kv_dpm.c:1400` — NULL deref sink
- `sys/dev/drm/amd/amdgpu/kv_dpm.c:2672-2675` — only place boot_ps is set
- `sys/dev/drm/amd/amdgpu/kv_dpm.c:3026`, `:3096` — unvalidated propagation
- `sys/dev/drm/amd/amdgpu/kv_dpm.c:371-373` — kv_get_ps deref

## What was verified, and how

1. **Source-level certainty.** Full-file pass-2 audit; the amdgpu_pm.c caller
   paths (`amdgpu_dpm_change_power_state_locked` :1895-1945, `kv_check_state_equal`
   NULL handling :3250-3259, print gated on `amdgpu_dpm == 1`, default -1 per
   amdgpu_drv.c:92) traced to confirm :1400 is the live sink.
2. **Harness on the guest** (DragonFly 6.5-DEVELOPMENT x86_64, base cc 8.3):
   `boot_ps_null.c` replicates `kv_get_ps`/`kv_update_current_ps` verbatim with a
   1:1 `struct amdgpu_ps`; crash mode executes the NULL+40 read → SIGSEGV
   exit=139 (kernel equivalent: page fault in kernel mode → panic).
3. **Hardware absence.** Guest is virtio-only; the in-kernel trigger needs a KV
   APU with a crafted VBIOS. Recorded **inconclusive**, impact none *on this
   guest*; source + harness certain.

## Fix validation

`fix.diff` (a) refuses init when no BOOT state was parsed (`kv_dpm_init` returns
-EINVAL, matching the family's fix pattern) and (b) NULL-guards the :1400 call.
Not built on the guest (no AMD hardware) → `fix_status: not_testable`.

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