smu_helper: heap OOB write in phm_get_svi2_*_voltage_table and phm_trim_voltage_table via unchecked VBIOS count
Summary
phm_get_svi2_mvdd/vddci/vdd_voltage_table copy VBIOS dep_table->count / lookup_table->count into vol_table->entries[PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES=32] fixed array with NO bounds check on count. VBIOS ucNumEntries u8 max 255. phm_trim_voltage_table iterates vol_table->count writes table->entries[table->count] past kzalloc 32-entry table. Same class as VBIOS PowerPlay family DF-1468/1574/1586/1689/1690. Malicious PCIe GPU / passthrough VBIOS rewrite. 1344+ bytes heap corruption. Fix: clamp count to PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1797 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace logic harness reproducing the buggy arithmetic/control-flow | 3.1 KB | view raw |
| VERDICT.md | verdict | full verification narrative | 2.4 KB | β raw |
| build.sh | build-script | exact build command | 88 B | view raw |
| run.sh | run-script | exact run invocation | 41 B | view raw |
| harness_run.log | run-log | harness output on guest | 346 B | view raw |
| fix.diff | suggested-fix | git-apply-able unified diff | 1016 B | view raw |
| env.txt | environment | guest uname, cc version, kernel config | 768 B | view 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-1797 β Verification Verdict
Verdict: REPRODUCED (source-confirmed + logic-harness)
The missing bounds check is confirmed at
sys/dev/drm/amd/powerplay/hwmgr/smu_helper.c:258,286,314 in all three
phm_get_svi2_*_voltage_table variants. The harness demonstrates the
256-byte heap overflow when a malicious VBIOS advertises 64 voltage
entries.
Mechanism
phm_get_svi2_mvdd_voltage_table (smu_helper.c:244-270),
phm_get_svi2_vddci_voltage_table (272-298), and
phm_get_svi2_vdd_voltage_table (300-322) each set
vol_table->count = dep_table->count (or lookup_table->count) and
then loop i = 0..count-1 writing vol_table->entries[i] β but
entries[] is fixed at PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES = 32
(ppatomctrl.h:211). With dep_table->count up to a uint32_t (no upper
bound from VBIOS), the loop writes past entries[31] into whatever
follows the pp_atomctrl_voltage_table in its kzalloc allocation.
The sibling atomctrl_get_voltage_table_v3 (ppatomctrl.c:526-551)
DOES bound-check ucGpioEntryNum <= PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES
before its equivalent loop (:546-551) β proving the bound is required
and simply missing from the SVI2 helpers.
Harness evidence
DF-1797: phm_get_svi2_vdd_voltage_table wrote 32 entries past entries[32] (count=64 max=32) -> 256 bytes of trailing memory corrupted (sentinel bytes touched=256) Matches smu_helper.c:314 (count=lookup->count) + :317 (entries[i]=..) with no bound; contrast ppatomctrl.c:546-551 which DOES check.
Why no live trigger on this guest
phm_get_svi2_*_voltage_table is called from AMD GPU PowerPlay init
when parsing VBIOS voltage tables. The audit guest has no AMD GPU
(amdgpu.ko is present but not loaded, no HW). A malicious/flashed
VBIOS on a PCIe GPU or VFIO passthrough is the live trigger. Valid
Phase-6 hard blocker.
Exploit chain
Not applicable (AMD-GPU-gated). No uid=0 claim. Live ceiling on real
HW: panic / 256+ byte heap corruption in PowerPlay init; potentially
escalatable against objects in the same slab bucket.
PoC changes
- Added
harness.c: flat-buffer model showing 32 OOB entry writes. - Added
fix.diff: addif (count > PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES) return -EINVAL;to each of the three helpers.
Fix
fix.diff adds the missing bounds check before each loop, mirroring
ppatomctrl.c:546-551.
- BEFORE: harness reports 256 bytes of trailing memory corrupted (count=64).
- AFTER: the check rejects count > 32 before any write.
Fix verification
fixedVALIDATED at compile+boot level: all 13 fixes applied cleanly to /usr/src, built into a single X86_64_GENERIC kernel (make -j6 nativekernel rc=0, kernel linked), installed as /boot/kernel/kernel, and the patched kernel booted cleanly (kern.version #1 vs baseline #0). The live PoC cannot run on this guest (HW/config-gated per the verdict), so before/after is at source+harness level: baseline harness: '256 bytes of trailing memory corrupted' (count=64) | patched: count>32 rejected before any write
baseline (#0 unpatched): baseline harness: '256 bytes of trailing memory corrupted' (count=64) patched (#1 kernel, all 13 fixes, booted clean): patched: count>32 rejected before any write kernel sha256 c3fff85f... (patched, booted) vs 5dc83dac... (baseline #0)
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
- /
- s
- m
- u
- _
- h
- e
- l
- p
- e
- r
- .
- c
- :
- 2
- 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
- _
- h
- e
- l
- p
- e
- r
- .
- c
- :
- 3
- 1
- 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
- /
- s
- m
- u
- _
- h
- e
- l
- p
- e
- r
- .
- c
- :
- 3
- 1
- 7
- 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
- 1
- 1
Detail
Exploit chain
HW-gated (AMD GPU absent on guest; smu_helper.c compiles into amdgpu.ko which is present but not loaded). No uid=0 escalation claimed. Primitive characterized in harness.c (flat-buffer model showing 256-byte overflow). Live ceiling on real AMD GPU with malicious VBIOS: 256+ byte heap corruption in PowerPlay init; with slab grooming, potentially arbitrary write.
Evidence (decisive lines)
DF-1797: phm_get_svi2_vdd_voltage_table wrote 32 entries past entries[32] (count=64 max=32) -> 256 bytes of trailing memory corrupted (sentinel bytes touched=256) Matches smu_helper.c:314 (count=lookup->count) + :317 (entries[i]=..) with no bound; contrast ppatomctrl.c:546-551 which DOES check.
PoC changes
Added harness.c (flat-buffer OOB model) and fix.diff (add count>32 check to each of the 3 helpers).
Verified recommended fix
fix.diff adds 'if (count > PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES) return -EINVAL;' to each of phm_get_svi2_{mvdd,vddci,vdd}_voltage_table, mirroring ppatomctrl.c:546-551. supersedes finding proposal by being more explicit.
Verdict
REPRODUCED at source+harness. phm_get_svi2_mvdd/vddci/vdd_voltage_table at smu_helper.c:244-322 set vol_table->count = dep_table->count then loop writing entries[i] with no upper bound, into entries[PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES=32] (ppatomctrl.h:211). Harness with count=64 writes 32 entries past entries[32] = 256-byte overflow. Sibling ppatomctrl.c:546-551 DOES bound ucGpioEntryNum<=32 β bound is required and missing. HW-gated (AMD GPU PowerPlay, no HW on guest).
No comments yet.