boot_ps NULL dereference in kv_dpm_disable (kv_update_current_ps(adev, NULL) -> kv_get_ps(NULL)) when the VBIOS power table has no BOOT-classified state β panic on suspend/detach
Summary
kv_parse_pplib_non_clock_info only assigns boot_ps for a BOOT-classified state; nothing validates one exists after parsing. A crafted VBIOS with states but none BOOT-classified leaves boot_ps NULL while DPM inits and enables normally (the NULL flows into current_ps/requested_ps). On the next suspend or detach, kv_dpm_disable calls kv_update_current_ps(adev, NULL) -> kv_get_ps(NULL) reads rps->ps_priv at NULL+40 -> panic. amdgpu kv_dpm instance of the boot_ps family (DF-3586/3610/3646/3623/3754). VERIFIED: verbatim NULL+40 harness (offsetof proof + SIGSEGV crash mode).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3889 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| boot_ps_null.c | β | 2.6 KB | view raw | |
| build.sh | β | 95 B | view raw | |
| run.sh | β | 116 B | view raw | |
| build.log | β | 107 B | view raw | |
| run.log | β | 184 B | view raw | |
| run.2.log | β | 221 B | view raw | |
| env.txt | β | 561 B | view raw | |
| fix.diff | β | 613 B | view raw | |
| VERDICT.md | β | 3.3 KB | β raw | |
| README.md | β | 1.9 KB | β raw | |
| verdict.json | β | 2.7 KB | view raw |
DF-3889 β boot_ps NULL dereference in kv_dpm_disable when the VBIOS power table has no BOOT-classified state
File: sys/dev/drm/amd/amdgpu/kv_dpm.c:1400 (sink: kv_update_current_ps(adev, adev->pm.dpm.boot_ps)), :3026 (unvalidated NULL assignment in sw_init)
Class: CWE-476 NULL-pointer dereference (boot_ps family: DF-3586/3610/3623/3646/3754 twins β this is the amdgpu kv_dpm instance, unfixed)
Verdict: inconclusive at runtime on this guest (no AMD GPU); source-certain; harness proves the verbatim NULL+40 deref β SIGSEGV.
Build (on the DragonFly guest, as root)
mkdir -p /tmp/DF-3889 && cp boot_ps_null.c /tmp/DF-3889/h.c cd /tmp/DF-3889 && cc -Wall -O0 -o t h.c
Run
./t # accounting β dereference site and offset (ps_priv at +40) ./t crash # verbatim kv_get_ps(NULL) -> SIGSEGV (exit 139)
Expected
- accounting:
offsetof(struct amdgpu_ps, ps_priv) = 40;kv_dpm_disable (:1400) -> kv_update_current_ps(adev, NULL) -> kv_get_ps(NULL) (:371-373): rps->ps_priv reads *(void**)(NULL + 40). - crash:
Segmentation fault, exit=139 (kernel equivalent: page fault in kernel mode β panic).
Kernel trigger
kv_parse_pplib_non_clock_info (kv_dpm.c:2672-2675) only assigns
adev->pm.dpm.boot_ps for a state with ATOM_PPLIB_CLASSIFICATION_BOOT. A VBIOS
power table without such a state leaves boot_ps == NULL while DPM otherwise
inits and runs (num_ps > 0, dpm_enabled = true). Then:
- suspend/resume or driver detach β
kv_dpm_hw_fini/kv_dpm_suspendβkv_dpm_disableβkv_update_current_ps(adev, NULL)(kv_dpm.c:1400) βkv_get_ps(NULL)(kv_dpm.c:371-373) βrps->ps_privat NULL+40 β panic; kv_dpm_sw_init(:3026) andkv_dpm_suspend(:3096) also propagate the NULL intocurrent_ps/requested_ps(benign assignments here, but the same unvalidated assumption).
DF-3889 β verdict narrative
Finding
boot_ps is set only inside kv_parse_pplib_non_clock_info
(kv_dpm.c:2672-2675):
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:
kv_dpm_disable:1400 βkv_update_current_ps(adev, adev->pm.dpm.boot_ps)βkv_get_ps(rps)at :371-373 doesstruct kv_ps *ps = rps->ps_priv;β a read atNULL + offsetof(struct amdgpu_ps, ps_priv)(= 40) β page fault β panic. Reached fromkv_dpm_hw_fini(:3074-3085, driver detach / GPU fini) andkv_dpm_suspend(:3087-3100, every S3 suspend) wheneverdpm_enabled.kv_dpm_sw_init:3026 βcurrent_ps = requested_ps = boot_psassigns NULLs into the framework (benign by itself;amdgpu_dpm_change_power_state_lockedhandles a NULLcurrent_psinkv_check_state_equalvia thecps == NULLcheck at :3250-3259, andamdgpu_dpm_print_power_stateis only called withamdgpu_dpm == 1, non-default).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 sinksys/dev/drm/amd/amdgpu/kv_dpm.c:2672-2675β only place boot_ps is setsys/dev/drm/amd/amdgpu/kv_dpm.c:3026,:3096β unvalidated propagationsys/dev/drm/amd/amdgpu/kv_dpm.c:371-373β kv_get_ps deref
What was verified, and how
- 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_equalNULL handling :3250-3259, print gated onamdgpu_dpm == 1, default -1 per amdgpu_drv.c:92) traced to confirm :1400 is the live sink. - Harness on the guest (DragonFly 6.5-DEVELOPMENT x86_64, base cc 8.3):
boot_ps_null.creplicateskv_get_ps/kv_update_current_psverbatim with a 1:1struct amdgpu_ps; crash mode executes the NULL+40 read β SIGSEGV exit=139 (kernel equivalent: page fault in kernel mode β panic). - 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.
Fix verification
not_testablefix.diff authored against the read-only sys/ tree after source+harness verification; not built (no AMD hardware on guest).
findings/poc/DF-3889/fix.diff
Confirmed kernel references
Detail
Exploit chain
crafted-VBIOS power table without BOOT classification -> dpm_enabled with boot_ps==NULL -> S3 suspend / driver detach -> kv_dpm_disable -> kv_get_ps(NULL) -> kernel page fault -> panic (DoS).
Evidence (decisive lines)
run.log: 'offsetof(struct amdgpu_ps, ps_priv) = 40', 'kv_dpm_disable (:1400) ... reads *(void**)(NULL + 40)'; run.2.log: crash mode + Segmentation fault exit=139; VERDICT.md; fix.diff
PoC changes
No seed (pass-2 self-authored); single guest build+run cycle succeeded.
Verified recommended fix
Fail kv_dpm_init when the parsed power table has no BOOT-classified state, and NULL-guard the kv_update_current_ps call in kv_dpm_disable (:1400).
Verdict
Source-certain NULL-pointer dereference (boot_ps family, DF-3586/3623/3646/3754 amdgpu-kv instance); runtime-inconclusive on this guest (no AMD GPU). kv_parse_pplib_non_clock_info (kv_dpm.c:2672-2675) only assigns adev->pm.dpm.boot_ps for an ATOM_PPLIB_CLASSIFICATION_BOOT state and nothing validates one exists; a VBIOS power table without it leaves boot_ps NULL while DPM otherwise inits and enables. On the next suspend or driver detach, kv_dpm_hw_fini/kv_dpm_suspend -> kv_dpm_disable (:1400) calls kv_update_current_ps(adev, NULL) -> kv_get_ps(NULL) (:371-373) reads rps->ps_priv at NULL+40 -> page fault -> panic. sw_init (:3026) and suspend (:3096) also propagate the NULL into current_ps/requested_ps. Harness executes the verbatim NULL+40 deref -> SIGSEGV exit=139 (kernel equivalent: panic).
No comments yet.