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

Heap OOB write in power-state callback via unbounded ucNumDPMLevels

Summary

smu8_dpm_get_pp_table_entry_callback at smu8_hwmgr.c:1355: levels[index] where index from ucNumDPMLevels (VBIOS u8, no bound vs SMU8_MAX_HARDWARE_POWERLEVELS=8). levels[8] is last array in smu8_power_state, only ~4B slack after struct. levels[8+] corrupts adjacent pp_power_state list pointers -> UAF/list corruption. Sibling of DF-1268/1305/1366 family. Crafted VBIOS. Fix: check index<SMU8_MAX_HARDWARE_POWERLEVELS.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1416 Β· 12 files
FileTypeDescriptionSize
harness.c trigger-source userspace replica of processpptables.c:928 caller loop + smu8 callback with ucNumDPMLevels=255 -> levels[8] OOB 4.8 KB view raw
build.sh build-script cc -O2 -Wall -o harness harness.c 107 B view raw
run.sh run-script ./harness 60 B view raw
build.log build-log final successful build, full output 78 B view raw
run.log run-log decisive run, full output 880 B view raw
fix.diff suggested-fix reject index >= SMU8_MAX_HARDWARE_POWERLEVELS at top of smu8 callback 828 B view raw
fix_module_proof.txt fix-build-proof smu8_hwmgr.o + processpptables.o produced, amdgpu.ko linked, 0 errors 269 B view raw
fix_module_build.log fix-build-log module build excerpt under -Werror 16.5 KB view raw
env.txt environment uname, cc version, kldstat (no DRM loaded) 301 B view raw
VERDICT.md verdict full narrative: mechanism, reachability, harness, fix 2.1 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
VERDICT.md verdict full narrative: mechanism, reachability, harness, fix
↓ download raw

DF-1416 β€” Heap OOB write via unbounded ucNumDPMLevels in smu8_hwmgr

Verdict: REPRODUCED (source-level + harness) β€” latent amdgpu-powerplay bug, heap OOB write

The bug

Two cooperating sites:

  • Caller: sys/dev/drm/amd/powerplay/hwmgr/processpptables.c:928 c for (i = 0; i < pstate_entry_v2->ucNumDPMLevels; i++) { /* u8 0..255, VBIOS, NO check */ ... res = func(hwmgr, &ps->hardware, i, pclock_info); /* invokes callback */ }
  • Callback: sys/dev/drm/amd/powerplay/hwmgr/smu8_hwmgr.c:1355-1362 c smu8_ps->levels[index].engineClock = ...; /* :1355 -- index from caller */ smu8_ps->levels[index].vddcIndex = ...; /* :1356 */ smu8_ps->level = index + 1; /* :1358 */ smu8_ps->levels[index].dsDividerIndex = 5; /* :1361 */ smu8_ps->levels[index].ssDividerIndex = 5; /* :1362 */

ucNumDPMLevels is a u8 (0..255) from the VBIOS with no check against SMU8_MAX_HARDWARE_POWERLEVELS (8) (smu8_hwmgr.h:33). levels[] is fixed at [SMU8_MAX_HARDWARE_POWERLEVELS=8] (smu8_hwmgr.h:159). With ucNumDPMLevels=255 the callback fires with index=8..254, overflowing levels[8] (the last field of struct smu8_power_state) into the adjacent pp_power_state list pointers -> UAF / list corruption. Sibling of the DF-1268/1305/1366 family.

Harness proof

VBIOS pstate->ucNumDPMLevels     = 255 (u8, no check vs SMU8_MAX)
SMU8_MAX_HARDWARE_POWERLEVELS    = 8 (smu8_hwmgr.h:33)
overflow iterations              = 247 past levels[8]
levels[8]  = {engineClock=1000008 vddc=8}  <-- FIRST OOB WRITE
RESULT: heap OOB write CONFIRMED at smu8_hwmgr.c:1355 via ucNumDPMLevels

Fix

fix.diff adds an index >= SMU8_MAX_HARDWARE_POWERLEVELS guard at the top of the callback (smu8_hwmgr.c), returning -EINVAL for out-of-range indices. This is the backend-level defense; the same defensive bound should be mirrored in the generic caller for other hwmgr backends.

Module build validation (Phase 8)

All 8 amdgpu fixes applied; amdgpu.ko built under -Werror: smu8_hwmgr.o (20208 bytes) and processpptables.o (12504 bytes) produced, 0 errors, amdgpu.ko (3741488 bytes) linked.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED via module build: fix.diff applied cleanly; amdgpu.ko built under -Werror with 0 errors; smu8_hwmgr.o (20208 bytes) and processpptables.o (12504 bytes) produced, amdgpu.ko linked. Runtime before/after not possible (no AMD GPU HW).

baseline (harness): levels[8] = {engineClock=1000008} <-- FIRST OOB WRITE
patched (module build): OK smu8_hwmgr.o (20208 bytes); OK processpptables.o (12504 bytes); amdgpu.ko = 3741488 bytes; error count: 0; AMDGPU_DONE
↓ fix.diffn/a (module build)

Confirmed kernel references

Detail

Exploit chain

Blocked by dead-code-on-guest hard blocker (valid): amdgpu powerplay not in GENERIC and no AMD GPU HW on the guest; kernel path cannot trigger end-to-end. Primitive proven at harness level (levels[8] OOB via ucNumDPMLevels). Realistic runtime impact with amdgpu HW + crafted VBIOS is heap corruption / panic / UAF. Evidence pack: findings/poc/DF-1416/ (harness.c).

Evidence (decisive lines)

VBIOS pstate->ucNumDPMLevels     = 255 (u8, no check vs SMU8_MAX)
SMU8_MAX_HARDWARE_POWERLEVELS    = 8 (smu8_hwmgr.h:33)
overflow iterations              = 247 past levels[8]
levels[7]  = {engineClock=1000007 vddc=7}  (in-bounds, last legal)
levels[8]  = {engineClock=1000008 vddc=8}  <-- FIRST OOB WRITE
levels[9]  = {engineClock=1000009 vddc=9}  <-- OOB
RESULT: heap OOB write CONFIRMED at smu8_hwmgr.c:1355 via ucNumDPMLevels
RUN_EXIT=0

PoC changes

Authored harness.c, build.sh, run.sh, fix.diff (reject index >= SMU8_MAX_HARDWARE_POWERLEVELS at top of callback), VERDICT.md, manifest.json.

Verified recommended fix

At the top of smu8_dpm_get_pp_table_entry_callback (smu8_hwmgr.c), add if (index >= SMU8_MAX_HARDWARE_POWERLEVELS) return -EINVAL;. Matches finding proposal (backend-level defense). Full diff in findings/poc/DF-1416/fix.diff.

Verdict

REPRODUCED. The caller loop at processpptables.c:928 iterates for(i=0;i<pstate_entry_v2->ucNumDPMLevels;i++) (u8 0..255 from VBIOS, NO check vs SMU8_MAX_HARDWARE_POWERLEVELS) and invokes the smu8 callback, which writes smu8_ps->levels[index] (smu8_hwmgr.h:159, fixed [8]). With ucNumDPMLevels=255 the callback fires with index=8..254, overflowing levels[8] (last field of struct smu8_power_state) into adjacent pp_power_state list pointers -> UAF/list corruption. Confirmed by harness: levels[8] clobbered.