Heap buffer overflow in si_get_svi2_voltage_table: unbounded VBIOS count writes past entries[32]
Summary
si_get_svi2_voltage_table at si_dpm.c:3951-3955: voltage_table->count=voltage_dependency_table->count (u8 from VBIOS, up to 255); loop for(i=0;i<count;i++) entries[i].value=dep_entries[i].v. entries[MAX_VOLTAGE_ENTRIES=32] fixed in radeon_mode.h. SVI2 voltage path selected when VBIOS advertises SVID2. GPIO path DOES trim via si_trim_voltage_table_to_fit_state_table but SVI2 branches at :3977-3982 and :3998-4004 call si_get_svi2_voltage_table with NO trimming. count>32 overflows into adjacent si_power_info fields (cac_weights, powertune pointers) -> RIP control during si_dpm_enable. Attacker: malicious VBIOS via reflash/KVM passthrough/QEMU emulated. Fix: clamp count to MAX_VOLTAGE_ENTRIES.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1136 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace replica of si_get_svi2_voltage_table count overflow + WITH-FIX pass | 5.9 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -Wextra -o harness harness.c | 114 B | view raw |
| run.sh | run-script | ./harness | 60 B | view raw |
| build.log | build-log | final build, full output | 8 B | view raw |
| run.log | run-log | decisive run incl CONFIRMED + FIX VALIDATED | 1.8 KB | view raw |
| env.txt | environment | uname, cc, pciconf (no AMD GPU) | 241 B | view raw |
| fix.diff | suggested-fix | clamp count to MAX_VOLTAGE_ENTRIES in si_get_svi2_voltage_table | 887 B | view raw |
| fix_validation.txt | fix-validation | apply-check + compile (si_dpm.o rc=0) + harness fix-demo | 1.7 KB | view raw |
| VERDICT.md | verdict | full narrative | 3.0 KB | β raw |
| README.md | readme | summary + repro | 2.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 |
DF-1136 β si_get_svi2_voltage_table VBIOS count overflow past entries[32]
Verdict
CONFIRMED (source-trace + harness) β INCONCLUSIVE on-guest (HW-gated). Real bug; not triggerable on the QEMU guest (no AMD Southern Islands GPU; radeon not in GENERIC).
Bug (one line)
si_get_svi2_voltage_table() sets count from the VBIOS-sourced dependency table and
loops for (i=0;i<count;i++) entries[i] = ... over a fixed entries[MAX_VOLTAGE_ENTRIES=32]
with no clamp; the two SVI2 call sites skip the trim that the GPIO path applies.
Mechanism (path:line)
si_dpm.c:3951βvoltage_table->count = voltage_dependency_table->count;(count sourced from VBIOS power tables, u8 0..255 upstream, stored in a u32 field; unbounded here).si_dpm.c:3952-3955βfor (i=0;i<count;i++) entries[i].value=...; entries[i].smio_low=0;radeon_mode.h:671,684βMAX_VOLTAGE_ENTRIES = 32;entries[32]fixed.count > 32=> writes pastentries[32]into the following fields ofstruct evergreen_power_info(the table is embedded there) β cac_weights, powertune pointers β corrupting state used bysi_dpm_enable.si_dpm.c:3977-3982(vddc) andsi_dpm.c:3998-4004(vddci) callsi_get_svi2_voltage_table()with no trim, unlike the GPIO path (si_dpm.c:3973-3976,3993-3996) which callssi_trim_voltage_table_to_fit_state_table.
Trigger / threat model
A malicious/corrupt VBIOS advertising SVI2 voltage control with count > 32
(reflash / KVM GPU passthrough / emulated radeon). Writes corrupt adjacent struct
fields -> corrupted pointers dereferenced during DPM enable -> panic or (no SMEP/SMAP)
potential code-exec. CVSS AC:H/PR:L.
Reproduction on the audit guest
Not possible β no AMD GPU; radeon not in GENERIC. harness.c replicates the loop with
VBIOS-style counts (8..255) and shows count>32 writes past entries[32], then the fix
(clamp to 32) yields 0 OOB.
Build / run
./build.sh && ./run.sh
Expected: DF-1136: CONFIRMED heap OOB write past entries[32] (VBIOS count unbounded)
then DF-1136 FIX: VALIDATED - clamp prevents all overflows past entries[32].
Fix
fix.diff clamps voltage_table->count to MAX_VOLTAGE_ENTRIES (matching the GPIO
path's trim intent). Applies cleanly; compiles in the radeon module build (si_dpm.o, rc=0).
DF-1136 β VERDICT
Verdict: CONFIRMED via source-trace + userspace harness. On-guest: INCONCLUSIVE (HW-gated β no AMD GPU; radeon not in GENERIC, only LINT64).
Root-cause confirmation
si_get_svi2_voltage_table() (si_dpm.c:3939-3958) copies the VBIOS-sourced count
straight into the fixed-size table:
voltage_table->count = voltage_dependency_table->count; (si_dpm.c:3951) then
for (i=0;i<count;i++){ entries[i].value=...; entries[i].smio_low=0; }
(si_dpm.c:3952-3955). struct atom_voltage_table.entries is a fixed
entries[MAX_VOLTAGE_ENTRIES] with MAX_VOLTAGE_ENTRIES = 32 (radeon_mode.h:671,684).
count originates in the VBIOS power tables (atom ucNumEntries, u8 0..255) and is
stored in a u32 field (radeon.h:1399-1401), so it is effectively unbounded from
the kernel's perspective. Any count > 32 writes past entries[32].
The defect is asymmetric in its callers: the GPIO voltage path trims via
si_trim_voltage_table_to_fit_state_table(SISLANDS_MAX_NO_VREG_STEPS)
(si_dpm.c:3973-3976 vddc, :3993-3996 vddci), but the two SVI2 call sites
(si_dpm.c:3977-3982 vddc, :3998-4004 vddci) call si_get_svi2_voltage_table()
with no trim. voltage_table is eg_pi->vddc_voltage_table / vddci_voltage_table,
embedded in struct evergreen_power_info, so entries[32+] overwrite the following
struct fields (cac_weights, powertune pointers) β corrupted state dereferenced during
si_dpm_enable.
Evidence
harness.c(run as unprivilegedmaxx) shows VBIOS counts 33/48/64/255 produce 1/16/32/223 OOB writes pastentries[32]; a concretecount=48model writes 128 bytes of an adjacent canary (128 = (48-32) * sizeof(atom_voltage_table_entry)).
Exploit chain / impact
This is a write primitive (CWE-787). On GENERIC it is not compiled in (radeon
absent). On real Southern Islands hardware with a malicious VBIOS, the writes corrupt
adjacent evergreen_power_info fields including pointers used by the DPM enable path
-> realistically a panic (corrupted-pointer deref) on INVARIANTS-ON GENERIC; on a
non-INVARIANTS build the corrupted pointers could be leveraged (no SMEP/SMAP) toward
code-exec. No uid=0 chain developed β path unreachable on this guest. Realistic
ceiling: panic / corruption.
Fix validation
fix.diff clamps voltage_table->count to MAX_VOLTAGE_ENTRIES (32) before the loop
(matching the GPIO path's trim intent).
- git apply --check -p1 => OK.
- Compiles in the radeon module: targeted make si_dpm.o built si_dpm.o (86880 B,
-Werror clean); full nativekernel rc=0.
- Harness "WITH FIX" pass: 0/6 counts produce any OOB.
- fix_status: not_testable (HW-gated runtime; apply-check + compile + harness fix-demo + trace all pass).
PoC changes
Evidence pack authored from scratch: harness.c (+ WITH-FIX pass), build.sh,
run.sh, fix.diff, VERDICT.md, manifest.json, logs.
Kernel refs (confirmed during verification)
sys/dev/drm/radeon/si_dpm.c:3951, :3952, :3977, :3998;
sys/dev/drm/radeon/radeon_mode.h:671, :684; sys/dev/drm/radeon/radeon.h:1399.
Fix verification
not_testablecompile+harness validated
module build rc=0 + harness 0 OOB
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed+harness. si_get_svi2_voltage_table count unbounded vs entries[32] -> up to 223 OOB writes. SVI2 path missing trim. radeon not in GENERIC.
No comments yet.