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

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_pages is an exact multiple of 2^20 (e.g., 0x100000 = 4 GB), size_in_bytes wraps 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 the video group).
  • 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 tunable drm.radeon.gart_size or 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 calling set_domain to 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_size is 2 GB).
  • Operator-tuned config (radeon_gart_size >= 4 GB, common on dedicated GPU workstations and CI runners): any local video-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.

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:264 where unsigned num_pages should be unsigned long to avoid truncating new_mem->num_pages before it reaches the copy callback;
  • cik_sdma.c:586 which 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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2091 Β· 2 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix 345 B view raw
VERDICT.md verdict source-trace confirmation 538 B ↓ raw
VERDICT.md verdict source-trace confirmation
↓ download 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

fixed
baseline reproduced→ patch + rebuild →patched clean

Changed to u64; batch build rc=0.

Changed to u64; batch build rc=0.
↓ fix.diffcombined 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.