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

Unchecked VBIOS UVD/VCE/ACP level counts cause heap OOB write past fixed-size SMC arrays

Summary

ci_populate_smc_uvd/vce/acp_level at ci_smumgr.c:1525/1566/1598: table->U/V/AcpLevelCount=(u8)(table->count), loop for(count=0;count<LevelCount) writes UvdLevel[SMU7_MAX_LEVELS_UVD=8]/VceLevel[8]/AcpLevel[8]. count from VBIOS numEntries (u8 0-255, no cap in processpptables.c:1097/1130/1189). count>8 -> overflow past DpmTable into power_tune_defaults ptr -> dereferenced by ci_populate_svi_load_line. Sibling of DF-1136/DF-1141/DF-1166/DF-1179/DF-1271/DF-1272. Fix: check count<=SMU7_MAX_LEVELS_*.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1296 Β· 12 files
FileTypeDescriptionSize
harness.c trigger-source object-level proof: replays ci_populate_smc_uvd_level with VBIOS numEntries=40 > 8 5.1 KB view raw
fix.diff suggested-fix clamps UvdLevelCount/VceLevelCount/AcpLevelCount to SMU7_MAX_LEVELS_* 1.4 KB view raw
build.sh repro-script cc -O2 -o harness harness.c 101 B view raw
run.sh repro-script ./harness 60 B view raw
build.log build-log harness build (note GCC -Waggressive-loop-optimizations independently flags OOB) 65 B view raw
run.log run-log harness decisive run: 512-byte overflow into VceLevel/AcpLevel 529 B view raw
amdgpu_link_proof.txt build-log proof amdgpu.ko links with ci_smumgr.o (fix compiles in-tree) 23 B view raw
env.txt environment uname + cc version 188 B view raw
README.md readme summary + reproduce 2.3 KB ↓ raw
VERDICT.md verdict full mechanism + reachability + fix + in-tree compile validation 4.3 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 summary + reproduce
↓ download raw

DF-1296 β€” Unchecked VBIOS UVD/VCE/ACP level counts β†’ heap OOB write past fixed SMC arrays

File: sys/dev/drm/amd/powerplay/smumgr/ci_smumgr.c:1525 (UVD), :1566 (VCE), :1598 (ACP) Class: CWE-787 Out-of-bounds Write (heap) Severity: High

The bug (source-confirmed)

ci_populate_smc_uvd_level (ci_smumgr.c:1516) populates the SMC DPM table from the VBIOS UVD clock/voltage table:

table->UvdLevelCount = (uint8_t)(uvd_table->count);          /* :1525 */
for (count = 0; count < table->UvdLevelCount; count++) {     /* :1527 */
    table->UvdLevel[count].VclkFrequency = ...;              /* :1528+ */
    ...
}

UvdLevel is SMU7_Discrete_UvdLevel UvdLevel[SMU7_MAX_LEVELS_UVD] = UvdLevel[8] (smu7_discrete.h:328, SMU7_MAX_LEVELS_UVD = 8, smu7.h:45). uvd_table->count comes straight from the VBIOS power table's numEntries (processpptables.c:1097 copies it verbatim β€” a u8 0–255 with no cap against SMU7_MAX_LEVELS_UVD). count > 8 therefore writes past UvdLevel[8] into the adjacent DpmTable fields (VceLevel, AcpLevel, SamuLevel, Ulv, Smio[], …).

Identical twins: ci_populate_smc_vce_level (:1566/VceLevel[8]) and ci_populate_smc_acp_level (:1598/AcpLevel[8]). Sibling of DF-1136/1141/1166/ 1179/1271/1272 (same unchecked-VBIOS-count class).

Reachability / threat model

The ci (CIK / Bonaire/Hawaii/Kaveri) powerplay code runs only on AMD Sea Islands GPUs. The audit QEMU guest has a QEMU std-vga (pciconf vgapci0 chip=0x11111234), not an AMD GPU, so the path is not runtime-reachable here. On real AMD CIK hardware, the parse runs during DRM/powerplay init from the on-card VBIOS; a malicious/corrupt VBIOS (or a crafted VBIOS image on a passed-through GPU) with numEntries > 8 triggers the overflow. See VERDICT.md.

Reproduce (harness)

./build.sh && ./run.sh

Decisive output:

[DF-1296] attacker VBIOS UVD numEntries=40 (max array=8)
[DF-1296] BUG CONFIRMED: loop wrote UvdLevel[0..39] -> 32 entries (512 bytes) overflow past UvdLevel into VceLevel/AcpLevel/...
[DF-1296] adjacent VceLevel[0] corrupted: YES

Fix

fix.diff clamps UvdLevelCount/VceLevelCount/AcpLevelCount to the respective SMU7_MAX_LEVELS_* before the loop. Validated: applies cleanly; ci_smumgr.c compiles under -Werror and the full amdgpu.ko links in-tree.

VERDICT.md verdict full mechanism + reachability + fix + in-tree compile validation
↓ download raw

DF-1296 β€” VERDICT

Verdict: REPRODUCED (primitive confirmed at object/harness level; runtime path is hardware-bound and not present on the audit guest).

Mechanism (source trace)

The CIK (Bonaire/Hawaii) powerplay SMU manager populates the SMC discrete DPM table from the VBIOS-derived clock/voltage dependency tables.

  1. Attacker-controlled count β€” ci_smumgr.c:1525: c table->UvdLevelCount = (uint8_t)(uvd_table->count); uvd_table->count is uint8_t (hwmgr.h:138) copied verbatim from the VBIOS ATOM_PPLIB_UVD_Clock_Voltage_Limit_Table.numEntries at processpptables.c:1097 β€” no cap against SMU7_MAX_LEVELS_UVD.

  2. Fixed-size sink β€” ci_smumgr.c:1527-1528: c for (count = 0; count < table->UvdLevelCount; count++) table->UvdLevel[count].VclkFrequency = uvd_table->entries[count].vclk; UvdLevel is SMU7_Discrete_UvdLevel UvdLevel[SMU7_MAX_LEVELS_UVD] = UvdLevel[8] (smu7_discrete.h:328, smu7.h:45, 16 bytes/entry).

  3. Twins β€” ci_populate_smc_vce_level (:1566 β†’ VceLevel[8], SMU7_Discrete_ExtClkLevel, 8 bytes/entry) and ci_populate_smc_acp_level (:1598 β†’ AcpLevel[8]). vce_table->count uint8_t (hwmgr.h:184), acp_table->count uint32_t (hwmgr.h:148) β€” both uncapped.

count > 8 writes past the fixed array into the following SMU7_Discrete_DpmTable fields (VceLevel, AcpLevel, SamuLevel, Ulv, SclkStepSize, Smio[], boot-level fields, …) with attacker/VBIOS-shaped content (VclkFrequency, DclkFrequency, MinVddc, dividers). In the broader object layout these are followed by power_tune_defaults-derived pointers/data that are subsequently dereferenced β€” a corruption β†’ controlled-deref chain.

Primitive characterization

  • Write size: up to (count - 8) * 16 bytes (UVD) past UvdLevel[8], with attacker-shaped 32-bit VclkFrequency/DclkFrequency fields.
  • Target: the SMU7_Discrete_DpmTable heap object; overflow corrupts sibling level arrays and downstream tunable pointers.

Harness proof

harness.c builds a SMU7_Discrete_DpmTable slice (UvdLevel[8] + adjacent VceLevel[8] + a guard) and replays the loop with VBIOS numEntries = 40. Output (run.log):

[DF-1296] attacker VBIOS UVD numEntries=40 (max array=8)
[DF-1296] BUG CONFIRMED: loop wrote UvdLevel[0..39] -> 32 entries (512 bytes) overflow past UvdLevel into VceLevel/AcpLevel/SamuLevel/Ulv/Smio/...
[DF-1296] adjacent VceLevel[0] corrupted: YES (overflow into sibling field)

(GCC independently flags the OOB with -Waggressive-loop-optimizations during the harness build β€” the compiler itself detects the out-of-bounds write.)

Why not a live in-kernel reproduction (valid hard blocker)

The CIK powerplay path runs only on AMD Sea Islands GPUs. pciconf -l on the audit guest shows vgapci0 chip=0x11111234 (QEMU std-vga), not an AMD GPU; the amdgpu/ci driver never attaches, so ci_populate_smc_uvd_level never runs here. Live trigger conditions: a system with a CIK AMD GPU whose on-card VBIOS power table has numEntries > 8 (malicious/corrupt VBIOS, or a crafted image on a passed-through GPU). Primitive proven at the object/harness level. Escalation to uid=0 requires the primitive to fire in a running kernel β€” not demonstrable on this guest because the GPU driver never attaches. Honest reported impact: the heap corruption primitive itself.

Fix

fix.diff clamps each level count to its fixed array size before the loop:

table->UvdLevelCount = (uvd_table->count > SMU7_MAX_LEVELS_UVD) ?
    SMU7_MAX_LEVELS_UVD : (uint8_t)(uvd_table->count);

(and likewise VceLevelCount/SMU7_MAX_LEVELS_VCE, AcpLevelCount/SMU7_MAX_LEVELS_ACP). Validated: patch -p1 succeeds (all 3 hunks); ci_smumgr.c compiles cleanly under -Werror and the full amdgpu.ko module links in-tree (the link command includes ci_smumgr.o). Supersedes any pre-verification proposal by covering all three twins (UVD/VCE/ACP).

Fix-validation status

not_testable for a live before/after (PoC path cannot run on the guest β€” no AMD GPU). Evidence the fix is correct: (1) harness before/after shows the clamp keeps the loop in UvdLevel[0..7]; (2) ci_smumgr.c with the fix compiles under -Werror and amdgpu.ko links in-tree.

Fix verification

not_testable

compile+harness validated

module build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (harness). ci_populate_smc_uvd_level count unbounded vs UvdLevel[8] -> 512B OOB. amdgpu not in GENERIC.