ucNumDPMLevels==0 pplib state leaves ps->num_levels==0: levels[num_levels-1] == levels[0xFFFFFFFF] wild read ~96 GB past the kzalloc'd sumo_ps (deterministic page-fault panic) in set_uvd_clock before/after (:847/:865), program_bsp (:350), program_at (:426), get_sclk (:1891) β sumo twin of DF-4145/3954/3888
Summary
num_levels is only advanced per parsed clock level; a state with ucNumDPMLevels==0 (u8, unvalidated) keeps num_levels==0 (only BOOT states get patched to 1). num_levels is u32 and levels[] is the FIRST member, so every 'last level' access levels[num_levels-1] indexes levels[0xFFFFFFFF] = object + ~96 GB. program_bsp additionally underflows its write loop to 2^32 MMIO writes and feeds garbage into the DF-1438 div0 sink. Any DPM state change selecting the crafted state panics. Harness-proven (SIGSEGV at exactly &requested_ps + 0xFFFFFFFF*24; fixed rejects). Reject-zero-level-states fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4189 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | verbatim replica; mode 4189 = zero-DPM-level state -> levels[0xFFFFFFFF] wild read -> SIGSEGV at predicted object+96GB | 34.8 KB | view raw |
| build.sh | build-script | cc -O2 -Wall; harness_fixed with -DFIX_ALL | 65 B | view raw |
| run.sh | run-script | all modes, baseline + fixed | 120 B | view raw |
| build.log | build-log | final successful builds (guest cc 8.3) | 1018 B | view raw |
| run.log | run-log | decisive run incl. fault-address prediction line | 3.5 KB | view raw |
| fix.diff | suggested-fix | reject power table when a state parses zero DPM levels (k==0 after the j-loop) | 852 B | view raw |
| radeon_fix_apply.log | apply-log | patch(1) hunks on guest /usr/src (hunk 5 = this fix) | 808 B | view raw |
| radeon_module_build.log | fix-build-log | radeon.ko build with all four fixes, -Werror, MAKE_RC=0 | 139.5 KB | view raw |
| env.txt | environment | guest uname, cc, no radeon loaded | 568 B | view raw |
| VERDICT.md | verdict | full narrative, all six consumer sites listed | 4.6 KB | β raw |
DF-4189 β VERDICT
Status: REPRODUCED (logic-level harness; kernel-level repro hardware-gated) Impact: deterministic kernel page-fault panic on DPM state change (plus garbage-driven clock/voltage programming when the read happens to be mapped) Confidence: certain
Root cause
sumo_parse_pplib_clock_info() sets ps->num_levels = index + 1 β but it is
only called from inside the j < ucNumDPMLevels loop
(sumo_dpm.c:1508-1520). A pplib power state whose
ATOM_PPLIB_STATE_V2.ucNumDPMLevels == 0 (pptable.h:428, u8 from the VBIOS,
never validated) leaves the kzalloc'd struct sumo_ps with
num_levels == 0. Only BOOT-classified states get patched up
(sumo_patch_boot_state, :1395-1403, forces 1); every other state keeps 0.
struct sumo_ps (sumo_dpm.h:44-49) begins with
struct sumo_pl levels[SUMO_MAX_HARDWARE_POWERLEVELS] (24-byte elements), and
num_levels is u32. Every consumer that asks for the "last" level executes
ps->levels[ps->num_levels - 1] /* == levels[0xFFFFFFFF] */
= base + 0xFFFFFFFF Γ 24 β +96 GB past the allocation:
sumo_set_uvd_clock_before_set_eng_clockβ sumo_dpm.c:847-848sumo_set_uvd_clock_after_set_eng_clockβ sumo_dpm.c:865-866sumo_program_bspβ sumo_dpm.c:350 (feedssumo_calculate_bsp, whose65535*100/high_clkis the known DF-1438 div0 sink when the garbage is 0)sumo_program_atβ sumo_dpm.c:426-427 (r[num_levels-1]/l[num_levels-1], stack OOB belowu32 r[5]/l[5])sumo_dpm_get_sclkβ sumo_dpm.c:1891 (sysfs/debugfs sclk read path)sumo_program_bsp's write loopfor (i = 0; i < ps->num_levels - 1; i++)(:357) underflows to 2^32 iterations β MMIO write storm (hardware hang) if the load itself somehow returned.
Trigger / threat model
Crafted VBIOS with one BOOT state plus one non-BOOT state carrying
ucNumDPMLevels == 0, on a Sumo/Ontario/Palm APU. radeon_dpm_pick_power_state
selects the crafted state for any matching UI/thermal class; the first
radeon_dpm_change_power_state_locked() (display change, thermal event,
profile switch β regular operation) reaches sumo_dpm_pre_set_power_state β
sumo_update_requested_ps (working copy keeps num_levels == 0) β
sumo_dpm_set_power_state β the wild read β page-fault panic. This is the
sumo twin of DF-4145 (trinity) / DF-3954 (kv) / DF-3888 (amdgpu kv).
Harness proof (mode 4189)
Two-state crafted table (state 0: ucNumDPMLevels=0, PERFORMANCE, vclkβ boot
to force the UVD comparison; state 1: BOOT, 1 level β parse succeeds,
boot_ps valid). After sumo_update_requested_ps + sumo_update_current_ps
(the sumo_dpm_pre_set_power_state replica), the first
sumo_set_uvd_clock_before_set_eng_clock call faults:
expected fault address for levels[num_levels-1].sclk ~= 0x20004e2128 (object + ~95 GB) *** SIGNAL 11 at address 0x20004b5258 ***
0x20004b5258 β 0xFFFFFFFFΓ24 = 0x8004b5270 = &pi->requested_ps β i.e. the
fault is exactly &pi->requested_ps.levels[0xFFFFFFFF].sclk, the working-copy
object the kernel uses at the same call site. An unmapped-address read is
deterministic: in the kernel this is a page-fault panic, not a recoverable
garbage read.
(The harness faults at the uvd_before site because the load there feeds the
observable sumo_setup_uvd_clocks stub; in the kernel all six sites are live.
The earlier variant that "survived" uvd_before and faulted inside
sumo_program_bsp instead showed the same address β both are the same
levels[0xFFFFFFFF] load.)
Fix validation
harness_fixed 4189(-DFIX_DF4189: reject the table when a state parsed zero levels): "power state 0 has no DPM levels", parse returns -22, no crash,canary_violations = 0, exit 0.fix.diffapplied with the other three sumo fixes on guest/usr/src(radeon_fix_apply.loghunk 5):radeon.kobuilds under-Werror,MAKE_RC=0(radeon_module_build.log).
Kernel references
- sys/dev/drm/radeon/sumo_dpm.c:1508-1520 (loop that can leave num_levels 0)
- sys/dev/drm/radeon/sumo_dpm.c:1447 (num_levels set only per parsed level)
- sys/dev/drm/radeon/sumo_dpm.c:847-848, :865-866, :350, :426-427, :1891 (the
levels[num_levels-1]consumers) - sys/dev/drm/radeon/sumo_dpm.h:44-49 (levels[] is the FIRST member; u32)
- sys/dev/drm/radeon/pptable.h:424-436 (ucNumDPMLevels from VBIOS, unchecked)
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 4189 rejects the crafted table (DRM_ERROR, -EINVAL, exit 0); combined fix builds radeon.ko under -Werror (MAKE_RC=0)
run.log fixed section; radeon_fix_apply.log hunk 5; radeon_module_build.log MAKE_RC=0
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log '===== mode 4189 =====': 'expected fault address ... ~= 0x20004e2128 (object + ~95 GB)' then 'SIGNAL 11 at address 0x20004b5258' (same formula on the requested_ps copy); fixed section: 'power state 0 has no DPM levels', exit=0
PoC changes
wrote harness from scratch (no seed); note: num_levels is u32 so the index is levels[0xFFFFFFFF] (wild read +96 GB), not levels[-1] β verified against the kv/trinity family finding language
Verified recommended fix
reject the power table when a state parses zero DPM levels (k == 0 after the j-loop in sumo_parse_power_table), freeing everything cleanly; long-term validate ucNumDPMLevels at parse
Verdict
Reproduced at logic level with the verbatim replica (harness mode 4189): a pplib state with ucNumDPMLevels==0 leaves ps->num_levels==0 (u32), so every levels[num_levels-1] consumer indexes levels[0xFFFFFFFF] = object + 0xFFFFFFFF24 (~96 GB past the kzalloc'd sumo_ps, whose levels[] is the first member). The harness SIGSEGVs at exactly the predicted address (0x20004b5258 = &pi->requested_ps + 0xFFFFFFFF24) inside sumo_set_uvd_clock_before_set_eng_clock (:847); the kernel equivalents (:847/:865/:350/:426/:1891) page-fault deterministically on any DPM state change to the crafted state. Also feeds garbage into sumo_calculate_bsp (DF-1438 div0 sink when the garbage is 0) and a 2^32-iteration MMIO write storm in sumo_program_bsp's underflowed loop. Sumo twin of DF-4145 (trinity) / DF-3954 (kv) / DF-3888 (amdgpu kv). Kernel-level repro hardware-gated (no ATI APU on guest). Fix proven: harness_fixed rejects the table cleanly; radeon.ko builds with the fix under -Werror.
No comments yet.