OOB stack read in bw_calcs_data_update_from_pplib when powerplay returns zero clock levels
- File:
sys/dev/drm/amd/display/dc/dce110/dce110_resource.c - Lines: 1146, 1169, 1187
- Severity: Low
- CVSS:
CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U:C:N/I:N/A:H - CWE: CWE-129 Improper Validation of Array Index
- Confidence: likely
Summary
bw_calcs_data_update_from_pplib() indexes
clks.clocks_in_khz[clks.num_levels - 1] without first checking that
clks.num_levels > 0.
dm_pp_get_clock_levels_by_type() unconditionally returns true and leaves
num_levels == 0 when adev->powerplay.pp_funcs is NULL or
get_clock_by_type is absent (notably the DISPLAY_CLK path has no
default-level fallback).
num_levels is uint32_t, so 0 - 1 wraps to 0xFFFFFFFF, producing a wild
read ~16 GB past the stack object and an immediate kernel page fault / panic.
Root cause
At dce110_resource.c:1145-1146 (ENGINE_CLK high_sclk), 1168-1169
(DISPLAY_CLK high_voltage_max_dispclk), and 1186-1187 (MEMORY_CLK
high_yclk), the code does:
dc->bw_vbios->high_sclk = bw_frc_to_fixed(
clks.clocks_in_khz[clks.num_levels-1], 1000);
where clks is a stack-local struct dm_pp_clock_levels
(dm_services_types.h:89-92) with clocks_in_khz[DM_PP_MAX_CLOCK_LEVELS=8].
clks.num_levels is uint32_t.
The provider dm_pp_get_clock_levels_by_type (amdgpu_dm_pp_smu.c:296-366)
always returns true; when adev->powerplay.pp_funcs is NULL or
pp_funcs->get_clock_by_type is NULL (line 307), the entire if-block is
skipped, pp_clks stays {0}, pp_to_dc_clock_levels (line 316) sets
dc_clks->num_levels = pp_clks->count = 0, and the post-reduction loops at
lines 342-363 do not execute (their condition is i < 0).
For DM_PP_CLOCK_TYPE_DISPLAY_CLK in particular there is also no
get_default_clock_levels() fallback inside the provider (default fallback at
line 311 only fires when get_clock_by_type exists AND returns an error).
Result: clks.num_levels == 0 reaches dce110_resource.c and the index
(uint32_t)0 - 1 = 0xFFFFFFFF dereferences
&clks.clocks_in_khz[0xFFFFFFFF] = &clks + 0x3FFFFFFFC, which is unmapped β
kernel page fault β panic.
The ENGINE_CLK and MEMORY_CLK paths are normally rescued by the
i>0?i:1 floor inside the reduction loop, but only if that loop actually runs
(num_levels>0); when num_levels==0 from the skipped if-block, the floor
never triggers and the same OOB occurs at lines 1146 and 1187.
Threat
Attacker position: a malicious or malfunctioning PCI/Thunderbolt/USB4 GPU whose
powerplay subsystem omits get_clock_by_type, or any system whose powerplay
firmware returns success with zero clock levels for any of ENGINE/DISPLAY/MEMORY
clocks.
No unprivileged-local or unauthenticated-remote path: the function is called only
from construct() at dce110_resource.c:1389, which runs once during
dc_create_resource_pool() at GPU/driver init.
Impact: deterministic kernel panic (wild read into unmapped address space) at GPU init or hot-plug β a local Denial of Service.
No info leak (the access faults before any value is returned) and no code execution.
The same buggy pattern is duplicated in dce112_resource.c:1005 and
dce120_resource.c:776.
Exploit / PoC
Reproduction requires either
- a Thunderbolt/USB4 AMD GPU whose powerplay implementation returns 0 levels for
DM_PP_CLOCK_TYPE_DISPLAY_CLKwith success status, or - booting on Carrizo/Stoney with a powerplay module that fails to register
pp_funcs->get_clock_by_type.
Trigger is automatic at amdgpu driver load: kldload amdgpu (or auto-load at
boot).
Expected kernel panic signature on DCE110 (Carrizo/Stoney) hardware:
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x3fffffffc + &clks cpuid = 0; apic id = 00000000 current process = kldload amdgpu (or kernel thread) fp at dm_pp_get_clock_levels_by_type + bw_calcs_data_update_from_pplib
To force the condition on test hardware without modifying firmware:
- Patch
amdgpu_dm_pp_smu.c: temporarily replace the body ofget_default_clock_levels()so itsDISPLAY_CLKcase setsnum_levels = 0, OR - On Carrizo/Stoney, ensure
adev->powerplay.pp_funcs->get_clock_by_typeis NULL (unload powerplay submodule before amdgpu probes DC).
Success criterion: kernel panics in bw_calcs_data_update_from_pplib during
amdgpu init, confirming the OOB read.
Recommended fix
Guard every clocks_in_khz[num_levels - 1] with a num_levels == 0 check that
falls back to safe defaults. Minimal fix at the three confirmed sinks:
--- a/sys/dev/drm/amd/display/dc/dce110/dce110_resource.c
+++ b/sys/dev/drm/amd/display/dc/dce110/dce110_resource.c
@@ -1139,6 +1139,8 @@ static void bw_calcs_data_update_from_pplib(struct dc *dc)
dm_pp_get_clock_levels_by_type(
dc->ctx,
DM_PP_CLOCK_TYPE_ENGINE_CLK,
&clks);
+ if (clks.num_levels == 0)
+ clks.num_levels = 1; /* defensive: avoid [num_levels-1] underflow */
/* convert all the clock fro kHz to fix point mHz */
dc->bw_vbios->high_sclk = bw_frc_to_fixed(
clks.clocks_in_khz[clks.num_levels-1], 1000);
@@ -1164,6 +1166,8 @@ static void bw_calcs_data_update_from_pplib(struct dc *dc)
dm_pp_get_clock_levels_by_type(
dc->ctx,
DM_PP_CLOCK_TYPE_DISPLAY_CLK,
&clks);
+ if (clks.num_levels == 0)
+ clks.num_levels = 1;
dc->bw_vbios->high_voltage_max_dispclk = bw_frc_to_fixed(
clks.clocks_in_khz[clks.num_levels-1], 1000);
@@ -1178,6 +1182,8 @@ static void bw_calcs_data_update_from_pplib(struct dc *dc)
dm_pp_get_clock_levels_by_type(
dc->ctx,
DM_PP_CLOCK_TYPE_MEMORY_CLK,
&clks);
+ if (clks.num_levels == 0)
+ clks.num_levels = 1;
dc->bw_vbios->low_yclk = bw_frc_to_fixed(
clks.clocks_in_khz[0] * MEMORY_TYPE_MULTIPLIER, 1000);
Better still, push the floor into dm_pp_get_clock_levels_by_type itself
(amdgpu_dm_pp_smu.c:296) so all callers are protected, including the matching
sinks in dce112_resource.c:1005 and dce120_resource.c:776: after
pp_to_dc_clock_levels(), add
if (dc_clks->num_levels == 0) get_default_clock_levels(clk_type, dc_clks);.
Related findings
- DF-1486 (twin, dcn_calcs.c):
fclks.data[-1]OOB read in the same family (num_levels == 1on DCN1).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1549 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able fix for the cited bug | 538 B | view raw |
| VERDICT.md | verdict | source-confirmation analysis | 733 B | β raw |
| build.sh | build-script | N/A (source-only) | 61 B | view raw |
| run.sh | run-script | N/A (source-only) | 87 B | view raw |
DF-1549 VERDICT
Verdict: REPRODUCED (source-confirmed)
Impact: Low (driver-level NULL deref / OOB / leak / DoS β hardware-gated)
Mechanism: dce110_resource.c:1145-1146 (ENGINE_CLK), 1168-1169 (DISPLAY_CLK), 1186-1187 (MEMORY_CLK) indexes clks.clocks_in_khz[clks.num_levels-1] without num_levels>0 check. clks is stack dm_pp_clock_levels wit
Citation: sys/dev/drm/amd/display/dc/dce110/dce110_resource.c:1146-1187
Fix: Applied fix.diff β compiles in batch kernel build (rc=0, -Werror).
Verification method: Source-only line-by-line trace of cited path:line. Low-severity driver bug; PoC trigger requires specific hardware or root context. Confirmed the cited vulnerable pattern exists in source.
Fix verification
fixedfix.diff compiled in batch kernel build rc=0 -Werror
fix.diff compiled in batch kernel build rc=0 -Werror
Confirmed kernel references
β
Detail
Exploit chain
none (Low severity)
Evidence (decisive lines)
Source-confirmed: clks.clocks_in_khz[num_levels-1] without num_levels>0 check (dce110_resource.c:1146)
Verified recommended fix
Source-confirmed: clks.clocks_in_khz[num_levels-1] without num_levels>0 check (dce110_resource.c:1146)
Verdict
Source-confirmed: clks.clocks_in_khz[num_levels-1] without num_levels>0 check (dce110_resource.c:1146)
No comments yet.