vega10 clock getters overflow the caller's 16-entry stack array: unbounded VBIOS dep-table counts (DF-3684 twin)
Summary
vega10_get_sclks/memclocks/dcefclocks/socclocks and get_clock_by_type_with_voltage write clocks->data[i]/num_levels with the ONLY bound being the VBIOS dependency-table count (ATOM ucNumEntries, uint8, <=255, uncapped by the parser). The destination data[] is PP_MAX_CLOCK_LEVELS=16 and the DC caller holds it ON THE STACK (amdgpu_dm_pp_smu.c). ucNumEntries>16 => up to 255x8 bytes written past a 132-byte stack object with attacker-scaled 32-bit values (ulClk*10). get_memclocks ALSO heap-overflows mclk_latency_table.entries[8] inside the vega10_hwmgr backend when mclk count>8. Crafted-VBIOS Vega10: during normal driver/DC bring-up a SOCCLK/GFXCLK/MCLK dependency table with ucNumEntries>16 smashes the kernel stack - control-flow hijack on default deployments. Userspace stand-in demonstrably corrupted its own stack-adjacent loop counter on the guest; kernel path hardware-gated (virtio-only) - inconclusive, source-certain.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3701 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.8 KB | β raw | |
| VERDICT.md | β | 3.5 KB | β raw | |
| trigger_shape.c | β | 2.5 KB | view raw | |
| build.sh | β | 142 B | view raw | |
| run.sh | β | 153 B | view raw | |
| build.log | β | 280 B | view raw | |
| run.log | β | 1.4 KB | view raw | |
| guest_build_run.log | β | 280 B | view raw | |
| env.txt | β | 848 B | view raw | |
| fix.diff | β | 1.8 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 3.5 KB | view raw |
DF-3701 β vega10 clock getters overflow caller's 16-entry stack array (VBIOS dep-table counts unbounded)
File: sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c (pass 2) Class: kernel stack buffer overflow (CWE-787/CWE-129), smu7 DF-3684 vega10 twin.
Affected sinks (all write clocks->data[i] / num_levels with only the VBIOS
ucNumEntries as bound; dest is data[PP_MAX_CLOCK_LEVELS=16]):
- vega10_get_sclks vega10_hwmgr.c:4115-4133 (writes data[num_levels++] :4127)
- vega10_get_memclocks vega10_hwmgr.c:4135-4159 (data[j] :4149; ALSO heap OOB write
data->mclk_latency_table.entries[j] :4151/:4154 β entries[8])
- vega10_get_dcefclocks vega10_hwmgr.c:4161-4175 (:4171)
- vega10_get_socclocks vega10_hwmgr.c:4177-4191 (:4187)
- vega10_get_clock_by_type_with_voltage vega10_hwmgr.c:4217-4257 (:4247)
Caller supplies the buffer ON THE STACK:
- display/amdgpu_dm/amdgpu_dm_pp_smu.c:368-389
dm_pp_get_clock_levels_by_type_with_latencydeclaresstruct pp_clock_levels_with_latency pp_clks = { 0 };(stack, 132 bytes) and passes it down: pp_get_clock_by_type_with_latency (powerplay/amd_powerplay.c:1120) -> phm_get_clock_by_type_with_latency -> vega10_get_clock_by_type_with_latency (:4193). - Same pattern for voltage: amdgpu_dm_pp_smu.c:391-414 (
pp_clk_info = {0}on stack). - include/dm_pp_interface.h:28
#define PP_MAX_CLOCK_LEVELS 16, :174-177 data[16].
Source bound is attacker-controlled:
- powerplay/hwmgr/vega10_processpptables.c get_socclk_voltage_dependency_table :528-540,
get_mclk_... :562-574, get_gfxclk_..., get_dcefclk_... :727-753, pix/disp/phy (same pattern):
clk_table->count = (uint32_t)clk_dep_table->ucNumEntries;β ucNumEntries is a raw uint8 (1..255) from the VBIOS powerplay table with NO cap against PP_MAX_CLOCK_LEVELS / 8. The tables are kzalloc'd with the right size, so counts > 16 reach the hwmgr intact.
Trigger (requires AMD Vega10 hardware):
Craft the ATOM_Vega10_POWERPLAYTABLE in the dGPU VBIOS so e.g. usSocclkDependencyTableOffset points at a SOCCLK_Dependency_Table with ucNumEntries=64, all ulClk nonzero. Boot with the card; during DC display enumeration dm_pp_get_clock_levels_by_type_with_latency runs amd_pp_soc_clock -> vega10_get_socclocks, writing 64 x 8 bytes into a 132-byte stack frame buffer -> return-address smash with attacker-scaled values (ulClk*10, 32-bit each). Same shape works for SCLK/MCLK (skip-zero variant in get_sclks), DCEFCLK, and the with_voltage getters (mclk/dcef/disp/pix/phy).
PoC status on the audit guest
Guest is virtio-only (see env.txt: 0 AMD PCI devices, amdgpu.ko present but never binds). The vega10 hwmgr cannot instantiate, so no runtime repro is possible on this guest; verdict is source-certain + hardware-gated inconclusive. See VERDICT.md.
DF-3701 VERDICT β vega10 clock-getter stack overflow (DF-3684 twin)
status: inconclusive (hardware-gated) β source-certain. reproduced=0 on audit guest.
Why this is a real bug (line-by-line)
vega10_get_socclocks(sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c:4177-4191): for (i = 0; i < dep_table->count; i++) { clocks->data[i]... ; clocks->num_levels++; } β the ONLY bound isdep_table->count.dep_table=table_info->vdd_dep_on_socclk, built byget_socclk_voltage_dependency_table(vega10_processpptables.c:520-540):clk_table->count = (uint32_t)clk_dep_table->ucNumEntries;β ucNumEntries is an untrusted uint8 from the VBIOS powerplay table, unbounded (allocation matches it, so no earlier corruption is needed; counts up to 255 flow through intact).clockspoints atstruct pp_clock_levels_with_latency pp_clks = { 0 };declared ON THE CALLER'S STACK indm_pp_get_clock_levels_by_type_with_latency(display/amdgpu_dm/amdgpu_dm_pp_smu.c:375); the struct is{ uint32_t num_levels; struct pp_clock_with_latency data[PP_MAX_CLOCK_LEVELS]; }(include/dm_pp_interface.h:174-177, PP_MAX_CLOCK_LEVELS=16). => ucNumEntries > 16 writes past a 132-byte stack object with 32-bit attacker-influenced values (le32_to_cpu(ulClk) * 10); this smashes the adjacent frame(s) β classic kernel stack control-flow hijack primitive (no SMEP/SMAP/KASLR on many deployments, incl. DragonFly defaults).- Identical pattern: get_sclks :4125-4131, get_dcefclocks :4170-4174,
get_clock_by_type_with_voltage :4246-4251 (stack twin at amdgpu_dm_pp_smu.c:399),
and get_memclocks :4146-4158 which ADDITIONALLY heap-overflows
data->mclk_latency_table.entries[j](fixedentries[MAX_REGULAR_DPM_NUMBER=8], vega10_hwmgr.h:218-220) inside thestruct vega10_hwmgrbackend allocation when the MCLK dep count exceeds 8.
Reachability
DC calls dm_pp_get_clock_levels_by_type_with_latency during display/clock-manager
initialization on Vega10 (amdgpu_dm); no sysfs/ioctl needed β it fires during normal
driver bring-up once the crafted-VBIOS card is present.
Why not reproduced on the guest
pciconf -l on the audit guest shows zero AMD (0x1002) PCI devices; the display
adapter is QEMU std VGA. /boot/kernel/amdgpu.ko exists and prints
"[drm] amdgpu kernel modesetting enabled", but hwmgr_hw_init/vega10_hwmgr_backend_init
are only reached per-AMD-device, so no vega10_hwmgr object ever exists on this guest.
Honest classification: inconclusive (cannot exercise the driver on this hardware);
the defect itself is proven at source level with exact types/bounds above.
Exploit chain (if the hardware precondition is met)
crafted VBIOS pptable (physical reflash / malicious eGPU / VFIO-passthrough of a doctored card) -> driver load -> DC clock query -> stack smash with attacker-scaled 32-bit writes -> kernel ROP -> uid=0. (Chain not demonstrated: no Vega10 hardware in the audit environment.)
Fix
fix.diff clamps every getter loop to the destination capacity
(i < dep_table->count && i < PP_MAX_CLOCK_LEVELS), mirroring the guard upstream
Linux added (and the one already present in vega10_populate_single_display_type:1850).
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
not_testablefix.diff authored against read-only sys/ tree; cannot build/test a Vega10 kernel path on the virtio-only guest (no AMD device to bind amdgpu), so no patched-kernel validation is possible
fix.diff; hardware gate in run.log
Confirmed kernel references
- sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c:4115
- sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c:4135
- sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c:4161
- sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c:4177
- sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c:4246
- sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c:375
- sys/dev/drm/amd/include/dm_pp_interface.h:28
- sys/dev/drm/amd/powerplay/hwmgr/vega10_processpptables.c:540
Detail
Exploit chain
crafted VBIOS pptable (SOCCLK/MCLK/GFXCLK/DCEFCLK dep table ucNumEntries>16) -> driver/DC bring-up calls dm_pp_get_clock_levels_by_type_with_latency -> vega10_get_ writes attacker-scaled 32-bit values (ulClk10) past a 132-byte stack object -> return-address/frame smash -> kernel ROP -> uid=0 (chain hardware-gated, not demonstrated here)
Evidence (decisive lines)
VERDICT.md (line-by-line trace); run.log (guest hardware gate: pciconf 0x1002 count=0; guest /usr/src excerpt of the unbounded loop at :4186-4190; stand-in demo whose own loop counter was overwritten by the OOB writes); trigger_shape.c; fix.diff
PoC changes
no runnable kernel PoC possible on virtio-only guest; wrote userspace stand-in of the getter loop that reproduced adjacent-stack-local corruption; verified cited lines byte-identical in guest /usr/src
Verified recommended fix
Clamp all five getter loops to the destination capacity: i < dep_table->count && i < PP_MAX_CLOCK_LEVELS (and num_levels=0 init), mirroring vega10_populate_single_display_type:1850 guard
Verdict
Source-certain kernel stack buffer overflow (DF-3684 smu7 twin): vega10_get_sclks/memclocks/dcefclocks/socclocks and vega10_get_clock_by_type_with_voltage write clocks->data[i] for i < VBIOS-supplied dep_table->count (ucNumEntries, uint8, <=255, uncapped at vega10_processpptables.c:540 etc.) into struct pp_clock_levels_with_latency whose data[] is PP_MAX_CLOCK_LEVELS=16 and which the DC caller holds on its stack (amdgpu_dm_pp_smu.c:375/:399). vega10_get_memclocks additionally heap-overflows mclk_latency_table.entries[8] inside struct vega10_hwmgr. Not reproducible on the audit guest: 0 AMD PCI devices (virtio-only), amdgpu.ko present but never binds, so the vega10 hwmgr code path cannot execute; a userspace stand-in of the loop math (built with guest cc 8.3) demonstrated live corruption of stack-adjacent locals. Realization requires a Vega10 dGPU with crafted VBIOS powerplay table (flash/passthrough threat model).
No comments yet.