β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1549

OOB stack read in bw_calcs_data_update_from_pplib when powerplay returns zero clock levels

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

  1. a Thunderbolt/USB4 AMD GPU whose powerplay implementation returns 0 levels for DM_PP_CLOCK_TYPE_DISPLAY_CLK with success status, or
  2. 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 of get_default_clock_levels() so its DISPLAY_CLK case sets num_levels = 0, OR
  • On Carrizo/Stoney, ensure adev->powerplay.pp_funcs->get_clock_by_type is 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.

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);.

  • DF-1486 (twin, dcn_calcs.c): fclks.data[-1] OOB read in the same family (num_levels == 1 on DCN1).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1549 Β· 4 files
FileTypeDescriptionSize
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
VERDICT.md verdict source-confirmation analysis
↓ download 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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff compiled in batch kernel build rc=0 -Werror

fix.diff compiled in batch kernel build rc=0 -Werror
↓ fix.diffcombined build rc=0

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)