SET_RESOURCE VALID_BUFFER relocates with 8-dword stride into 7-dword resource slots (validated/executed desync)
Summary
r600 SET_RESOURCE slots are 7 dwords (count%7 check; all reads use i*7) but the VALID_BUFFER case writes the relocated base at idx+1+(i*8)+0/+2 - an evergreen copy-paste. Writes land in later slots' already-validated words and trailing reloc-NOP words, so the executed (base,size) pairing diverges from the validated pairing: slot j executes with a base pointing into BO_i while its size was validated against a much larger BO_j -> GPU vertex fetch up to ~64 MiB past a 4 KiB BO (cross-process disclosure); with k=6 the final merge-write also lands on a reloc-NOP header the GPU parses as a packet header. resource_index_sim on the guest tabulates the math and proves writes stay inside the IB. 7-dword-stride fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4029 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 555 B | β raw | |
| VERDICT.md | β | 3.8 KB | β raw | |
| build.log | β | 1.1 KB | view raw | |
| build.sh | β | 65 B | view raw | |
| env.txt | β | 586 B | view raw | |
| fix.diff | β | 398 B | view raw | |
| r600_cs_poc.c | β | 11.6 KB | view raw | |
| resource_index_sim.c | β | 2.5 KB | view raw | |
| run.2.log | β | 881 B | view raw | |
| run.log | β | 1.3 KB | view raw | |
| run.sh | β | 89 B | view raw | |
| verdict.json | β | 2.9 KB | view raw |
DF-4029 evidence pack
r600_packet3_check SET_RESOURCE validates 7-dword resource slots (count%7, reads at i7) but writes the relocated VALID_BUFFER base at i8 (r600_cs.c:2010-2012) β an evergreen copy-paste. Writes land
Build
./build.sh (cc -O2 -Wall; verified on the DragonFly audit guest β build.log)
Run
./run.sh (verified on the DragonFly audit guest β run.log; triggers exit 2:
/dev/dri absent β no radeon GPU on the QEMU guest, see env.txt)
Expected on r600/rv770 hardware
See VERDICT.md.
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):
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 >= 1the write atidx+1+8ilands past slot i's own words β inside later slots of the same packet (words the loop has already validated or is about to read). Fori = 5(6-slot packet,k=6) the second write atidx+1+8*5+2lands 2 dwords past the last resource dword, in the trailing reloc-NOP packets, and for smalleriin already-consumed NOP words (resource_index_simoutput in run.log tabulates this). - Because each
VALID_BUFFERresource consumes one trailing 2-dword reloc NOP, the IB length is always β₯idx+9k+1while the max write index isidx+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.cmoderesourceβ 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.
Fix verification
not_testablefix.diff authored against the read-only sys/ tree after source verification; baseline and patched runs require r600/rv770 hardware which the audit guest lacks, so fix validation is not testable here.
fix.diff in this pack
Confirmed kernel references
Detail
Exploit chain
Unpriv CS with 6 VALID_BUFFER resources mixing a 4 KiB and a 64 MiB BO; executed base/size pairing break -> vertex fetcher reads past the 4 KiB BO into neighboring GPU memory -> sampled/rendered back to attacker BO.
Evidence (decisive lines)
VERDICT.md (full path:line trace); run.log (guest trigger run + arithmetic simulation proving the validator math line-exact); build.log (clean guest build); env.txt (guest has no radeon GPU β hardware-gated); fix.diff (proposed fix, NOT applied to sys/)
PoC changes
Trigger written from scratch for this pack (raw ioctl, no libdrm, self-contained struct defs); arithmetic sims written to verify each wrap/desync claim line-exact; past-IB hypothesis for DF-4029 explicitly tested and killed by resource_index_sim.
Verified recommended fix
Use (i*7) in the two write lines (see fix.diff).
Verdict
r600_packet3_check SET_RESOURCE validates 7-dword resource slots (count%7, reads at i7) but writes the relocated VALID_BUFFER base at i8 (r600_cs.c:2010-2012) β an evergreen copy-paste. Writes land in later slots' already-validated words and in trailing reloc-NOP words, so the executed (base,size) pairing diverges from the validated pairing: slot j can execute with base from BO_i while its size was validated against a much larger BO_j -> GPU vertex fetch past small BOs (cross-process disclosure). resource_index_sim (run on the guest, run.log) proves read/write index tables and that mandatory trailing reloc NOPs keep writes inside the IB (past-IB hypothesis explicitly killed). Hardware-gated: no radeon GPU on the audit guest.
No comments yet.