# VERDICT — DF-5067 (pass 2, df-bsd-reviewer)

Status: **untested** (hardware-gated; not runnable on the audit guest). Impact
assessed statically: **panic (kernel NULL-deref) if the latent branch ever becomes
reachable**. Not a live bug today — this is a latent-hazard filing in the same
family as DF-1913 / DF-4831 / DF-4886.

## Where the defect is (path:line)

`sys/dev/drm/amd/powerplay/hwmgr/pp_psm.c:234-257` — `power_state_management()`:

```c
246: 	pcurrent = hwmgr->current_ps;
247:
248: 	phm_apply_state_adjust_rules(hwmgr, requested, pcurrent);          /* (1) */
249: 	if (pcurrent == NULL || (0 != phm_check_states_equal(hwmgr,
250: 			&pcurrent->hardware, &requested->hardware, &equal)))
251: 		equal = false;
252:
253: 	if (!equal || phm_check_smc_update_required_for_display_configuration(hwmgr)) {
254: 		phm_set_power_state(hwmgr, &pcurrent->hardware, &requested->hardware);  /* (2) */
```

(1) `pcurrent` is passed to the backend **before any NULL check**. The dispatcher
    `phm_apply_state_adjust_rules()` (hardwaremanager.c:125-138) forwards the 3rd
    argument verbatim; the smu8 backend dereferences it unconditionally:
    smu8_hwmgr.c:1044-1045
    `const struct smu8_power_state *smu8_current_ps = cast_const_smu8_power_state(&pcurrent_ps->hardware);`
    and smu8_hwmgr.c:1066 `smu8_ps->action = smu8_current_ps->action;`
    (smu7/vega10 backends merely ignore it — verified smu7_hwmgr.c:2880-3010).

(2) The code explicitly anticipates `pcurrent == NULL` at :249 and deliberately sets
    `equal = false` in that case — which makes :253 take the `!equal` branch — and
    :254 then computes `&pcurrent->hardware` on the NULL pointer. A NULL check that
    routes control flow *into* the dereference is the inverted-guard hazard.

## Why it is latent (reachability analysis)

`power_state_management()` is only called from `psm_adjust_power_state_dynamic()`
under `if (hwmgr->ps)` (pp_psm.c:270-271). The only writers of the trio are:

- `psm_init_power_state_table()` pp_psm.c:53-71 — allocates `ps`, `request_ps`,
  `current_ps` all-or-nothing; on any alloc failure it frees and NULLs all three
  and returns -ENOMEM; `hwmgr->ps` is only published after all three allocations
  succeed (line 53 happens to precede, but the failure paths NULL it again).
- `psm_fini_power_state_table()` pp_psm.c:102-107 — frees and NULLs all three
  together; second call is guarded by `if (!hwmgr->ps) return 0;` (:99-100).

So today `hwmgr->ps != NULL ⟹ hwmgr->current_ps != NULL`; the NULL branch at :249
is dead code and the hazard cannot fire. That invariant is implicit only — no
assertion, no comment — and a single future assignment (or a partial-reinit path)
turns it into a NULL-deref panic. Exactly the DF-1913/DF-4831/DF-4886 pattern
(latent unchecked pointer acknowledged then used).

## Why untested on the guest

`power_state_management()` runs only after amdgpu attach + `hwmgr_hw_init()`
(hwmgr.c:238-254). The QEMU guest has no AMD display device
(pciconf: only vgapci0 chip=0x11111234 QEMU std VGA; /dev/dri absent; dmesg shows
no amdgpu/radeon/inteldrm attach — see env.txt). No runtime trigger is possible;
no fix-validation kernel rebuild is meaningful without the hardware.

## Fix (fix.diff)

```diff
--- a/sys/dev/drm/amd/powerplay/hwmgr/pp_psm.c
+++ b/sys/dev/drm/amd/powerplay/hwmgr/pp_psm.c
@@ -246,6 +246,9 @@ static void power_state_management(struct pp_hwmgr *hwmgr,
 		requested = hwmgr->request_ps;
 
 	pcurrent = hwmgr->current_ps;
+	if (pcurrent == NULL)
+		return;
 
 	phm_apply_state_adjust_rules(hwmgr, requested, pcurrent);
 	if (pcurrent == NULL || (0 != phm_check_states_equal(hwmgr,
```

Early-return when there is no current state: no state transition can be diffed or
programmed against a nonexistent current state anyway, and the early return also
covers the unchecked pass-through at :248. (Optionally simplify :249 to drop the
now-dead NULL test.)

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