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

Integer overflow in u32 bounds checks in ci_set_smc_sram_address and ci_copy_bytes_to_smc (wraparound defeats SMC SRAM upper bound)

Field Value
ID DF-2105
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H
CWE CWE-190 Integer Overflow or Wraparound
File sys/dev/drm/radeon/ci_smc.c
Lines 36-39, 57-60
Area drm/radeon
Confidence likely
Discovered 2026-07-25
Reported pending
Known CVE none
CVE match variant

Summary

Both bounds checks that gate writes into SMC SRAM use 32-bit unsigned addition that wraps on overflow: (smc_address + 3) > limit and (smc_start_address + byte_count) > limit. A large attacker-influenced smc_start_address near UINT32_MAX causes the sum to wrap below limit, the check passes, and the kernel performs an MMIO write of an attacker-controlled 32-bit value to SMC_IND_INDEX_0. The address source is ultimately the firmware-controlled pm_fuse_table_offset read out of SMC SRAM in ci_dpm.c, so a crafted SMC firmware can drive this path.

Root cause

ci_set_smc_sram_address (ci_smc.c:33-45):

static int ci_set_smc_sram_address(struct radeon_device *rdev,
                                   u32 smc_address, u32 limit)
{
    if (smc_address & 3)                    // ci_smc.c:36  -- alignment, fine
        return -EINVAL;
    if ((smc_address + 3) > limit)          // ci_smc.c:38  -- u32 add, WRAPS
        return -EINVAL;
    WREG32(SMC_IND_INDEX_0, smc_address);   // ci_smc.c:41

and ci_copy_bytes_to_smc (ci_smc.c:47-60):

    if (smc_start_address & 3)              // ci_smc.c:57
        return -EINVAL;
    if ((smc_start_address + byte_count) > limit)   // ci_smc.c:59  -- u32 add, WRAPS
        return -EINVAL;

Example: smc_address = 0xFFFFFFFD, limit = 0x40000. Then smc_address + 3 = 0x100000000 truncates to 0 in u32, and 0 > 0x40000 is false, so the bound check at ci_smc.c:38 is bypassed and execution proceeds to WREG32(SMC_IND_INDEX_0, 0xFFFFFFFD) at ci_smc.c:41 β€” an MMIO write of an attacker-controlled 32-bit value. Likewise for the outer check at ci_smc.c:59 with smc_start_address = 0xFFFFFFFF and byte_count = 4: 0xFFFFFFFF + 4 = 3, 3 > 0x40000 is false, check passes, and ci_copy_bytes_to_smc descends into its loop with addr = 0xFFFFFFFF.

Reachability: ci_dpm.c:478 reads pm_fuse_table_offset out of SMC SRAM at SMU7_FIRMWARE_HEADER_LOCATION + offsetof(SMU7_Firmware_Header, PmFuseTable). After the SMC firmware is loaded (which is attacker-controlled per DF-2104), that SRAM location holds attacker-controlled data, and the resulting pm_fuse_table_offset is then passed verbatim as smc_start_address to ci_copy_bytes_to_smc at ci_dpm.c:508 with byte_count = sizeof(SMU7_Discrete_PmFuses) (a compile-time constant

4). The overflow at ci_smc.c:59 fires, the inner ci_set_smc_sram_address call at ci_smc.c:69 then hits the overflow at ci_smc.c:38, and the WREG32 at ci_smc.c:41 writes 0xFFFFFFFD (or whatever the attacker chose) to SMC_IND_INDEX_0. The same pattern reaches the other ci_dpm.c callers that pass firmware-derived offsets (arb_table_start, soft_regs_start, smc_powertune_table offsets).

Threat model & preconditions

  • Attacker position: same as DF-2104 (firmware replacement). Combined with DF-2104 the attacker fully controls the SRAM dword that becomes pm_fuse_table_offset.
  • Privileges gained or impact: attacker-selected 32-bit value written to the SMC indirect-index MMIO register, beyond the SMC_RAM_END ceiling the limit parameter was supposed to enforce. Downstream of that MMIO write, every WREG32(SMC_IND_DATA_0, ...) writes to an attacker-chosen offset in the GPU indirect SRAM window β€” i.e. the limit check that the rest of the file believes is enforcing the upper bound is silently defeated for any caller passing a near-UINT32_MAX address. On platforms where the SMC indirect window aliases other MMIO space, this could be turned into arbitrary MMIO writes; on stock BONAIRE/HAWAII it is at minimum a GPU-state-corruption / system-hang primitive (denial of service) and a write of controlled bytes to controlled GPU SRAM offsets.
  • Required config or capabilities: BONAIRE/HAWAII GPU with a tampered SMC firmware (root or firmware-path control), OR a kld module that calls ci_write_smc_sram_dword(rdev, 0xFFFFFFFD, ...) directly (root).

Proof of Concept

Build on the DF-2104 malicious firmware, but additionally arrange for the SRAM dword at SMU7_FIRMWARE_HEADER_LOCATION + offsetof(SMU7_Firmware_Header, PmFuseTable) to be 0xFFFFFFFD:

  1. In the malicious SMC firmware image, set ucode_start_addr so that the byte offset SMU7_FIRMWARE_HEADER_LOCATION + offsetof(SMU7_Firmware_Header, PmFuseTable) lands inside the loaded ucode body.
  2. At that offset in the firmware payload, store the little-endian bytes for 0xFFFFFFFD.
  3. After ci_load_smc_ucode runs, ci_populate_pm_base (ci_dpm.c:471) calls ci_read_smc_sram_dword to fetch pm_fuse_table_offset = 0xFFFFFFFD, then calls ci_copy_bytes_to_smc(rdev, 0xFFFFFFFD, &pi->smc_powertune_table, sizeof(SMU7_Discrete_PmFuses), pi->sram_end).
  4. At ci_smc.c:59: (0xFFFFFFFD + sizeof(SMU7_Discrete_PmFuses)) > 0x40000 evaluates with u32 wraparound to a small value, the check passes, and the loop writes attacker-controlled powertune-table bytes to SMC SRAM offsets starting near 0xFFFFFFFD (auto-increment wrapping).

If the runner cannot easily arrange specific SRAM contents post firmware-load, a simpler demonstration: invoke ci_write_smc_sram_dword(rdev, 0xFFFFFFFD, 0x41414141, 0x40000) via a small kld module that calls the exported symbol β€” the check at ci_smc.c:38 fails to reject and the MMIO write at ci_smc.c:41 fires.

Impact

Combined with DF-2104 (which lets an attacker shape SRAM post-load), this yields a write of controlled bytes to controlled offsets in the GPU indirect SRAM window, bypassing the limit ceiling. State corruption and system hang are immediate; further escalation depends on whether the SMC indirect window aliases host MMIO on the specific platform.

Rewrite the comparisons so they cannot wrap. Standard idiom is to widen to 64-bit for the comparison:

--- a/sys/dev/drm/radeon/ci_smc.c
+++ b/sys/dev/drm/radeon/ci_smc.c
@@ -33,11 +33,16 @@ static int ci_set_smc_sram_address(struct radeon_device *rdev,
                   u32 smc_address, u32 limit)
 {
+   uint64_t end;
    if (smc_address & 3)
        return -EINVAL;
-   if ((smc_address + 3) > limit)
+   if (limit < 4)
+       return -EINVAL;
+   end = (uint64_t)smc_address + 3;
+   if (end > (uint64_t)limit)
        return -EINVAL;

@@ -57,8 +62,12 @@ int ci_copy_bytes_to_smc(struct radeon_device *rdev,
             u32 byte_count, u32 limit)
 {
+   uint64_t end;
    if (smc_start_address & 3)
        return -EINVAL;
-   if ((smc_start_address + byte_count) > limit)
+   end = (uint64_t)smc_start_address + (uint64_t)byte_count;
+   if (end > (uint64_t)limit)
        return -EINVAL;

This is a pure hardening of the bounds-check arithmetic and has no effect on legitimate callers, whose offsets and sizes are always far below 0x40000.

References

  • DF-2104 β€” radeon twin of amdgpu DF-2096 (firmware-trust defect that supplies the attacker-controlled SRAM offset).
  • sys/dev/drm/radeon/ci_dpm.c:478,508 β€” firmware-derived pm_fuse_table_offset flow.

Timeline

  • 2026-07-25 Discovered during automated audit.
  • 2026-07-25 Reported to DragonFlyBSD security contact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2105 Β· 4 files
FileTypeDescriptionSize
VERDICT.md file 757 B ↓ raw
build.sh file 161 B view raw
fix.diff file 164 B view raw
run.sh file 80 B view raw
VERDICT.md file
↓ download raw

DF-2105 - Verification Verdict

Status: reproduced (source-confirmed) Impact: none Confidence: certain

Verdict

Source-confirmed: ci_set_smc_sram_address (:38) and ci_copy_bytes_to_smc (:59) use 32-bit unsigned addition that wraps; (smc_address+3)>limit bypassed when smc_address near 0xFFFFFFFD; GPU/HW-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/radeon/ci_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

fixed
baseline reproduced→ patch + rebuild →patched clean

batch build rc=0

batch build rc=0
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

ci_set_smc_sram_address 32-bit wrap; GPU-gated

Verified recommended fix

ci_set_smc_sram_address 32-bit wrap; GPU-gated

Verdict

ci_set_smc_sram_address 32-bit wrap; GPU-gated