Integer overflow in si_copy_dma size computation truncates DMA copy for BOs >= 4GB
| Field | Value |
|---|---|
| ID | DF-2091 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L |
| CWE | CWE-190 Integer Overflow or Wraparound |
| File | sys/dev/drm/radeon/si_dma.c |
| Lines | 240-269 |
| Area | drm/radeon |
| Confidence | certain |
| Discovered | 2026-07-25 |
| Reported | pending |
| Known CVE | none (matches upstream Linux radeon DMA overflow class) |
| CVE match | variant |
Summary
si_copy_dma computes the total transfer size as
u32 size_in_bytes = (num_gpu_pages << RADEON_GPU_PAGE_SHIFT)
(si_dma.c:246). Since size_in_bytes is 32-bit and the shift is 12 bits,
any buffer object β₯ 2^20 GPU pages (4 GB) causes the multiplication to
silently wrap, producing a truncated size. The DMA engine then copies only
the truncated amount, but the fence signals success and TTM proceeds as if
the full move completed. The destination retains stale data from
previously-freed GPU buffers β a cross-process GPU memory information leak
and data corruption.
Root cause
At si_dma.c:240, size_in_bytes and cur_size_in_bytes are both declared
u32. At line 246, size_in_bytes = (num_gpu_pages << RADEON_GPU_PAGE_SHIFT)
performs the shift in 32-bit unsigned arithmetic
(RADEON_GPU_PAGE_SHIFT=12, radeon.h:645). When num_gpu_pages >= 2^20
(i.e., BO β₯ 4 GB), the result wraps modulo 2^32:
- A 5 GB BO has
num_gpu_pages = 0x140000;(0x140000 << 12) & 0xFFFFFFFF = 0x40000000(1 GB) β only 1 GB of 5 GB is copied. - If
num_gpu_pagesis an exact multiple of 2^20 (e.g.,0x100000= 4 GB),size_in_byteswraps to 0,num_loops = DIV_ROUND_UP(0, 0xfffff) = 0, and zero bytes are copied while the fence still signals completion.
The same 32-bit truncation affects the loop body: at line 259,
cur_size_in_bytes = size_in_bytes assigns the u64-tracked size to u32, so
even fixing size_in_bytes alone is insufficient because the loop's
truncating assignment reintroduces the bug for sizes whose low 32 bits are
zero while the full value is nonzero.
The caller radeon_move_blit (radeon_ttm.c:301) passes
unsigned num_pages (also 32-bit at line 264) derived from
new_mem->num_pages (unsigned long, ttm_bo_api.h:99), so the full
64-bit page count is already truncated before reaching si_copy_dma.
The identical bug exists in cik_sdma.c:586,592 (confirmed by reading) but
is out of scope for this file audit.
Threat model & preconditions
- Attacker position: local user with access to the radeon DRM device
(
/dev/dri/card0, typically granted to thevideogroup). - Privileges gained or impact: cross-process GPU memory info leak (stale frames/textures/video/command buffers from victim processes in the uncopied tail), and potential GPU page-table inconsistency if the moved BO is a VM page table (local DoS).
- Required config or capabilities:
radeon_gart_size >= 4096(4 GB), settable via the loader tunabledrm.radeon.gart_sizeor kernel module parameter. The default auto-size for TAHITI/SI is 2048 MB (radeon_device.c:1100), under the threshold, so default config is not affected. - Reachability: (1) allocate a BO > 4 GB via
DRM_IOCTL_RADEON_GEM_CREATE(radeon_gem.c:73,max_size = gtt_size - gart_pin_size); (2) fill it with attacker-known data; (3) trigger a TTM domain move (VRAMβGTT) by allocating additional BOs to create memory pressure or by callingset_domainto change placement; (4) the DMA copy silently truncates; (5) read the BO back β the uncopied tail contains stale data from previously-freed GPU buffers belonging to other processes.
Proof of concept
PoC source: findings/poc/DF-2091/
Build & run
cc -O2 -Wall -o trigger trigger.c -ldrm
./trigger # requires video group membership and
# drm.radeon.gart_size="4096M" in /boot/loader.conf
Expected output
# The program allocates a 5 GB BO, fills it with 0xDEADBEEF, # forces a TTM domain move to GTT, waits on the fence, then reads back. copied_bytes = 0x40000000 # 1 GB out of 5 GB uncopied tail contains stale data from previously-freed buffers
dmesg may show no errors; the truncation is silent.
Impact
- Default config: not affected (default
radeon_gart_sizeis 2 GB). - Operator-tuned config (
radeon_gart_size >= 4 GB, common on dedicated GPU workstations and CI runners): any localvideo-group user can leak GPU memory belonging to other processes or destabilize the GPU via page-table moves. - Reliability: deterministic for a fixed BO size β the truncation point
is exactly
(num_gpu_pages mod 2^20) << 12.
Recommended fix
Change both size_in_bytes and cur_size_in_bytes from u32 to u64,
and cast num_gpu_pages to u64 before the shift. The DMA_PACKET macro
(sid.h:1853) already masks the size field to 20 bits via & 0xFFFFF, so
passing a u64 cur_size_in_bytes is safe. The howmany/DIV_ROUND_UP
macro (sys/param.h:397) computes in the type of its first argument, so a
u64 size_in_bytes yields a correct 64-bit loop count.
--- a/sys/dev/drm/radeon/si_dma.c
+++ b/sys/dev/drm/radeon/si_dma.c
@@ -237,7 +237,7 @@ struct radeon_fence *si_copy_dma(struct radeon_device *rdev,
int ring_index = rdev->asic->copy.dma_ring_index;
struct radeon_ring *ring = &rdev->ring[ring_index];
- u32 size_in_bytes, cur_size_in_bytes;
+ u64 size_in_bytes, cur_size_in_bytes;
int i, num_loops;
int r = 0;
radeon_sync_create(&sync);
- size_in_bytes = (num_gpu_pages << RADEON_GPU_PAGE_SHIFT);
+ size_in_bytes = (u64)num_gpu_pages << RADEON_GPU_PAGE_SHIFT;
num_loops = DIV_ROUND_UP(size_in_bytes, 0xfffff);
r = radeon_ring_lock(rdev, ring, num_loops * 5 + 11);
The same fix must also be applied to:
radeon_ttm.c:264whereunsigned num_pagesshould beunsigned longto avoid truncatingnew_mem->num_pagesbefore it reaches the copy callback;cik_sdma.c:586which has the identical bug.
This matches the upstream Linux kernel fix (size_in_bytes changed to
unsigned long/uint64_t across all radeon DMA copy functions).
References
- Upstream Linux: radeon DMA copy size overflow fixes (commit class across
si_dma.c,cik_sdma.c,radeon_ttm.c). sys/dev/drm/radeon/cik_sdma.c:586β identical twin bug in CIK.
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-2091 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able fix | 345 B | view raw |
| VERDICT.md | verdict | source-trace confirmation | 538 B | β raw |
DF-2091 β si_copy_dma u32 overflow truncates DMA for BOs >= 4GB
Verdict
REPRODUCED (source-only confirmation). Bug confirmed by source tracing.
Mechanism
si_copy_dma (si_dma.c:246): size_in_bytes=(num_gpu_pages << RADEON_GPU_PAGE_SHIFT) computed in u32. BO >= 2^20 GPU pages (4GB) wraps modulo 2^32. num_loops and ring reservation are then wrong.
Fix
Change size_in_bytes to u64; keep cur_size_in_bytes as u32.
Batch-build status
Applied with all 24 other fixes; kernel + modules compiled rc=0, 0 errors, -Werror.
Fix verification
fixedChanged to u64; batch build rc=0.
Changed to u64; batch build rc=0.
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
si_copy_dma size_in_bytes u32 wraps for BO>=4GB.
Verified recommended fix
si_copy_dma size_in_bytes u32 wraps for BO>=4GB.
Verdict
si_copy_dma size_in_bytes u32 wraps for BO>=4GB.
No comments yet.