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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1136 Β· 12 files
FileTypeDescriptionSize
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
README.md readme summary + repro
↓ download 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 past entries[32] into the following fields of struct evergreen_power_info (the table is embedded there) β€” cac_weights, powertune pointers β€” corrupting state used by si_dpm_enable.
  • si_dpm.c:3977-3982 (vddc) and si_dpm.c:3998-4004 (vddci) call si_get_svi2_voltage_table() with no trim, unlike the GPIO path (si_dpm.c:3973-3976, 3993-3996) which calls si_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).

VERDICT.md verdict full narrative
↓ download raw

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 unprivileged maxx) shows VBIOS counts 33/48/64/255 produce 1/16/32/223 OOB writes past entries[32]; a concrete count=48 model 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_testable

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