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)
PoC verification
Evidence pack
findings/poc/DF-1416 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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:928c 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-1362c 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
fixedVALIDATED 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
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- p
- o
- w
- e
- r
- p
- l
- a
- y
- /
- h
- w
- m
- g
- r
- /
- p
- r
- o
- c
- e
- s
- s
- p
- p
- t
- a
- b
- l
- e
- s
- .
- c
- :
- 9
- 2
- 8
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- p
- o
- w
- e
- r
- p
- l
- a
- y
- /
- h
- w
- m
- g
- r
- /
- s
- m
- u
- 8
- _
- h
- w
- m
- g
- r
- .
- c
- :
- 1
- 3
- 5
- 5
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- p
- o
- w
- e
- r
- p
- l
- a
- y
- /
- h
- w
- m
- g
- r
- /
- s
- m
- u
- 8
- _
- h
- w
- m
- g
- r
- .
- c
- :
- 1
- 3
- 5
- 8
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- p
- o
- w
- e
- r
- p
- l
- a
- y
- /
- h
- w
- m
- g
- r
- /
- s
- m
- u
- 8
- _
- h
- w
- m
- g
- r
- .
- h
- :
- 3
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- p
- o
- w
- e
- r
- p
- l
- a
- y
- /
- h
- w
- m
- g
- r
- /
- s
- m
- u
- 8
- _
- h
- w
- m
- g
- r
- .
- h
- :
- 1
- 5
- 9
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.
No comments yet.