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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3701 Β· 12 files
FileTypeDescriptionSize
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_latency declares struct 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.

VERDICT.md
↓ download raw

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)

  1. 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 is dep_table->count.
  2. dep_table = table_info->vdd_dep_on_socclk, built by get_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).
  3. clocks points at struct pp_clock_levels_with_latency pp_clks = { 0 }; declared ON THE CALLER'S STACK in dm_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).
  4. 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] (fixed entries[MAX_REGULAR_DPM_NUMBER=8], vega10_hwmgr.h:218-220) inside the struct vega10_hwmgr backend 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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-3701

Confirmed kernel references

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).