β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5067

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5067 Β· 5 files
FileTypeDescriptionSize
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.md
↓ download raw

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 β€” 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)

--- 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_testable
baseline no→ patch + rebuild →patched clean

fix.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.
↓ fix.diffper-fix-DF-5067

Confirmed kernel references

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