rs780_dpm: NULL boot_ps / skipped state parse (ucStateEntrySize==1 or no BOOT-classified state) -> NULL-pointer page-fault panic in rs780_dpm_enable and print paths
Summary
boot_ps is only assigned inside the per-state parse when a state carries the BOOT classification, and only inside the 'if (ucStateEntrySize - 1)' block - yet the parse returns success with num_ps set when no BOOT state exists or the stride skips everything. radeon_pm_init_dpm discards the init return and sets current/requested to boot_ps, then calls dpm_enable which derefs NULL->ps_priv. Harness-verified: no-BOOT-class SIGSEGV at exactly offsetof(ps_priv); stride-1 SIGSEGV at offsetof(sclk_low). RS780 twin of DF-4187/4124/4143/4118. Guard+reject-strides fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4493 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.6 KB | β raw | |
| VERDICT.md | β | 3.6 KB | β raw | |
| harness.c | β | 48.7 KB | view raw | |
| build.sh | β | 65 B | view raw | |
| run.sh | β | 121 B | view raw | |
| build.log | β | 2.0 KB | view raw | |
| run.log | β | 7.0 KB | view raw | |
| env.txt | β | 390 B | view raw | |
| fix.diff | β | 4.5 KB | view raw | |
| radeon_fix_apply.log | β | 435 B | view raw | |
| radeon_module_build.log | β | 139.5 KB | view raw | |
| verdict.json | β | 3.7 KB | view raw |
DF-4493 β rs780_dpm.c: NULL boot_ps / skipped state parse β NULL-deref page-fault panic
What
Two ways to reach a NULL rdev->pm.dpm.boot_ps (or NULL ps_priv) that the
DPM enable path dereferences without any check:
-
No BOOT-classified state (rs780_dpm.c:741-742 sets
boot_psonly whenrps->class & ATOM_PPLIB_CLASSIFICATION_BOOT). A PowerPlayInfo table whose non-clock infos lack the BOOT bit parses "successfully" (rs780_parse_power_table()returns 0, :843), leavingboot_ps == NULL.radeon_pm_init_dpm()(radeon_pm.c:1448) discards the dpm_init return, setscurrent_ps = requested_ps = boot_ps(:1449) and callsradeon_dpm_enable()(:1453) βrs780_dpm_enable()fetchesboot_ps(:600) and passes it tors780_initialize_dpm_parameters()(:608) βrs780_initialize_dpm_power_state()doesdefault_state = rs780_get_ps(boot_ps)(:78) β NULL->ps_priv read at address 0x28 β page-fault panic. -
ucStateEntrySize == 1: the guardif (power_info->pplib.ucStateEntrySize - 1)(:822) is false, so NO state is parsed at all β everyps[i].ps_privstays NULL andboot_psis never set β yetnum_ps = ucNumStatesis still assigned (:842) and the function returns success. Any laterrs780_get_ps()consumer derefs NULL:rs780_dpm_print_power_state()(:946ps->sclk_low, address 0x8),rs780_dpm_set_power_state()(:567-568),rs780_dpm_get_sclk()(:965-970),rs780_dpm_force_performance_level()(:1031).
Family: DF-4187 (sumo), DF-4124 (rv6xx), DF-4143 (trinity), DF-4118 (cypress) β rs780 is the last sibling with the same shape.
Reproduce
./build.sh ./run.sh # modes 4493 (no BOOT state) and 4493b (ucStateEntrySize==1)
Expected baseline (run.log):
- mode 4493:
boot_ps=0x0then*** SIGNAL 11 at address 0x28 ***, exit=99 - mode 4493b:
ps[0].ps_priv=0x0then*** SIGNAL 11 at address 0x8 ***, exit=99
Expected with harness_fixed: mode 4493 prints
DRM_ERROR: no boot power state, refusing to enable DPM (enable guard),
mode 4493b rejects the table at parse (invalid state entry size 1),
both exit=0.
Kernel-level reproduction is hardware-gated (no RS780 IGP in the QEMU guest); the harness is a verbatim replica β see VERDICT.md.
Fix
fix.diff (in this directory): rs780_dpm_enable() refuses to run without
a boot power state, and the parse rejects ucStateEntrySize < 2 (an
ATOM_PPLIB_STATE is 2 bytes). Validated: radeon.ko builds under -Werror
after applying the fix to the guest's /usr/src (radeon_module_build.log,
MAKE_RC=0).
DF-4493 β VERDICT
Finding: NULL boot_ps / never-parsed states (ucStateEntrySize == 1 or
missing BOOT classification) β NULL-pointer page-fault panic in the rs780
DPM enable/print paths.
Status: REPRODUCED at logic level (verbatim replica), 2 attempts. Kernel-level trigger hardware-gated (no RS780/RS880 IGP in the QEMU guest); family precedent DF-4187/DF-4124/DF-4143/DF-4118 verified identically.
Reachability chain (all cited)
radeon_pm_init_dpm()βradeon_dpm_init(rdev);at radeon_pm.c:1448 is a bare statement: the return value ofrs780_dpm_init()is discarded, so parse "success" is not even required β but in these two scenarios the parse genuinely returns 0 (rs780_dpm.c:843).- radeon_pm.c:1449
current_ps = requested_ps = boot_ps(NULL). - radeon_pm.c:1453
radeon_dpm_enable(rdev)β rs780_dpm.c:600boot_ps = rdev->pm.dpm.boot_ps(NULL) β :608rs780_initialize_dpm_parameters(rdev, NULL)β :78rs780_get_ps(NULL)βrps->ps_privload from NULL+40 (0x28).
Variant B (ucStateEntrySize == 1): rs780_dpm.c:822
if (power_info->pplib.ucStateEntrySize - 1) evaluates 1 - 1 == 0 β the
entire per-state parse (kzalloc igp_ps, ps_priv attach, boot_ps assignment
at :741-742) is skipped, while :842 still sets num_ps = ucNumStates and
the function returns 0. Consumers then deref the NULL ps_priv:
rs780_dpm_print_power_state() :946 (ps->sclk_low, NULL+8),
rs780_dpm_set_power_state() :567-568, rs780_dpm_get_sclk() :965-970,
rs780_dpm_force_performance_level() :1031.
Observed (run.log)
===== mode 4493 ===== rs780_parse_power_table() = 0 num_ps=1 boot_ps=0x0 calling rs780_dpm_enable() with boot_ps == NULL ... *** SIGNAL 11 at address 0x28: SIGSEGV ... (kernel: page-fault PANIC) *** exit=99 ===== mode 4493b ===== rs780_parse_power_table() = 0 num_ps=1 boot_ps=0x0 ps[0].ps_priv=0x0 radeon_dpm_print_power_states() walks 1 states whose ps_priv are NULL ... *** SIGNAL 11 at address 0x8: SIGSEGV ... *** exit=99
Address 0x28 is exactly offsetof(struct radeon_ps, ps_priv) (verified in
the harness against the kernel layout radeon.h:1342-1357); address 0x8 is
offsetof(struct igp_ps, sclk_low) (rs780_dpm.h:52-58). The harness is
compiled with -fno-delete-null-pointer-checks β the same semantics the
kernel relies on β so the loads trap instead of being folded away.
Fix validation
fix.diff: (a) rs780_dpm_enable() returns -EINVAL when
boot_ps == NULL; (b) parse rejects ucStateEntrySize < 2. Fixed harness:
DRM_ERROR: no boot power state, refusing to enable DPM rs780_dpm_enable() returned -22 - NO CRASH (fix active) exit=0 DRM_ERROR: invalid state entry size 1 rs780_parse_power_table() = -22 ... crafted table rejected exit=0
Applied to the guest /usr/src: radeon_fix_apply.log (7 hunks),
radeon_module_build.log β radeon.ko links under -Werror, MAKE_RC=0.
Guest source reverted (md5 verified) and guest reset afterwards.
Attempts
- First run showed "NO CRASH": the userspace stub's dead outputs let GCC
eliminate the NULL loads (UB folding). Fixed by compiling with
-fno-delete-null-pointer-checksand feeding the loaded sclk into a volatile sink β restoring kernel trap semantics. - Decisive run in run.log.
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
fixedharness_fixed 4493 returns -EINVAL before any deref ('no boot power state'); 4493b rejected at parse ('invalid state entry size 1'); combined fix builds radeon.ko under -Werror (MAKE_RC=0)
run.log fixed sections for modes 4493/4493b (exit=0, canary_violations=0); radeon_fix_apply.log hunk 1 (enable guard) and hunk 4 (entry-size check); radeon_module_build.log MAKE_RC=0
Confirmed kernel references
Detail
Exploit chain
crafted VBIOS PowerPlayInfo (no BOOT class, or ucStateEntrySize==1) -> parse returns success with boot_ps/ps_priv NULL -> radeon_pm.c:1448 swallows nothing to swallow (parse succeeded) -> dpm enable runs -> NULL->ps_priv read at 0x28 -> kernel page-fault panic (local DoS on probe / first mode set / radeon_dpm=1 boot print)
Evidence (decisive lines)
run.log '===== mode 4493 =====': 'boot_ps=0x0' then 'SIGNAL 11 at address 0x28', exit=99; '===== mode 4493b =====': 'ps[0].ps_priv=0x0' then 'SIGNAL 11 at address 0x8', exit=99; fixed sections: 'DRM_ERROR: no boot power state, refusing to enable DPM' / 'DRM_ERROR: invalid state entry size 1', exit=0; radeon_module_build.log MAKE_RC=0 under -Werror
PoC changes
wrote harness from scratch (no seed); -fno-delete-null-pointer-checks + volatile clock sink required so the NULL loads trap with kernel semantics instead of being folded by GCC
Verified recommended fix
refuse to enable DPM without a boot power state (boot_ps NULL check in rs780_dpm_enable) and reject ucStateEntrySize < 2 in rs780_parse_power_table, as in fix.diff
Verdict
Reproduced at logic level with the verbatim replica (harness modes 4493/4493b): a PowerPlayInfo table with no BOOT-classified state leaves rdev->pm.dpm.boot_ps NULL (rs780_dpm.c:741-742 is the only assignment), radeon_pm_init_dpm discards the init return (radeon_pm.c:1448) and still calls radeon_dpm_enable (:1453) -> rs780_get_ps(NULL) reads NULL->ps_priv at 0x28 -> SIGSEGV (kernel: page-fault panic); ucStateEntrySize==1 makes the if (ucStateEntrySize - 1) guard (:822) skip every state while num_ps is still set (:842), leaving all ps_priv NULL -> rs780_dpm_print_power_state :946 derefs ps->sclk_low at 0x8 -> SIGSEGV. Fault addresses match the kernel struct layouts exactly (offsetof radeon_ps.ps_priv = 40, igp_ps.sclk_low = 8). RS780 twin of DF-4187 (sumo) / DF-4124 (rv6xx) / DF-4143 (trinity) / DF-4118 (cypress). Kernel-level repro hardware-gated (no RS780 IGP in the QEMU guest). Fix proven: enable refuses NULL boot_ps; parse rejects ucStateEntrySize < 2; radeon.ko builds under -Werror (MAKE_RC=0).
No comments yet.