vega20_get_sclk_od/get_mclk_od: count-1 underflow wild read (+34 GB) and DIV_ROUND_UP divide-by-zero on zero golden clock via world-readable pp_sclk_od/pp_mclk_od (DF-3686/DF-3707 twins)
Summary
vega20_get_sclk_od and get_mclk_od index dpm_levels[count - 1] with unsigned count and then DIV_ROUND_UP(value*100, golden_value) with no guards. Case A (div0): with the DPM SMU feature disabled, the single golden level is seeded from the raw VBIOS bootup clock copied with no zero check - a crafted/blank VBIOS value 0 makes golden_value 0 -> #DE panic on first read of the world-readable sysfs. Case B (wild read): after a failed SMU re-enable on resume (no cleanup), golden_dpm_table stays the memset-zero copy -> count==0 -> dpm_levels[0xFFFFFFFF] = read at ~32 GiB past the kzalloc'd backend. Guest-verified arithmetic: SIGFPE 3/3 for case A; case B's verbatim wild load confirmed executing in guest objdump. Unprivileged local DoS (panic) plus wild kernel OOB read on Vega20 - any local user can 'cat pp_sclk_od'; preconditions VBIOS-gated or a degraded post-resume DPM state.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3858 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.4 KB | β raw | |
| VERDICT.md | β | 5.7 KB | β raw | |
| od_div0.c | β | 4.2 KB | view raw | |
| build.sh | β | 115 B | view raw | |
| run.sh | β | 128 B | view raw | |
| run.log | β | 527 B | view raw | |
| run.2.log | β | 1.4 KB | view raw | |
| env.txt | β | 467 B | view raw | |
| fix.diff | β | 1.7 KB | view raw | |
| verdict.json | β | 5.0 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw |
DF-3858 β vega20_get_sclk_od/get_mclk_od: count-1 underflow wild read + divide-by-zero on zero golden clock (DF-3686/DF-3707 twins)
What
sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:1318-1381:
int value = sclk_table->dpm_levels[sclk_table->count - 1].value; /* :1326 */
int golden_value = golden_sclk_table->dpm_levels[golden_sclk_table->count - 1].value; /* :1327-28 */
value -= golden_value;
value = DIV_ROUND_UP(value * 100, golden_value); /* :1332 <-- #DE */
(twin at :1372-1378 for memclk.)
Case A β divide-by-zero (DF-3707 twin). When the DPM_GFXCLK (resp.
DPM_UCLK) SMU feature is not enabled, vega20_setup_gfxclk_dpm_table() takes
the boot-clock branch (:544-547): count = 1; dpm_levels[0].value =
data->vbios_boot_state.gfx_clock / 100; β the raw VBIOS
bootup_sclk_in10khz copied with no zero check
(ppatomfwctrl.c:523/:556 β vega20_hwmgr.c:745). A crafted/blank VBIOS
value 0 β golden_value == 0 β DIV_ROUND_UP(x, 0) β #DE panic.
Case B β unsigned count-1 underflow (DF-3686 twin). If DPM (re-)enable
aborts inside vega20_setup_default_dpm_tables() (any SMU message failure,
PP_ASSERT at :594/:613/... returns early after the memset of the
whole dpm_table at :588), dpm_table/golden_dpm_table stay all-zero
(count == 0). Boot-time failure tears the device down
(hwmgr_hw_init β err2 frees the backend, probe fails) β but the
resume path has no cleanup (hwmgr_resume, hwmgr.c:308-330: returns the
error with the backend intact and pm_en untouched), and pm_en was set at
powerplay-create time (amd_powerplay.c:51), so after a failed SMU re-enable
on resume the world-readable sysfs node still dispatches into
get_sclk_od β dpm_levels[(uint32_t)0 - 1] = dpm_levels[0xFFFFFFFF]
β read at +34,359,738,360 bytes (~32 GiB) past dpm_levels[0] inside
the kzalloc'd struct vega20_hwmgr backend β a wild kernel OOB read; if it
lands on a zero, case A's divide then panics.
Reach (world-readable)
/sys/class/drm/card0/device/pp_sclk_od and pp_mclk_od are S_IRUGO
(amdgpu_pm.c:1019-1022) β amdgpu_get_pp_sclk_od (:794, no other check)
β pp_dpm_get_sclk_od (amd_powerplay.c:757, only !hwmgr || !pm_en
guard) β vega20_get_sclk_od. Any local user can cat it.
Reproduce (arithmetic-level; kernel reach hardware-gated)
build.sh # cc -O2 -Wall -o /tmp/od_div0 /tmp/od_div0.c run.sh # /tmp/od_div0 0 (case A) /tmp/od_div0 1 (case B)
Expected (observed on the audit guest, run.log):
case A: ... DIV_ROUND_UP(x, 0)
Floating point exception (core dumped) # exit=136 (SIGFPE = #DE), 3/3 runs
case B: golden gfx_table.count = 0 -> index (uint32_t)0-1 = 4294967295
effective read offset = 34359738360 bytes past dpm_levels[0] (elem size 8)
Disassembly on the guest confirmed the case-B load
mov 0x401ef8(,%rax,8),%ecx with rax = 0xFFFFFFFF really executes
(+34 GB address); in this userland process it happened to land on an
unrelated mapped page (returned garbage 0) β in the kernel the backend is a
kmalloc object and a +34 GB landing is near-certainly unmapped (page-fault
panic) or feeds garbage into the case-A divide.
Impact
Local DoS (panic) and/or wild OOB read via an unprivileged sysfs read, on a Vega20 (Radeon VII / MI50 / MI60) in the affected states (crafted VBIOS zero boot clock; or DPM bring-up failure). Hardware/VBIOS-gated.
DF-3858 VERDICT β vega20_get_sclk_od/get_mclk_od count-1 underflow + div0
Status: not_reproduced (kernel reach hardware-gated) β both arithmetic facts proven on the guest (SIGFPE 3/3; wild +34 GiB index proven in disassembly), source chain traced end-to-end.
Root cause (path:line, sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c
unless noted)
vega20_get_sclk_od() :1318-1335 (mem twin :1364-1381):
:1326sclk_table->dpm_levels[sclk_table->count - 1].value:1327-1328golden_sclk_table->dpm_levels[golden_sclk_table->count - 1].value:1332value = DIV_ROUND_UP(value * 100, golden_value);
No zero-count and no zero-divisor guard.
Case A β golden_value == 0 (DF-3707 twin)
vega20_setup_gfxclk_dpm_table():538-547: whendata->smu_features[GNLD_DPM_GFXCLK].enabledis false,dpm_table->count = 1; dpm_table->dpm_levels[0].value = data->vbios_boot_state.gfx_clock / 100;(:545-546).data->vbios_boot_state.gfx_clock = boot_up_values.ulGfxClk(vega20_hwmgr.c:745), whereulGfxClk = fw_info->bootup_sclk_in10khz(ppatomfwctrl.c:523and:556) β raw VBIOS u32, no zero check anywhere on this path.vega20_setup_default_dpm_tables():713-715memcpy's the whole table intogolden_dpm_table, so the golden top (and only) value is 0.- First
cat pp_sclk_odafter boot βDIV_ROUND_UP(0*100, 0)β #DE β panic. (Values in the enabled path are protected:vega20_get_dpm_frequency_by_index():499-501asserts*clk != 0β the developer bounded the SMU path but not the VBIOS boot-clock path.)
Case B β count == 0 wild read (DF-3686 twin)
vega20_setup_default_dpm_tables():588memset(&data->dpm_table, 0, sizeof(data->dpm_table)); each sub-table is filled only if its SMU feature is enabled and every SMU message succeeds (:592-711,PP_ASSERTat:594/:541/:562/...return early).- Boot-time failure is torn down:
hwmgr_hw_init(hwmgr.c:246-256) goeserr2βbackend_fini(kfree backend) βpp_hw_init(amd_powerplay.c:118-130) returns the error β the powerplay IP block init fails β the device never probes (no sysfs). The live degraded state is resume:hwmgr_resume(hwmgr.c:308-330) callsphm_enable_dynamic_state_managementwith no cleanup on failure (no backend free, pm_en untouched), leaving the driver attached with the memset-zero dpm tables whenever SMU re-enable fails after suspend (flaky SMU / VF passthrough states). hwmgr->pm_enwas set at powerplay-create time (amd_powerplay.c:51) and is not cleared on the resume failure; the sysfs nodes exist for the lifetime of the device (amdgpu_device.c:2664,amdgpu_pm.c:1019-1022) andpp_dpm_get_sclk_od(amd_powerplay.c:757-763) checks only!hwmgr || !pm_enβ so a world-readablecat pp_sclk_odstill dispatches intovega20_get_sclk_od.golden_dpm_table.count == 0βcount - 1 == 0xFFFFFFFF(uint32count,vega20_hwmgr.h:156-159) βdpm_levels[0xFFFFFFFF].value: element size 8 β effective read offset 34,359,738,360 bytes pastdpm_levels[0]in the kzalloc'dstruct vega20_hwmgrbackend (vega20_hwmgr_backend_init():383).- Wild read outcome: unmapped β page-fault panic; mapped garbage β
value - garbagethen divided by garbage β and if the garbage golden value is 0, case A's #DE. (DF-1253 covers the analogous underflow insidevega20_apply_clocks_adjust_rules():3095-3104with themclk_latency_table.countloop; this finding is the distinct get_sclk_od/get_mclk_od sink, checklist item (c) "OD table getters, get_max_sclk/mclk consumers".)
Why vega20_get_profiling_clk_mask/force_clock_level are NOT affected
:2229-2231 (count - 1 into a mask) and :2249/:2276
(soft_max_level >= count reject) were traced: even with count==0 the mask
path shifts by 0xFFFFFFFF (x86 shl masks to 31) β soft_max_level = 31 β₯
count β -EINVAL. No OOB.
What ran on the Guest
od_div0.c replicates the exact struct layout (vega20_hwmgr.h:140-171)
and the verbatim get_sclk_od body.
- Case A (
count=1, value=0): SIGFPE, exit=136, 3/3 runs (run.logΓ2,run.2.log), dmesgpid NNNN (od_div0), uid 0: exited on signal 8. - Case B (
count=0): prints index4294967295and effective offset34359738360bytes; the verbatim access executed (objdump on the guest:mov 0x401ef8(,%rax,8),%ecxwithrax=0xFFFFFFFF) and landed on an unrelated mapped page in this userland process (returned 0) β in-kernel the same address relative to a kmalloc'd backend is a wild read.
Why not reproduced
Audit guest has 0 AMD PCI devices, no /dev/dri (env.txt); vega20_hwmgr
code cannot execute. Kernel reach needs a Vega20 board in state A (crafted
VBIOS zero boot clock with GFXCLK DPM feature disabled) or state B (failed
DPM bring-up). Impact ceiling: unprivileged local DoS via panic +
wild OOB read.
Fix validation
fix.diff makes vega20_get_sclk_od/get_mclk_od return 0 when either table
count is 0 or the golden top value is 0 (mirroring the DF-3707 fix), and
zero-guards the VBIOS boot-up clocks in vega20_init_smc_table (floor to
existing smu_features-independent 10 kHz minimum like other consumers).
git-apply-clean against the read-only sys/ tree; a patched kernel cannot
be exercised on this guest (no Vega20 ASIC), so fix validation is
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 adds the count/golden-value guards to both OD getters and is git-apply-clean against the read-only sys/ tree; no Vega20 ASIC on this guest (amdgpu never binds), so a patched-kernel run cannot be exercised - not_testable
fix.diff; VERDICT.md; env.txt
Confirmed kernel references
- sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:1326
- sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:1332
- sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:1372
- sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:1378
- sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:546
- sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:567
- sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:745
- sys/dev/drm/amd/powerplay/hwmgr/ppatomfwctrl.c:523
- sys/dev/drm/amd/powerplay/hwmgr/ppatomfwctrl.c:556
- sys/dev/drm/amd/powerplay/hwmgr/hwmgr.c:308
- sys/dev/drm/amd/powerplay/amd_powerplay.c:51
- sys/dev/drm/amd/powerplay/amd_powerplay.c:757
- sys/dev/drm/amd/amdgpu/amdgpu_pm.c:1019
Detail
Exploit chain
unprivileged local user: cat /sys/class/drm/card0/device/pp_sclk_od -> amdgpu_get_pp_sclk_od (amdgpu_pm.c:794) -> pp_dpm_get_sclk_od (amd_powerplay.c:757) -> vega20_get_sclk_od -> (A) DIV_ROUND_UP by golden_value==0 -> #DE panic, or (B) dpm_levels[0xFFFFFFFF] wild read at +34.4 GB -> page fault/panic or garbage into the divide. Preconditions: (A) VBIOS bootup_sclk_in10khz==0 with DPM_GFXCLK disabled; (B) failed SMU re-enable on resume leaving memset-zero dpm tables.
Evidence (decisive lines)
run.log (case A SIGFPE exit=136 x2, case B index/offset printout); run.2.log (3rd trap + dmesg 'exited on signal 8' + objdump of the verbatim wild load); VERDICT.md (full chain incl. resume-path reachability correction and killed profiling-mask variants); fix.diff (git-apply-clean); env.txt (no AMD ASIC)
PoC changes
Authored from scratch. One build fix: missing
Verified recommended fix
Guard vega20_get_sclk_od/get_mclk_od: return 0 when either table count is 0 or the golden top value is 0 (and zero-floor the VBIOS boot-up clocks in vega20_init_smc_table)
Verdict
Source-traced dual defect in vega20_get_sclk_od/vega20_get_mclk_od (vega20_hwmgr.c:1318-1381), the DF-3686/DF-3707 twins: (A) DIV_ROUND_UP(value*100, golden_value) at :1332/:1378 divides by zero when the golden top clock is 0 - reachable with the DPM_GFXCLK/DPM_UCLK SMU feature disabled, where setup_gfxclk/memclk_dpm_table seeds dpm_levels[0].value from the raw VBIOS bootup_sclk/mclk_in10khz copied with no zero check (ppatomfwctrl.c:523/:556 -> vega20_hwmgr.c:745-746, :546/:567, golden copy at :714); (B) dpm_levels[count-1] with count==0 wraps to 0xFFFFFFFF - a wild read 34,359,738,360 bytes (~32 GiB) past dpm_levels[0] in the kzalloc'd vega20_hwmgr backend - reachable after a failed SMU re-enable on resume (hwmgr.c:308-330 has no cleanup, pm_en stays set from amd_powerplay.c:51) with the sysfs node still dispatching. Trigger is the world-readable sysfs pp_sclk_od/pp_mclk_od (S_IRUGO, amdgpu_pm.c:1019-1022 -> pp_dpm_get_sclk_od amd_powerplay.c:757, only !hwmgr||!pm_en guard). Guest evidence: case A traps SIGFPE (exit=136, signal 8 = the kernel #DE class) 3/3 runs; case B's verbatim access with rax=0xFFFFFFFF confirmed in guest objdump (mov 0x401ef8(,%rax,8),%ecx) computing the +34 GiB address. Kernel reach not reproducible here: 0 AMD PCI devices, no /dev/dri (env.txt). Distinct from known DF-1253 (the apply_clocks_adjust_rules mclk_latency_table loop), DF-1254 (OD8 arrays), DF-1255 (setup_single_dpm_table write). Impact ceiling on Vega20 hardware in the affected states: unprivileged local DoS via panic + wild OOB read.
No comments yet.