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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4029 Β· 12 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-4029

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.