power_state_management() NULL-checks pcurrent then dereferences it anyway β latent kernel NULL-deref (NULL check at :249 routes control INTO the :254 deref; :248 passes the pointer to the smu8 backend unchecked)
Summary
The dead NULL branch forces the !equal path to deref NULL at :254, and :248 passes pcurrent unchecked into the smu8 backend which derefs unconditionally. Latent today (ps/current_ps alloc and fini together) - a landmine for any future invariant-breaking writer. Hardware-gated; early-return fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5067 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.3 KB | β raw | |
| VERDICT.md | β | 4.2 KB | β raw | |
| fix.diff | β | 529 B | view raw | |
| verdict.json | β | 3.3 KB | view raw | |
| env.txt | β | 1.8 KB | view raw |
DF-5067 β power_state_management() NULL-checks pcurrent then dereferences it anyway (latent NULL-deref)
Build
Not buildable/runnable on the audit guest: requires an AMD GPU (amdgpu attach) for
power_state_management() to execute. The guest has only QEMU std VGA
(chip=0x11111234) and /dev/dri does not exist (see env.txt).
Run
N/A β see env.txt for the documented hardware-absence evidence.
Expected behavior if the latent path ever becomes reachable
With hwmgr->ps != NULL but hwmgr->current_ps == NULL (any future writer that
breaks the current allocate-together/free-together invariant):
- pp_psm.c:248 passes NULL into phm_apply_state_adjust_rules(), whose smu8 backend
dereferences it unconditionally at smu8_hwmgr.c:1044-1045
(cast_const_smu8_power_state(&pcurrent_ps->hardware)) and reads
smu8_current_ps->action at smu8_hwmgr.c:1066 β kernel NULL-deref panic
(CZ/Stoney Ridge APUs).
- Independently, pp_psm.c:254 phm_set_power_state(hwmgr, &pcurrent->hardware, ...)
computes &((struct pp_power_state *)NULL)->hardware (small constant address) and
hands it to the backend on every ASIC β fault.
Fix
Apply fix.diff (early-return when current_ps is NULL). The fix also protects the
pass-through at :248, which the old pcurrent == NULL check at :249 never covered.
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():
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 β allocatesps,request_ps,current_psall-or-nothing; on any alloc failure it frees and NULLs all three and returns -ENOMEM;hwmgr->psis 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 byif (!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)
--- 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.
Fix verification
not_testablefix.diff adds the early-return guard; cannot be boot-validated on the audit guest because the vulnerable path requires an AMD GPU (no amdgpu attach possible on QEMU std VGA - see env.txt). Fix is a two-line invariant-preserving guard reviewed against all power_state_management() callers.
findings/poc/DF-5067/fix.diff; VERDICT.md fix section.
Confirmed kernel references
- sys/dev/drm/amd/powerplay/hwmgr/pp_psm.c:234
- sys/dev/drm/amd/powerplay/hwmgr/pp_psm.c:248
- sys/dev/drm/amd/powerplay/hwmgr/pp_psm.c:249
- sys/dev/drm/amd/powerplay/hwmgr/pp_psm.c:254
- sys/dev/drm/amd/powerplay/hwmgr/hardwaremanager.c:125
- sys/dev/drm/amd/powerplay/hwmgr/smu8_hwmgr.c:1044
- sys/dev/drm/amd/powerplay/hwmgr/smu8_hwmgr.c:1066
- sys/dev/drm/amd/powerplay/hwmgr/pp_psm.c:53
- sys/dev/drm/amd/powerplay/hwmgr/pp_psm.c:102
Detail
Evidence (decisive lines)
VERDICT.md (annotated source walk pp_psm.c:246-254 + smu8_hwmgr.c:1044-1066 deref proof + invariant analysis why it is latent); env.txt (guest pciconf/dmesg//dev/dri evidence that the path is hardware-gated); fix.diff (early-return guard).
PoC changes
No PoC exists (finding is a latent static hazard). Evidence pack is analysis-only with hardware-absence documentation.
Verified recommended fix
Early-return from power_state_management() when hwmgr->current_ps is NULL (pp_psm.c:247) so neither the :248 backend pass-through nor the :254 &pcurrent->hardware dereference can run on NULL.
Verdict
Latent NULL-deref hazard, not a live bug: power_state_management() (sys/dev/drm/amd/powerplay/hwmgr/pp_psm.c:234-257) passes hwmgr->current_ps into phm_apply_state_adjust_rules() at :248 before any NULL check (smu8 backend dereferences it unconditionally at smu8_hwmgr.c:1044-1045,1066), and explicitly acknowledges pcurrent==NULL at :249 only to route control into :254 'phm_set_power_state(hwmgr, &pcurrent->hardware, ...)' on the NULL pointer. Today the dead NULL branch is unreachable because psm_init_power_state_table() allocates ps/request_ps/current_ps all-or-nothing (pp_psm.c:53-71) and psm_fini NULLs them together (pp_psm.c:102-107); the sole guard is the implicit invariant behind the hwmgr->ps check at :270. Filed as the DF-1913/DF-4831/DF-4886 latent family. Not runnable on the audit guest: requires an AMD GPU; the QEMU guest has only std VGA (chip=0x11111234), /dev/dri absent, no amdgpu attach (env.txt).
No comments yet.