# DF-2214 — Verification Verdict

**Status:** reproduced (verbatim-code KLD rig on the QEMU guest)
**Impact:** GPU-side memory integrity (silent truncation of large DMA copies);
no host kernel memory corruption; no privilege-escalation primitive
**Confidence:** certain

## Verdict

REPRODUCED in kernel context by compiling the UNMODIFIED production function
`rv770_copy_dma` (sys/dev/drm/radeon/rv770_dma.c lines 42-94, sha256
c1b2d82ab166f9e04f7158a8d6fe0506d22ee1c6f05aca121ca0ac9b5a6b547c, extracted
from the guest tree file which is byte-identical to the audited tree,
b33101a3...) into a KLD that drives it at the exact boundary state with
fabricated-but-real-constant structures (radeon_ring with real ptr_mask
semantics per radeon.h:2708-2717 and real ring geometry 64 KiB per
rv770.c:1968, R600_RING_TYPE_DMA_INDEX=3 per radeon.h:149,
RADEON_GPU_PAGE_SHIFT=12 per radeon.h:645, DMA_PACKET macro per
rv770d.h:663-669, ring reservation semantics per radeon_ring.c:107-127).

Line 57 computes `size_in_dw = (num_gpu_pages << RADEON_GPU_PAGE_SHIFT) / 4`
in 32-bit unsigned arithmetic (u32 size_in_dw, unsigned num_gpu_pages). For
num_gpu_pages >= 2^20 the shift result exceeds 32 bits and wraps:

- `0x000FFFFF` (2^20-1): size_in_dw=0x3FFFFC00, 16385 DMA_PACKET_COPY packets
  chunked at 0xFFFF, 4294963200 bytes scheduled = 100.0000% — correct,
  fence seq=1. FULL_COPY.
- `0x00100000` (2^20, a 4 GiB move): **size_in_dw wraps to 0, num_loops=0,
  zero DMA packets, zero bytes scheduled — and the fence is still emitted
  and the function still returns a success fence** (run_extract.log:
  "scheduled_bytes=0 (0.0000% of request) fence_emitted=1 ... VERDICT:
  SILENT_TRUNCATION").
- `0x00100001`: wraps to 4096 bytes — exactly one packet with n=1024 dwords
  (first_pkt=0x30000400) out of a 4 GiB+4K request.
- `0x00100800`: wraps to 8 MiB (0.1949% of request).
- `0xC0000000` (12 TiB request): wraps to 0 — silent total loss.

On the REAL ring geometry (64 KiB, rv770.c:1968) the contrast is sharpest:
a 4 GiB-4K copy (correct arithmetic) needs 81933 dwords, `radeon_ring_lock`
refuses with -ENOMEM and the production DRM_ERROR "radeon: moving bo (-12)"
fires — a loud failure the caller can react to. The wrapped 4 GiB copy asks
for only 8 dwords, locks trivially, emits nothing, and fences success:
`ttm_bo_move_accel_cleanup` then publishes a destination buffer that was
never written. Silent integrity corruption of GPU memory, undetectable by
the caller. Deterministic: three identical runs (run_extract.log,
run2_extract.log, run3_extract.log).

## Negative claims checked (host memory / privesc)

- **No host kernel memory corruption**: the wrap can only SHRINK size_in_dw
  (wrapped = true_size mod 2^32 <= true_size), hence num_loops and the ring
  reservation `num_loops*5+8` only ever shrink; ring writes are bounded by
  the reservation and by `wptr &= ptr_mask` inside the kmalloc'd ring buffer.
  Verified empirically: 4096-byte canary zones bracketing the ring buffer
  were INTACT after all 16 runs, count_dw never went negative (final=8,
  underruns=0). This is a GPU-data integrity bug, not a host memory-safety
  bug — there is no overwrite primitive and no route to uid=0 (the directive
  chain was assessed and closed: not a host memcorrupt/privesc class).
- **Not remotely reachable without local GPU access**: the trigger requires a
  >= 4 GiB buffer-object move through the radeon TTM layer (radeon_ttm.c
  radeon_move_blit -> radeon_copy callback), i.e. an unprivileged local GPU
  client performing huge allocations/moves on rv770-class (r7xx/evergreen DMA)
  hardware with sufficient GTT. GPU-gated, Medium severity as filed.

## Fix Status

**fixed** — A/B-validated in the same KLD. `fix.diff` (generated mechanically
with diff -u, applies cleanly with patch -p1) widens the computation to 64
bit, matching the shape of the verified evergreen fix
(findings/poc/batch_build/all_fixes.patch hunk at evergreen_dma.c):

- `u64 size_in_dw; u32 cur_size_in_dw;`
- `size_in_dw = ((u64)num_gpu_pages << RADEON_GPU_PAGE_SHIFT) / 4;`

FIXED variant results: every sub-2^20 case bit-identical to baseline (e.g.
0xFFFFF: same 16385 packets, 100.0000%, same fence seq) — zero regression;
every >= 2^20 case schedules exactly 100.0000% of the request
(0x100000: sum_chunk_dw=1073741824 = the full 4 GiB) on the oversized test
ring, and on the real 64 KiB ring it now fails LOUDLY with ERR_PTR(-12) —
exactly like a genuine 4 GiB-4K copy — instead of silently fencing an empty
command stream. Residual nit (unchanged from evergreen fix): for absurd
inputs (num_gpu_pages >= 2^22) `cur_size_in_dw = size_in_dw` narrows before
the 0xFFFF cap on iterations after the first; harmless in practice because
ring_lock refuses such reservations long before (observed: attempted_ndw=
251662093 -> lock_rc=-12), but a follow-up could cap before assigning.

## Source File

`sys/dev/drm/radeon/rv770_dma.c` (function lines 42-94; defect at line 57,
propagated through lines 58/69-81; success fence at line 83-93).

## Iterations (poc_changes)

1. First kldload panicked the guest BEFORE the module executed: DFly's
   link_elf_obj loader cannot base .eh_frame relocations emitted without the
   kernel flag set ("panic: lost base for relatab", see
   kldload_panic_iteration.txt). Fixed by compiling with
   sys/platform/pc64/conf/kern.mk flags (-fno-asynchronous-unwind-tables
   -mcmodel=kernel -mno-red-zone -msoft-float ...). Guest reset with-src;
   not related to the finding (module had not run).
2. Kernel include environment replicated from sys/conf/kmod.mk semantics
   (merged machine/ namespace, machine_base/cpu_base/dragonfly symlinks,
   -D_KERNEL, -I/usr/src/include for <string.h>-class forwarders; then
   dropped <string.h> because systm.h fortified memset collides).
3. Trigger values exactly as the finding specifies (2^20-1, 2^20, 2^20+1,
   plus 2^20+2048, 0xC0000000, and real-ring 128 MiB control).

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