# DF-4031 — r600 CS tracked offsets wrap in u32 while the GPU addresses 40 bits (streamout `*4` and base `<<8` truncation)

**File:** `sys/dev/drm/radeon/r600_cs.c`
**Severity:** High (kernel validates the *wrong* GPU offset → cross-process GPU OOB read/write; hardware-gated)
**Cross-ref:** DF-3838 (same arithmetic family on `evergreen_cs.c`).

## Root cause (path:line)

r600/rv770 GPU addresses are 40-bit; the relevant BASE registers hold `addr >> 8` and the
SIZE registers hold DWORD counts. The kernel mirror in `struct r600_cs_track` does the
conversion in **32-bit arithmetic**, so the tracked offset silently truncates:

| site | code (u32) | effect |
|---|---|---|
| r600_cs.c:1084 | `vgt_strmout_bo_offset[tmp] = radeon_get_ib_value(p, idx) << 8;` (u32 field) | raw ≥ 2^24 wraps |
| r600_cs.c:1096 | `vgt_strmout_size[tmp] = radeon_get_ib_value(p, idx) * 4;` (u32 field) | raw ≥ 2^30 wraps to ~0 |
| r600_cs.c:1281 | `track->cb_color_bo_offset[tmp] = radeon_get_ib_value(p, idx) << 8;` (u32 shift, stored to u64) | raw ≥ 2^24 wraps |
| r600_cs.c:1295 | `track->db_offset = radeon_get_ib_value(p, idx) << 8;` (u32 field) | same |
| r600_cs.c:1308 | `track->htile_offset = radeon_get_ib_value(p, idx) << 8;` (u32 shift into u64 field) | same |

Every downstream bounds check then validates the wrong offset:

* Streamout (`r600_cs.c:717-736`): `offset = (u64)bo_offset + (u64)size ≤ bo_size`.
  With `VGT_STRMOUT_BUFFER_SIZE_0 = 0x40000000` the tracked size is `0x40000000*4 mod 2^32
  = 0` → the check passes for a 4 KiB BO, while the GPU executes the register verbatim
  with a **4 GiB** streamout bound (`strmout_wrap_sim` output in run.log). Geometry-driven
  streamout then writes past the BO across GPU memory.
* CB/DB/HTile (`r600_cs_track_validate_cb:380,443` / `validate_db:557,567,623,692`): with
  `CB_COLOR0_BASE` raw `0x01000000` the GPU's base is `gpu_offset + 4 GiB` while the kernel
  validates `gpu_offset + 0` (sim output: kernel-tracked 0x0000000000 vs GPU
  0x0100000000). Render/depth/htile writes land 4 GiB away from the validated window —
  attacker-groomable to land on any other process's BO.

All inputs are plain register values from the unprivileged CS ioctl; no special setup
beyond the packets in `r600_cs_poc.c` mode `strmout` (which drives the streamout variant
end-to-end through `r600_cs_track_check`).

## Upstream status

Current Linux master has the identical u32 arithmetic (upstream `r600_cs.c` lines 724,
1098, and the `<<8` sites). Unfixed upstream; DF-3838 documented the same family for
evergreen.

## Verification (this pack)

* `strmout_wrap_sim.c` — demonstrates both wraps numerically (run.log, executed on the
  guest): tracked size 0 vs GPU bound 4294967296 bytes; tracked offset 0 vs GPU offset
  0x0100000000.
* `r600_cs_poc.c` mode `strmout` — full CS submission (STRMOUT_EN/BUFFER_EN/SIZE/BASE +
  reloc + draw) that the validator accepts; builds clean on the guest.
* Runtime hardware-gated (no radeon GPU on the audit guest — env.txt).

## Suggested fix

`fix.diff`: widen `vgt_strmout_bo_offset`, `vgt_strmout_size`, `db_offset` to u64 and do
every `<<8` / `*4` conversion in 64-bit.

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