Unbounded VBIOS ucSclkEntryNum overflows fixed 8-entry stack table in atomctrl_get_smc_sclk_range_table
- File:
sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c - Lines: 1368, 1374, 1375, 1383
- Severity: High
- CVSS:
CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U:C:H/I:H/A:H - CWE: CWE-787 Out-of-bounds Write
- Confidence: certain
Summary
atomctrl_get_smc_sclk_range_table() loops
for (i=0; i<psmu_info->ucSclkEntryNum; i++) writing table->entry[i] where
ucSclkEntryNum is a UCHAR taken straight from the VBIOS SMU_Info atom table
with no comparison against the fixed array size (MAX_SCLK_RANGE=8).
Both the source (ATOM_SMU_INFO_V2_1.asSclkFcwRangeEntry[8],
atombios.h:5640) and destination
(pp_atom_ctrl_sclk_range_table.entry[8], ppatomctrl.h:244) are 8 entries;
ucSclkEntryNum can be 0..255.
Both callers (polaris10_smumgr.c:803 and vegam_smumgr.c:673) allocate the
destination as a 64-byte stack local, so ucSclkEntryNum>8 smashes up to
~2 KB of kernel stack.
psmu_info is also dereferenced without a NULL check.
Root cause
ppatomctrl.c:1368-1371 fetches psmu_info = smu_atom_get_data_table(...)
which smu_helper.c:660-672 returns NULL when the SMU_Info data table header
fails to parse. ppatomctrl.c:1374 then reads psmu_info->ucSclkEntryNum with
no NULL guard (NULL deref β panic, a lesser DoS).
More seriously, the loop bound at :1374 is the raw VBIOS UCHAR
ucSclkEntryNum and there is no
PP_ASSERT_WITH_CODE((psmu_info->ucSclkEntryNum <= MAX_SCLK_RANGE), ...)
anywhere β contrast atomctrl_get_voltage_table_v3:546-551 which DOES cap
ucGpioEntryNum against PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES.
Each iteration writes 5 fields (ucVco_setting, ucPostdiv, usFcw_pcc,
usFcw_trans_upper, usRcw_trans_lower) into table->entry[i];
sizeof(pp_atom_ctrl_sclk_range_table_entry)=8 (ppatomctrl.h:234-240) and
entry[] has MAX_SCLK_RANGE=8 slots (ppatomctrl.h:232,244) = 64 bytes total.
The two callers allocate struct pp_atom_ctrl_sclk_range_table range_table_from_vbios = {{{0}}};
on their stack frames (polaris10_smumgr.c:803 inside
polaris10_get_sclk_range_table, vegam_smumgr.c:673 inside
vegam_get_sclk_range_table) and pass &range_table_from_vbios as table.
With ucSclkEntryNum=255 the loop writes 255*8=2040 bytes starting at
table->entry[0], i.e. ~1976 bytes past the end of the 64-byte stack object,
overwriting saved frame pointer, return address, and adjacent frames.
This is the same defect class as DF-1468..DF-1472 (VBIOS-supplied count into a fixed kernel array) but uniquely yields a stack write rather than a read.
Threat
Local, kernel-context, reached on the amdgpu driver-load / powerplay-init path
for CHIP_POLARIS10/11/12 (polaris10_smumgr) and VEGAM (vegam_smumgr).
Precondition: attacker controls the GPU VBIOS SMU_Info atom table. Two
realistic placements matching the AGENT.md FS/VBIOS-image threat model:
- Host root or an attacker with physical/PCI-bar access flashes a malicious VBIOS ROM;
- SR-IOV / VFIO passthrough where a malicious or compromised host feeds a guest
amdgpu driver a forged atom BIOS image β
smu_atom_get_data_table(smu_helper.c:669) returnsbios + data_startdirectly from the per-deviceatom_context, so the guest kernel consumes attacker bytes.
Impact: kernel stack buffer overflow during driver init. Without
-fstack-protector on this build path it is a return-address overwrite β local
kernel code execution; with stack canaries it is a reliable kernel panic (DoS)
on every load.
The OOB read side (psmu_info->asSclkFcwRangeEntry[i] for i>=8) also leaks
adjacent VBIOS/kernel bytes into the SMC DPM table that is later uploaded to the
GPU.
Exploit / PoC
This surface has no syscall entry point; the PoC is a forged VBIOS atom data table, mirroring the DF-1468 family.
- Dump a reference
SMU_Info(DATA, SMU_Info)atom table from a Polaris10/ VEGAM target via atombios parsing. - Patch the
ATOM_SMU_INFO_V2_1.ucSclkEntryNumbyte (offset ofucSclkEntryNumwithin the struct = offsetof after theATOM_COMMON_TABLE_HEADER) from its valid value (β€8) to0xFF, leaving the 8 realasSclkFcwRangeEntryentries in place. - Recompute the atom master-data-table size/checksum so
amdgpu_atom_parse_data_headeraccepts the forged image; write the image to the GPU ROM (host) or inject it into the guestatom_context(SR-IOV). - Bind amdgpu on a POLARIS10/POLARIS11/POLARIS12/VEGAM part; the load path
polaris10_smumgr.cβpolaris10_get_sclk_range_table:807βatomctrl_get_smc_sclk_range_table:1374runs unconditionally and writestable->entry[8..254].
Success = immediate kernel panic (stack smashing detected / page fault on stack
guard) proving memory corruption; on a non-canary kernel build, replace the
overflowed return-address bytes in the forged VBIOS image with a chosen value to
redirect polaris10_get_sclk_range_table's return into kernel shellcode.
Recommended fix
Bounds-check ucSclkEntryNum against MAX_SCLK_RANGE and NULL-check
psmu_info before the loop, matching the existing PP_ASSERT_WITH_CODE style
used elsewhere in this file (e.g. :546-551).
--- a/sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c
+++ b/sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c
@@ -1365,11 +1365,19 @@ int atomctrl_get_smc_sclk_range_table(struct pp_hwmgr *hwmgr, struct pp_atom_ctr
ATOM_SMU_INFO_V2_1 *psmu_info =
(ATOM_SMU_INFO_V2_1 *)smu_atom_get_data_table(hwmgr->adev,
GetIndexIntoMasterTable(DATA, SMU_Info),
&size, &frev, &crev);
+ PP_ASSERT_WITH_CODE((NULL != psmu_info),
+ "Could not find SMU Info Table in BIOS.", return -EINVAL;);
+
+ PP_ASSERT_WITH_CODE((psmu_info->ucSclkEntryNum <= MAX_SCLK_RANGE),
+ "Too many SCLK range entries in SMU Info table!",
+ return -EINVAL;);
+
for (i = 0; i < psmu_info->ucSclkEntryNum; i++) {
table->entry[i].ucVco_setting = psmu_info->asSclkFcwRangeEntry[i].ucVco_setting;
Related findings
- DF-1468, DF-1469, DF-1470 (siblings, processpptables.c): same VBIOS-count-OOB class.
- DF-1497, DF-1498 (siblings): same file's other VBIOS-parser defects.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1496 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | replica of atomctrl_get_smc_sclk_range_table with ucSclkEntryNum=255 into a stack-local entry[8] + canary | 7.4 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 | 979 B | view raw |
| fix.diff | suggested-fix | PP_ASSERT_WITH_CODE NULL check on psmu_info + ucSclkEntryNum<=MAX_SCLK_RANGE bound | 974 B | view raw |
| fix_module_proof.txt | fix-build-proof | ppatomctrl.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.4 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-1496 β Unbounded ucSclkEntryNum overflows fixed 8-entry stack array (ppatomctrl.c)
Verdict: REPRODUCED (source-level + harness) β latent amdgpu-powerplay bug, stack smash
The bug
sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c, function
atomctrl_get_smc_sclk_range_table, lines 1361-1386:
ATOM_SMU_INFO_V2_1 *psmu_info =
(ATOM_SMU_INFO_V2_1 *)smu_atom_get_data_table(...); /* may return NULL */
...
for (i = 0; i < psmu_info->ucSclkEntryNum; i++) { /* :1374 NO bound */
table->entry[i].ucVco_setting = psmu_info->asSclkFcwRangeEntry[i]....; /* :1375 */
table->entry[i].ucPostdiv = ...; /* :1376 */
table->entry[i].usFcw_pcc = le16_to_cpu(...); /* :1378 */
...
}
ucSclkEntryNum is a UCHAR (0..255) from the VBIOS SMU_Info table with
no comparison vs MAX_SCLK_RANGE (8). The source VBIOS array
asSclkFcwRangeEntry[8] (atombios.h:5640) and the dest
table->entry[8] (ppatomctrl.h:232,244, 8 entries * 8 bytes = 64 bytes)
are both fixed at 8. Callers polaris10_smumgr.c:803 and vegam_smumgr.c:673
allocate a 64-byte stack-local struct pp_atom_ctrl_sclk_range_table. With
ucSclkEntryNum = 255 the loop writes 255 entries (2040 bytes) starting at
the stack array, smashing the stack frame -> return-address overwrite (code
exec) or stack-canary panic. psmu_info is also deref'd without a NULL check.
POLARIS10/11/12/VEGAM.
Harness proof
VBIOS psmu_info->ucSclkEntryNum = 255 (u8, NO check vs MAX_SCLK_RANGE)
MAX_SCLK_RANGE = 8 (ppatomctrl.h:232)
dest stack table = 64 bytes (entry[8])
loop would write = 2040 bytes (255 entries)
OVERSHOOT past entry[8] = 1976 bytes of stack smash
entry[7] = {vco=0x17 postdiv=0x27} (in-bounds, last legal)
bytes past entry[8] = 0x10 0x20 0x00 0x10 ... (was zero)
post_canary = 0x3000200010002010 (expected 0x2222222222222222)
RESULT: stack overflow CONFIRMED at ppatomctrl.c:1375
Fix
fix.diff adds two PP_ASSERT_WITH_CODE guards before the loop:
1. NULL check on psmu_info (smu_atom_get_data_table can return NULL).
2. ucSclkEntryNum <= MAX_SCLK_RANGE bound check.
Module build validation (Phase 8)
All 8 amdgpu fixes applied; amdgpu.ko built under -Werror:
ppatomctrl.o (14240 bytes) produced, 0 errors, amdgpu.ko linked.
Fix verification
fixedVALIDATED via module build: fix.diff applied cleanly; amdgpu.ko built under -Werror with 0 errors; ppatomctrl.o (14240 bytes) produced, amdgpu.ko linked. The PP_ASSERT_WITH_CODE bounds compiles into the module. Runtime before/after not possible (no AMD GPU HW).
baseline (harness): OVERSHOOT=1976 bytes; post_canary=0x3000200010002010 (expected 0x2222222222222222) -- stack smash patched (module build): OK ppatomctrl.o (14240 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
- p
- a
- t
- o
- m
- c
- t
- r
- l
- .
- c
- :
- 1
- 3
- 6
- 1
- 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
- p
- a
- t
- o
- m
- c
- t
- r
- l
- .
- c
- :
- 1
- 3
- 6
- 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
- /
- p
- p
- a
- t
- o
- m
- c
- t
- r
- l
- .
- c
- :
- 1
- 3
- 7
- 4
- 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
- p
- a
- t
- o
- m
- c
- t
- r
- l
- .
- c
- :
- 1
- 3
- 7
- 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
- /
- p
- p
- a
- t
- o
- m
- c
- t
- r
- l
- .
- h
- :
- 2
- 3
- 2
- 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
- p
- a
- t
- o
- m
- c
- t
- r
- l
- .
- h
- :
- 2
- 4
- 4
Detail
Exploit chain
Blocked by dead-code-on-guest hard blocker (valid): amdgpu powerplay not in GENERIC and no AMD GPU HW on the audit guest; kernel path cannot trigger end-to-end. Primitive proven at harness level (stack-local entry[8] smashed by ucSclkEntryNum=255, canary overwritten). Realistic runtime impact with POLARIS10/11/12/VEGAM HW + crafted VBIOS is stack smash -> RCE (return-addr overwrite) or stack-canary panic. Evidence pack: findings/poc/DF-1496/ (harness.c).
Evidence (decisive lines)
VBIOS psmu_info->ucSclkEntryNum = 255 (u8, NO check vs MAX_SCLK_RANGE)
MAX_SCLK_RANGE = 8 (ppatomctrl.h:232)
dest stack table = 64 bytes (entry[8])
loop would write = 2040 bytes (255 entries)
OVERSHOOT past entry[8] = 1976 bytes of stack smash
entry[7] = {vco=0x17 postdiv=0x27} (in-bounds, last legal)
bytes past entry[8] = 0x10 0x20 0x00 0x10 ... (was zero)
post_canary = 0x3000200010002010 (expected 0x2222222222222222)
RESULT: stack overflow CONFIRMED at ppatomctrl.c:1375
RUN_EXIT=0
PoC changes
Authored harness.c (stack-local entry[8] replica with pre/post canaries), build.sh, run.sh, fix.diff (PP_ASSERT_WITH_CODE NULL check + ucSclkEntryNum<=MAX_SCLK_RANGE bound), VERDICT.md, manifest.json. fix.diff regenerated via copy+edit+diff.
Verified recommended fix
In atomctrl_get_smc_sclk_range_table (ppatomctrl.c:1368-1374), before the loop add PP_ASSERT_WITH_CODE((NULL != psmu_info), ..., return -EINVAL); and PP_ASSERT_WITH_CODE((psmu_info->ucSclkEntryNum <= MAX_SCLK_RANGE), ..., return -EINVAL);. Matches finding proposal. Full diff in findings/poc/DF-1496/fix.diff.
Verdict
REPRODUCED. atomctrl_get_smc_sclk_range_table (ppatomctrl.c:1361-1386) loops for(i=0;i<psmu_info->ucSclkEntryNum;i++) writing table->entry[i] where ucSclkEntryNum is UCHAR (0..255) from VBIOS SMU_Info with NO check vs MAX_SCLK_RANGE=8 (ppatomctrl.h:232). Both source asSclkFcwRangeEntry[8] and dest entry[8] are fixed at 8. Callers polaris10_smumgr.c:803 and vegam_smumgr.c:673 allocate a 64-byte stack-local dest; ucSclkEntryNum=255 writes 2040 bytes -> stack smash (return-addr overwrite / canary panic). psmu_info also deref'd without NULL check. Confirmed by harness: post_canary clobbered (0x3000200010002010 vs expected 0x2222222222222222).
No comments yet.