Heap OOB write past SISLANDS_SMC_STATETABLE.lowSMIO[32] via untrimmed SVI2 VDDCI voltage-table count in si_populate_smc_voltage_table β radeon twin of the lowSMIO half of amdgpu DF-3585
Summary
Mixed-VRM crafted VBIOS (VDDC GPIO_LUT with VDDCI SVID2, no VDDCI GPIO): si_construct_voltage_tables sets vddci_voltage_table.count = vddci_dependency_on_mclk.count (u8, up to 255) with NO trim (GPIO paths trim). Because voltage_control_svi2 is false, the else branch calls si_populate_smc_voltage_table, whose loop OR-writes table->lowSMIO[i] for i<count into the packed 3012-byte SISLANDS_SMC_STATETABLE embedded in the kzalloc'd si_power_info (lowSMIO is uint32[32]). count 33..255 writes up to 1020 bytes past lowSMIO[0], corrupting voltageMaskTable, phaseMaskTable, dpm2Params, the four SWSTATEs and dpmLevels - data uploaded verbatim to the GPU power-management microcontroller (wrong voltage masks/TDP limits/state clocks; under/overvoltage risk). Overflow contained within the single si_power_info allocation (max touched offset 1087 vs 3012) - hence Medium. The entries[] write of the same root cause is pass-1 DF-1136; this sink survives an entries-only clamp. VERIFIED: verbatim packed struct replicas; gcc itself warns 'iteration 32 invokes undefined behavior'; [A] lowSMIO[32]=0xa5a55a5a (OOB start) with voltageMaskTable/phaseMaskTable/dpm2Params.TDPLimit/initialState all corrupted; fixed-path [B] tail intact.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3614 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | β | 10.4 KB | view raw | |
| build.sh | β | 66 B | view raw | |
| run.sh | β | 90 B | view raw | |
| build.log | β | 1.0 KB | view raw | |
| run.log | β | 1.5 KB | view raw | |
| fix.diff | β | 315 B | view raw | |
| fix_build.log | β | 1.6 KB | view raw | |
| fix_run.log | β | 250 B | view raw | |
| VERDICT.md | β | 4.6 KB | β raw | |
| README.md | β | 2.2 KB | β raw | |
| manifest.json | β | 844 B | view raw | |
| verdict.json | β | 4.3 KB | view raw |
DF-3614 β Heap OOB write past SISLANDS_SMC_STATETABLE.lowSMIO[32] via untrimmed SVI2 VDDCI voltage-table count in si_populate_smc_voltage_table
Severity: Medium Confidence: certain Status: reproduced (harness) Impact: dos
What
Mixed-VRM VBIOS: VDDC via GPIO_LUT (voltage_control true) + VDDCI via SVID2 (vddci_control false, vddci_control_svi2 true). si_construct_voltage_tables:3998-4004 fills vddci_voltage_table.count = vddci_dependency_on_mclk.count (u8, up to 255) with NO trim (the GPIO paths trim at :3973/:3993/:4020). si_populate_smc_voltage_tables then takes the non-SVI2-VDDC else branch (:4058 false since VDDC is GPIO) and at :4079-4080 calls si_populate_smc_voltage_table, whose loop at :4046 OR-writes table->lowSMIO[i] for i<count into the packed STATETABLE embedded in the kzalloc'd si_power_info (si_dpm.h:196). count 33..255 writes up to 1020 bytes past lowSMIO[0] (struct is 3012 B; overflow is contained within the si_power_info allocation) corrupting voltageMaskTable, phaseMaskTable, dpm2Params, initialState/ACPIState/ULVState/driverState and dpmLevels β data that si_init_smc_table uploads verbatim to the GPU SMC at :4779-4781, programming wrong voltage masks / TDP limits / state clocks (hardware misconfiguration, undervoltage/overvoltage risk). Harness with verbatim packed struct replicas from sislands_smc.h confirms the exact corrupted fields; gcc itself flags the replica loop (-Warray-bounds: 'iteration 32 invokes undefined behavior').
Reproduce
sh build.sh # cc -O2 -Wall -o harness harness.c sh run.sh # prints [A] buggy-path behaviour, [B] fixed-path behaviour
Expected decisive output (see run.log): the [A] section shows the fault or
corruption the kernel would take; the [B] section shows the behaviour with
fix.diff applied.
No AMD GPU is attached to the audit QEMU guest (virtio-only), so the radeon DPM path cannot be exercised live; the harness replicates the exact kernel expressions with crafted-VBIOS inputs (house methodology, cf. DF-1137..1140).
Fix
fix.diff β validated: git apply --check clean; make si_dpm.o in
sys/dev/drm/radeon with KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC
compiles -Werror clean with all five pass-2 fixes stacked; harness [B]
shows the primitive gone.
DF-3614 β Verdict
Verdict: REPRODUCED (source-level + harness) β OOB write past lowSMIO[32], confined to the si_power_info allocation; corrupts SMC-bound power configuration
radeon twin of the lowSMIO sink half of amdgpu DF-3585. The entries[]
half of the same root cause is pass-1 DF-1136; this sink is a distinct
write primitive that survives an entries-only clamp.
Bug confirmation
Reachability β all three flags are VBIOS-derived (si_dpm_init):
si_dpm.c:7001-7003 pi->voltage_control = VDDC advertises GPIO_LUT (true)
si_dpm.c:7017-7023 eg_pi->vddci_control = VDDCI GPIO_LUT (false)
si_pi->vddci_control_svi2= VDDCI SVID2 (true)
si_dpm.c:7004-7011 si_pi->voltage_control_svi2 = VDDC SVID2 (false)
Data flow:
si_dpm.c:3967-3976 VDDC GPIO table fetched AND TRIMMED to 32
si_dpm.c:3998-4004 vddci_control_svi2 -> si_get_svi2_voltage_table(
&dyn_state.vddci_dependency_on_mclk, &vddci_voltage_table)
-> vddci_voltage_table.count = dependency count (u8, <=255)
NO TRIM (GPIO paths trim at :3973/:3993/:4020)
[entries[] overflow there == DF-1136; the COUNT survives]
si_dpm.c:4058 if (si_pi->voltage_control_svi2) { ... } else { // else taken:
si_dpm.c:4079 if (eg_pi->vddci_voltage_table.count) {
si_dpm.c:4080 si_populate_smc_voltage_table(rdev,
&eg_pi->vddci_voltage_table, table);
si_dpm.c:4046-4047 for (i = 0; i < voltage_table->count; i++)
table->lowSMIO[i] |= cpu_to_be32(entries[i].smio_low);
lowSMIO is uint32_t[32] (sislands_smc.h:207/215) inside the packed
3012-byte SISLANDS_SMC_STATETABLE, embedded in the kzalloc'd
si_power_info (si_dpm.h:196). With count 33..255 the OR-write walks up to
1020 bytes past lowSMIO[0], corrupting voltageMaskTable,
phaseMaskTable, dpm2Params, the four SWSTATEs and dpmLevels[] β
everything that si_init_smc_table uploads to the GPU power-management
microcontroller at si_dpm.c:4779-4781 (si_copy_bytes_to_smc(... sizeof
(SISLANDS_SMC_STATETABLE) ...)). Consequence: wrong VDDC/VDDCI/MVDD masks,
wrong TDP limits, wrong state clocks programmed on the GPU β miscalibrated
voltages (undervoltage instability / overvoltage stress) from a crafted
VBIOS. The overflow is CONTAINED within the single si_power_info allocation
(max touched offset 1087 vs struct size 3012+), so no cross-object kernel
heap corruption β hence Medium, not High.
Harness confirmation
run.log (verbatim packed struct replicas from sislands_smc.h):
sizeof(SISLANDS_SMC_STATETABLE) = 3012; lowSMIO spans 4..132
last byte touched by buggy loop: lowSMIO[254] at offset 1020
[A] lowSMIO[32]=0xa5a55a5a (OOB start)
voltageMaskTable.lowMask[0] (VDDC mask) = 0xa5a55a5a CORRUPTED
phaseMaskTable.lowMask[0] = 0xa5a55a5a CORRUPTED
dpm2Params.TDPLimit = 0xa5a55a5a CORRUPTED
initialState.levels[0].sclk.sclk_value = 0xa5a55a5a CORRUPTED
first corrupted byte at allocation offset 68; last touched offset 1087
[B] lowSMIO[32]=0x00000000 (untouched) ... dpm2Params.TDPLimit intact
GCC independently flags the replica loop:
warning: iteration 32 invokes undefined behavior [-Warray-bounds] /
array subscript 32 is above array bounds of 'uint32[32]' β the compiler's
own confirmation that i>=32 is out of bounds.
Trigger conditions (not met on this guest)
SI GPU + radeon DPM (virtio-only guest) + mixed-VRM VBIOS (VDDC GPIO_LUT,
VDDCI SVID2) with vddci_dependency_on_mclk.ucNumEntries > 32.
Exploit chain
Contained corruption β no cross-object primitive, no kernel-memory info leak; the attacker-visible effect is tampered GPU power config (hardware risk), so impact is DoS-grade for the kernel itself.
Fix
fix.diff β bound the sink loop:
for (i = 0; i < voltage_table->count && i < SISLANDS_MAX_NO_VREG_STEPS; i++)
(defense-in-depth at the sink; independently recommended alongside the
DF-1136 root-cause clamp in si_get_svi2_voltage_table).
Validation: apply-check OK; make si_dpm.o -Werror clean (stacked);
harness [B] shows the statetable tail intact after the fix.
fix_status: fixed (compile + harness-level).
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedgit apply --check OK (standalone and stacked with the other four si_dpm.c fixes); targeted module TU compile make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC si_dpm.o => rc=0, -Werror clean (87080 B). Harness fixed-path shows the primitive gone (fix.diff bounds the sink loop: for (i = 0; i < voltage_table->count && i < SISLANDS_MAX_NO_VREG_STEPS; i++). git apply --check OK; stacked module TU compile -Werror clean; harness [B] shows the statetable tail intact.)
fix.diff bounds the sink loop: `for (i = 0; i < voltage_table->count && i < SISLANDS_MAX_NO_VREG_STEPS; i++)`. git apply --check OK; stacked module TU compile -Werror clean; harness [B] shows the statetable tail intact.
Confirmed kernel references
Detail
Exploit chain
crafted VBIOS: VDDC GPIO_LUT + VDDCI SVID2 + vddci_dependency_on_mclk.ucNumEntries>32 -> si_get_svi2_voltage_table sets count untrimmed -> si_populate_smc_voltage_tables else-branch -> si_populate_smc_voltage_table loop -> lowSMIO[32..254] |= value -> SMC statetable fields corrupted -> uploaded to GPU power-management microcontroller (wrong voltage masks/TDP/clocks)
Evidence (decisive lines)
run.log [A]: 'lowSMIO[32]=0xa5a55a5a (OOB start)'; 'voltageMaskTable.lowMask[0] (VDDC mask) = 0xa5a55a5a CORRUPTED'; 'dpm2Params.TDPLimit = 0xa5a55a5a CORRUPTED'; 'initialState.levels[0].sclk.sclk_value = 0xa5a55a5a CORRUPTED'; 'last touched offset 1087' inside 3012-byte STATETABLE; [B] fixed: 'lowSMIO[32]=0x00000000 (untouched)'; build warnings: 'iteration 32 invokes undefined behavior' [-Warray-bounds]
PoC changes
n/a (pass-2 finding authored with its harness; no seeded PoC to fix)
Verified recommended fix
see fix.diff
Verdict
radeon twin of the lowSMIO half of amdgpu DF-3585 (the entries[] half is pass-1 DF-1136; both stem from the untrimmed SVI2 dependency count, but the lowSMIO sink at :4046-4047 survives an entries-only clamp and is a distinct write primitive). Mixed-VRM VBIOS: VDDC via GPIO_LUT (voltage_control true) + VDDCI via SVID2 (vddci_control false, vddci_control_svi2 true). si_construct_voltage_tables:3998-4004 fills vddci_voltage_table.count = vddci_dependency_on_mclk.count (u8, up to 255) with NO trim (the GPIO paths trim at :3973/:3993/:4020). si_populate_smc_voltage_tables then takes the non-SVI2-VDDC else branch (:4058 false since VDDC is GPIO) and at :4079-4080 calls si_populate_smc_voltage_table, whose loop at :4046 OR-writes table->lowSMIO[i] for i<count into the packed STATETABLE embedded in the kzalloc'd si_power_info (si_dpm.h:196). count 33..255 writes up to 1020 bytes past lowSMIO[0] (struct is 3012 B; overflow is contained within the si_power_info allocation) corrupting voltageMaskTable, phaseMaskTable, dpm2Params, initialState/ACPIState/ULVState/driverState and dpmLevels β data that si_init_smc_table uploads verbatim to the GPU SMC at :4779-4781, programming wrong voltage masks / TDP limits / state clocks (hardware misconfiguration, undervoltage/overvoltage risk). Harness with verbatim packed struct replicas from sislands_smc.h confirms the exact corrupted fields; gcc itself flags the replica loop (-Warray-bounds: 'iteration 32 invokes undefined behavior').
No comments yet.