boot_ps NULL dereference panic in kv_dpm_enable/kv_dpm_disable (plus kv_dpm_get_sclk ps_priv-NULL window) when VBIOS power table has no BOOT-classified state
Summary
kv_parse_pplib_non_clock_info only assigns boot_ps for a BOOT-classified state; a VBIOS power table without one parses successfully with boot_ps==NULL, and radeon_pm_init_dpm DISCARDS kv_dpm_init()'s return value so DPM enables anyway. enable/disable call kv_update_current_ps(rdev, NULL) -> kv_get_ps(NULL) reads rps->ps_priv at NULL+64 -> panic. Family sink: kv_dpm_get_sclk dereferences the requested ps_priv which stays NULL when pick_power_state returned NULL; next dce8_bandwidth_update faults. VERIFIED: verbatim-expression harnesses on the guest - both children SIGSEGV as predicted (NULL+64). Guaranteed boot/suspend/detach panic on KV/BT/ML APU hardware with crafted VBIOS. amdgpu twin DF-3889.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3953 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 3.9 KB | β raw | |
| trigger_df3953.c | β | 5.5 KB | view raw | |
| build.sh | β | 92 B | view raw | |
| run.sh | β | 116 B | view raw | |
| build.log | β | 249 B | view raw | |
| run.log | β | 2.2 KB | view raw | |
| env.txt | β | 725 B | view raw | |
| fix.diff | β | 1.8 KB | view raw | |
| manifest.json | β | 1.7 KB | view raw | |
| verdict.json | β | 3.5 KB | view raw |
DF-3953 β radeon kv_dpm boot_ps NULL dereference (kv_dpm_enable / kv_dpm_disable)
File: sys/dev/drm/radeon/kv_dpm.c (pass 2, GLM 5.3)
Severity: Medium Β· CWE: CWE-476 (NULL pointer dereference) Β· Confidence: certain (source)
Status: inconclusive on this guest (no AMD GPU β hardware-gated), arithmetic-simulation harness verified.
What
rdev->pm.dpm.boot_ps stays NULL when the VBIOS PowerPlayInfo table parses
successfully but contains no state classified ATOM_PPLIB_CLASSIFICATION_BOOT.
Nothing validates its existence, and radeon_pm_init_dpm()
(radeon_pm.c:1448) discards kv_dpm_init()'s return value, so DPM
enable/disable run regardless:
kv_dpm_enable()end:kv_update_current_ps(rdev, rdev->pm.dpm.boot_ps)β kv_dpm.c:1277kv_dpm_disable()end (suspend viaradeon_pm_suspend_dpmradeon_pm.c:1260, detach viaradeon_pm_fini_dpmradeon_pm.c:1694): same call β kv_dpm.c:1326- Family sink:
kv_dpm_get_sclk()kv_dpm.c:2881 dereferencespi->requested_rps.ps_priv, which stays NULL wheneverradeon_dpm_pick_power_state()returned NULL (radeon_pm.c:1051) and compute_clocks bailed (radeon_pm.c:1075-1078); the nextdce8_bandwidth_update(cik.c:9287/9327) faults throughlevels[0].
All dereference rps->ps_priv at NULL + 64 via kv_get_ps(NULL)
(kv_dpm.c:239-244) β kernel page fault on the NULL page β panic.
Reproduce
cd findings/poc/DF-3953 ./build.sh # cc -O2 -Wall -o trigger_df3953 trigger_df3953.c ./run.sh # analysis mode (exit 0), then crash mode
Expected (crash mode): both children die SIGSEGV sig=11 reproducing the
verbatim NULL + offsetof(struct radeon_ps, ps_priv) access; final line
RESULT: BOTH SINKS FAULT AS PREDICTED (rc=0). Kernel equivalent: panic at
boot / S3 suspend / driver detach on a KV APU whose VBIOS power table lacks a
BOOT-classified state.
Threat model
Attacker controls the GPU VBIOS image (physical replacement, rom flashing as root, or firmware supply chain). Result: guaranteed boot/suspend/detach panic (local DoS of the machine). No unprivileged-local or remote vector. Same family as amdgpu DF-3889 (Medium), si_dpm DF-3586, ci_dpm DF-3623, ni_dpm DF-3754 β not covered by pass-1 DF-1305/1306/1307 on this file.
Fix
Guard boot_ps in kv_dpm.c (fix.diff) and stop discarding kv_dpm_init()'s
return value in radeon_pm.c:1448 (noted in fix.diff header; radeon_pm.c is
outside this file's audited scope).
DF-3953 VERDICT
Verdict: INCONCLUSIVE at kernel level (no AMD GPU on guest) β source-certain NULL-page dereference; userland arithmetic simulation of both sinks faults exactly as predicted (SIGSEGV at NULL+64).
How the bug was established
-
boot_ps is conditionally assigned.
kv_parse_pplib_non_clock_info()(kv_dpm.c:2598-2601) setsrdev->pm.dpm.boot_ps = rpsonly whenrps->class & ATOM_PPLIB_CLASSIFICATION_BOOT. A PowerPlayInfo table whose states carry other classifications parses successfully (kv_parse_power_tablereturns 0 at kv_dpm.c:2714) withboot_ps == NULL. -
The error channel that should catch this is dead.
radeon_pm_init_dpm()(radeon_pm.c:1448) calls theradeon_dpm_initmacro βrdev->asic->dpm.init(rdev)=kv_dpm_init()β without examining its return value, then unconditionally callsradeon_dpm_setup_asic()andradeon_dpm_enable()(=kv_dpm_enable). -
Sinks. On functioning hardware the SMC calls at the start of
kv_dpm_enable()succeed, so the function reaches its last statement (kv_dpm.c:1277):kv_update_current_ps(rdev, rdev->pm.dpm.boot_ps)βkv_get_ps(NULL)(kv_dpm.c:239-244) loadsrps->ps_privfromNULL + 64β kernel page fault β panic. Identically at kv_dpm.c:1326 inkv_dpm_disable(), reached from suspend (radeon_pm.c:1260) and fromradeon_pm_fini_dpmon detach (radeon_pm.c:1694, gated only by the legacynum_power_states > 1counter), and again on resume throughradeon_pm_resume_dpmβradeon_dpm_enable(radeon_pm.c:1321-1322). -
Second sink of the same NULL family (verified in harness child2).
kv_dpm_get_sclk()(kv_dpm.c:2878-2887) dereferenceskv_get_ps(&pi->requested_rps)βps_privremains NULL until the first successfulkv_dpm_pre_set_power_state. Ifradeon_dpm_pick_power_state()finds no match it returns NULL (radeon_pm.c:1051) andradeon_pm_compute_clocks_dpmreturns early without setting it (radeon_pm.c:1075-1078); the next modeset'sdce8_bandwidth_update(cik.c:9287/9327, gated only ondpm_enabled) callsradeon_dpm_get_sclkβrequested_state->levels[0]withrequested_state == NULLβ panic.
What the harness proves (and does not prove)
- Proves: the exact index/offset arithmetic (
offsetof(struct radeon_ps, ps_priv) = 64), that the expressions as written in kv_dpm.c fault on a NULLrps(children die SIGSEGV at the predicted addresses), and the call-chain wiring (every hop verified in source with line cites). - Does not prove: an on-target kernel panic β that requires KV/BT/ML APU
hardware with a crafted VBIOS. The QEMU guest has only a virtio VGA device
(
pciconf -lv: vgapci0 1af4:1111) and no radeon.ko loaded.
Why not uid0
NULL-page read at a fixed small offset; the faulting value is never used as a
write target or control flow beyond the copy in kv_update_current_ps
(pi->current_rps = *rps also faults first, at NULL+0). Impact ceiling:
panic/DoS. Not exploitable for privilege escalation.
Fix validation
fix.diff authored against the read-only sys/ tree (guards in kv_dpm.c +
note for radeon_pm.c:1448). Not built/tested: no AMD hardware on the guest
(fix_status: not_testable). The guard is trivially sufficient by
inspection: both NULL sinks receive boot_ps only after the check.
Family context
amdgpu kv_dpm.c twin = DF-3889 (Medium, same verdict shape). radeon-wide twins: DF-3586 (si_dpm), DF-3623 (ci_dpm), DF-3754 (ni_dpm). Distinct from pass-1 DF-1305/1306/1307 (different indices, different sinks).
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 the guest.
findings/poc/DF-3953/fix.diff
Confirmed kernel references
Detail
Exploit chain
crafted-VBIOS power table without BOOT classification -> DPM enabled with boot_ps==NULL -> boot/suspend/resume/detach -> kv_update_current_ps(NULL) -> NULL+64 page fault -> panic (DoS). No uid0 path: NULL-page read only, value never used as write target.
Evidence (decisive lines)
run.log: 'offsetof(struct radeon_ps, ps_priv) = 64', 'child1 ... SIGNALLED sig=11', 'child2 (kv_dpm_get_sclk :2881): SIGNALLED sig=11', 'RESULT: BOTH SINKS FAULT AS PREDICTED (rc=0)'; VERDICT.md traces every hop; fix.diff
PoC changes
No seed (pass-2 self-authored). Two fix cycles: nested comment broke the first build; first crash run had child1's fault optimized away (dead store) - made the loaded pointer volatile so the faulting load is observable.
Verified recommended fix
Guard boot_ps in kv_dpm_enable/kv_dpm_disable and NULL-check requested_state in kv_dpm_get_sclk (fix.diff); additionally honor kv_dpm_init()'s return value at radeon_pm.c:1448.
Verdict
Source-certain NULL-pointer dereference (radeon-kv boot_ps family; amdgpu twin is DF-3889). kv_parse_pplib_non_clock_info (kv_dpm.c:2598-2601) only assigns rdev->pm.dpm.boot_ps for an ATOM_PPLIB_CLASSIFICATION_BOOT state; nothing validates one exists, and radeon_pm.c:1448 DISCARDS kv_dpm_init()'s return value so DPM is enabled regardless. kv_dpm_enable (:1277) and kv_dpm_disable (:1326, suspend radeon_pm.c:1260 / fini radeon_pm.c:1694 / resume radeon_pm.c:1321-1322) call kv_update_current_ps(rdev, NULL) -> kv_get_ps(NULL) (:239-244) reads rps->ps_priv at NULL+64 -> page fault -> panic. Additional sink: kv_dpm_get_sclk (:2881) dereferences pi->requested_rps.ps_priv which stays NULL when radeon_dpm_pick_power_state returned NULL (radeon_pm.c:1051) and compute_clocks bailed (:1075-1078); next dce8_bandwidth_update (cik.c:9287/9327) faults. Harness executes the verbatim NULL+64 deref in children -> SIGSEGV sig=11 both sinks (kernel equivalent: panic). Runtime-inconclusive on this guest: virtio VGA only, no radeon hardware.
No comments yet.