Heap OOB write in ci_populate_smc_uvd/vce/acp/samu_level: VBIOS counts index fixed-size SMU7 level arrays unchecked
Summary
ci_populate_smc_uvd_level(:2791)/_vce_level(:2834)/_acp_level(:2867)/_samu_level(:2899): loop bound = VBIOS-supplied count (u8 up to 255) writing into SMU7_Discrete_DpmTable.UvdLevel[8]/VceLevel[8]/AcpLevel[8]/SamuLevel[8]. No clamp against SMU7_MAX_LEVELS_*=8. Crafted VBIOS >8 entries overflows adjacent Level arrays and downstream fields in pi->smc_state_table. Voltage tables are trimmed by ci_trim_voltage_table_to_fit_state_table but these are not. Attacker: malicious VBIOS. Fix: clamp each XxxLevelCount to SMU7_MAX_LEVELS_*.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1142 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | clamp Uvd/Vce/Acp/Samu LevelCount to SMU7_MAX_LEVELS_* (git-apply-able) | 1.6 KB | view raw |
| build.sh | build-script | apply fix.diff + incremental compile of amdgpu ci_dpm.o | 451 B | view raw |
| run.sh | run-script | documents runtime-unreachable (no AMD GPU on guest) | 494 B | view raw |
| VERDICT.md | verdict | full source-trace + mechanism + fix rationale | 3.2 KB | β raw |
| env.txt | environment | guest uname, pci, driver build status | 2.0 KB | view raw |
| build.log | build-log | fix compile-validation output (patched ci_dpm.o, -Werror clean) | 1.4 KB | view raw |
| README.md | readme | human repro summary | 1.8 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-1142 β Heap OOB write in SMC UVD/VCE/ACP/SAMU level population (amdgpu ci_dpm.c)
Status: REPRODUCED at code level β latent at runtime on this guest (no AMD GPU). Severity (finding): High Β· CWE-787 Out-of-bounds Write
What the bug is
Four SMC level-population functions (ci_populate_smc_{uvd,vce,acp,samu}_level,
sys/dev/drm/amd/amdgpu/ci_dpm.c:2791/2834/2867/2899) set each
table->*LevelCount directly from the VBIOS-derived
*_clock_voltage_dependency_table.count (a u8, up to 255, parsed unbounded
at amdgpu_dpm.c:302) and then loop for(count=0; count<*LevelCount; count++)
writing table->*Level[count]. The destination arrays are fixed at 8
(SMU7_MAX_LEVELS_UVD/VCE/ACP/SAMU = 8, smu7.h:45-48;
SMU7_Discrete_DpmTable at smu7_discrete.h:314-331). No clamp. A crafted
VBIOS with count > 8 overflows each Level array into the next and into SMU
state fields. (Voltage tables are clamped by ci_trim_voltage_table_to_fit_state_table;
these are not.)
Why it does not trigger here
No AMD GPU on the QEMU/KVM guest (0x1234:0x1111 std VGA); amdgpu not in
X86_64_GENERIC. Latent on physical Sea Islands HW.
What was validated
- Source trace confirmed (see VERDICT.md
kernel_refs). - Baseline
amdgpu.ko(incl.ci_dpm.o) builds clean under-Werror. - Fix
fix.diffapplies (4 hunks) and patchedci_dpm.orebuilds clean under-Werror(ci_dpm.o: 68080 -> 68112 bytes).
Reproduce (compile-validation only)
scp this-folder/fix.diff root@guest:/root/df1142.diff cd /usr/src && patch -p1 --forward < /root/df1142.diff cd sys/dev/drm/amd/amdgpu && rm -f ci_dpm.o && make ci_dpm.o # -Werror clean
Fix
fix.diff clamps each *LevelCount to its SMU7_MAX_LEVELS_* via
min_t(u8, ...) before the loop β matches the finding recommendation.
DF-1142 β Heap OOB write in SMC UVD/VCE/ACP/SAMU level population (amdgpu ci_dpm.c)
Verdict
REPRODUCED (code-level, latent at runtime). Genuine unbounded-copy-into-
fixed-array heap OOB write confirmed by source trace. Not triggerable at
runtime on this guest (no AMD GPU; amdgpu not in GENERIC) -> runtime
not_testable. fix.diff validated to apply + compile under -Werror.
Mechanism (trigger -> primitive -> effect)
- Sink arrays (in
SMU7_Discrete_DpmTable,sys/dev/drm/amd/powerplay/inc/smu7_discrete.h:314-331): uint8_t UvdLevelCount; ...; SMU7_Discrete_UvdLevel UvdLevel[SMU7_MAX_LEVELS_UVD](SMU7_MAX_LEVELS_UVD = 8,smu7.h:45-48); likewiseVceLevel[8],AcpLevel[8],SamuLevel[8].- Unbounded count + loop in four functions (all in
sys/dev/drm/amd/amdgpu/ci_dpm.c): ci_populate_smc_uvd_level:2791-2794:table->UvdLevelCount = adev->pm.dpm.dyn_state.uvd_clock_voltage_dependency_table.count;thenfor (count = 0; count < table->UvdLevelCount; count++) table->UvdLevel[count].VclkFrequency = ...(writesUvdLevel[count], count from VBIOS, no clamp).ci_populate_smc_vce_level:2834-2837(writesVceLevel[count]).ci_populate_smc_acp_level:2867-2870(writesAcpLevel[count]; note the original already casts to(u8)but never clamps to 8).ci_populate_smc_samu_level:2899-2902(writesSamuLevel[count]).- Count source: each
*_clock_voltage_dependency_table.countis parsed from the VBIOS PowerPlay table (e.g.amdgpu_parse_clk_voltage_dep_table,amdgpu_dpm.c:302) with no upper bound;countis au8-promoted-to-u32. - Contrast with voltage tables: the finding correctly notes the VDDC/VDDCI voltage tables are trimmed by
ci_trim_voltage_table_to_fit_state_table(bounded to 8) β but the UVD/VCE/ACP/SAMU level tables are not, so the omission is an oversight, not policy. - Effect: with a crafted VBIOS whose
*_clock_voltage_dependency_table.count > 8, each loop runs off its[8]Level array into the adjacent Level array (UVD->VCE->ACP->SAMU) and then into the downstream fields ofpi->smc_state_table, a write-what-where into SMU control state.
Threat model / reachability
- Attacker: malicious/reflashed VBIOS or malicious PCIe/Thunderbolt AMD GPU. Reached at DPM init when
ci_populate_smc_*_levelare called to build the SMC state table. - On this guest: NOT reachable (QEMU std VGA only; amdgpu not in GENERIC). Valid hard blocker: runtime-unreachable here, latent on physical AMD CIK HW.
Exploit chain
None developed β valid hard blocker (HW-dependent VBIOS-parsing write, no
userspace-guest path to supply a VBIOS). Demonstrated work is the
source-level confirmation + compiling fix. No exploit.c (not a
userspace-reachable primitive).
PoC changes
No trigger PoC seeded. This folder adds fix.diff, build.sh, run.sh,
VERDICT.md, manifest.json, env.txt, build.log, README.md.
Recommended fix
Clamp each *LevelCount to its SMU7_MAX_LEVELS_* (=8) using min_t(u8, ...)
before the loop, so both the stored count and the loop bound are safe.
Implemented in fix.diff for all four functions. Matches the finding
proposal ("clamp each XxxLevelCount to SMU7_MAX_LEVELS_*").
Fix verification
not_testablecompile validated -Werror
module build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed+compile. ci_populate_smc_level LevelCount unbounded vs SMU7_MAX_LEVELS=8. amdgpu not in GENERIC.
No comments yet.