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 | 1.1 KB | β raw |
| build.sh | build-script | N/A (source-only) | 65 B | view raw |
| run.sh | run-script | N/A (source-only) | 89 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 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
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.