# DF-4029 — r600 `SET_RESOURCE` VALID_BUFFER relocates with 8-dword stride into 7-dword resource slots

**File:** `sys/dev/drm/radeon/r600_cs.c`
**Severity:** Medium (validated/executed desync → cross-process GPU OOB vertex fetch; hardware-gated)

## Root cause (path:line)

In `r600_packet3_check()`, `PACKET3_SET_RESOURCE` is validated as **7-dword resource slots**
(`r600_cs.c:1941-1944`: `pkt->count % 7` must be 0; all *reads* use `idx+1+(i*7)+n`,
e.g. r600_cs.c:1957, 1983-1984, 2000-2001). The `SQ_TEX_VTX_VALID_BUFFER` case then
*writes* the relocated base address using an **8-dword stride** — copy-paste from
evergreen (where resources are 8 dwords):

```c
2009:			offset64 = reloc->gpu_offset + offset;
2010:			ib[idx+1+(i*8)+0] = offset64;
2011:			ib[idx+1+(i*8)+2] = (ib[idx+1+(i*8)+2] & 0xffffff00) |
2012:					    (upper_32_bits(offset64) & 0xff);
```

### What the writes clobber (deterministic)

* For `i >= 1` the write at `idx+1+8i` lands **past slot i's own words** — inside later
  slots of the same packet (words the loop has *already validated* or is *about to read*).
  For `i = 5` (6-slot packet, `k=6`) the second write at `idx+1+8*5+2` lands 2 dwords past
  the last resource dword, in the **trailing reloc-NOP packets**, and for smaller `i` in
  already-consumed NOP words (`resource_index_sim` output in run.log tabulates this).
* Because each `VALID_BUFFER` resource consumes one trailing 2-dword reloc NOP, the IB
  length is always ≥ `idx+9k+1` while the max write index is `idx+8k-5` — so the writes
  never leave the IB. **Honest scoping:** this is *not* a kernel-heap OOB write (my initial
  hypothesis, killed by the same sim); it is an in-IB validated/executed desync.

### Why the desync breaks the security boundary

The validator enforces, per slot, `size_i + offset_i ≤ radeon_bo_size(reloc_i->robj)`
(r600_cs.c:2002). But after the smearing, the **executed** state of slot j is a mix: word0
(base low) / word2 (base-high merge) of slot j get overwritten with `offset64` of an
*earlier* slot i (`gpu_offset_i + offset_i`, an absolute address into *BO_i*), while the
size word keeps the attacker's `size_j` that was only validated against *BO_j*. Pairing
`(base→BO_i, size→bound of BO_j)` with a 4 KiB BO_i and a 64 MiB BO_j yields a GPU vertex
fetch window of up to ~64 MiB past a 4 KiB buffer — cross-process GPU memory disclosure
(fetched vertex data can be rendered back to a legitimate BO) and, via transform-feedback /
streamout of fetched data, corruption primitives. With `k=6` the final write also lands on
a **reloc-NOP header** the GPU parses as a packet header — a second, partially-controlled
desync point (low 12 bits fully attacker-chosen since GPU offsets are page-aligned).

## Upstream status

Current Linux master has the same `i*8` writes against `i*7` reads in
`drivers/gpu/drm/radeon/r600_cs.c` (upstream lines 2015-2016 vs 2005-2006). Unfixed
upstream.

## Verification (this pack)

* `resource_index_sim.c` — tabulates read vs write indices per slot, proves the writes stay
  inside the IB (killing the past-IB hypothesis) and demonstrates the smearing targets
  (run.log, executed on the guest).
* `r600_cs_poc.c` mode `resource` — 6-slot VALID_BUFFER SET_RESOURCE submission with a
  tiny (4 KiB) BO for slots 0-4 and a 64 MiB BO for slot 5, relocs interleaved as the
  parser requires. Builds clean on the guest.
* Runtime hardware-gated (no radeon GPU on the audit guest — env.txt).

## Suggested fix

`fix.diff`: use `(i*7)` in both write lines (one-line fix, matches the read stride).

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