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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1142 Β· 9 files
FileTypeDescriptionSize
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
README.md readme human repro summary
↓ download 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

  1. Source trace confirmed (see VERDICT.md kernel_refs).
  2. Baseline amdgpu.ko (incl. ci_dpm.o) builds clean under -Werror.
  3. Fix fix.diff applies (4 hunks) and patched ci_dpm.o rebuilds 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.

VERDICT.md verdict full source-trace + mechanism + fix rationale
↓ download raw

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); likewise VceLevel[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; then for (count = 0; count < table->UvdLevelCount; count++) table->UvdLevel[count].VclkFrequency = ... (writes UvdLevel[count], count from VBIOS, no clamp).
  • ci_populate_smc_vce_level :2834-2837 (writes VceLevel[count]).
  • ci_populate_smc_acp_level :2867-2870 (writes AcpLevel[count]; note the original already casts to (u8) but never clamps to 8).
  • ci_populate_smc_samu_level :2899-2902 (writes SamuLevel[count]).
  • Count source: each *_clock_voltage_dependency_table.count is parsed from the VBIOS PowerPlay table (e.g. amdgpu_parse_clk_voltage_dep_table, amdgpu_dpm.c:302) with no upper bound; count is a u8-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 of pi->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_*_level are 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.

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_testable

compile 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.