Tracked offsets wrap in u32 while the GPU addresses 40 bits: streamout size raw*4 and base<<8 truncation validate the WRONG offset
Summary
r600/rv770 GPU addresses are 40-bit (BASE regs hold addr>>8, streamout SIZE regs hold DW counts) but the kernel mirror computes in 32-bit: vgt_strmout_size = raw*4 wraps for raw >= 2^30 so VGT_STRMOUT_BUFFER_SIZE_0=0x40000000 validates as 0 bytes while the GPU executes a 4 GiB streamout bound; cb_color/db/htile/strmout offsets all do raw<<8 in u32 so raw >= 2^24 makes every BO-size check validate an offset 4 GiB away from where the GPU actually writes. strmout_wrap_sim proves both wraps numerically (kernel 0x0 vs GPU 0x0100000000). GPU OOB writes into other processes' BOs (r600 analogue of DF-3838). u64-fields fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4031 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 553 B | β raw | |
| VERDICT.md | β | 3.4 KB | β raw | |
| build.log | β | 1.1 KB | view raw | |
| build.sh | β | 65 B | view raw | |
| env.txt | β | 586 B | view raw | |
| fix.diff | β | 2.3 KB | view raw | |
| r600_cs_poc.c | β | 11.6 KB | view raw | |
| run.2.log | β | 881 B | view raw | |
| run.log | β | 751 B | view raw | |
| run.sh | β | 89 B | view raw | |
| strmout_wrap_sim.c | β | 2.1 KB | view raw | |
| verdict.json | β | 3.1 KB | view raw |
DF-4031 evidence pack
r600 CS tracked offsets are computed in u32 while the GPU addresses 40 bits: vgt_strmout_size = raw*4 wraps (r600_cs.c:1096, u32 field) so VGT_STRMOUT_BUFFER_SIZE_0=0x40000000 validates as size 0 (che
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-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. WithVGT_STRMOUT_BUFFER_SIZE_0 = 0x40000000the tracked size is0x40000000*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_simoutput 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): withCB_COLOR0_BASEraw0x01000000the GPU's base isgpu_offset + 4 GiBwhile the kernel validatesgpu_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.cmodestrmoutβ 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.
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: streamout size register 0x40000000 -> kernel validates 0 bytes, GPU writes up to 4 GiB past the BO; or CB_COLOR0_BASE raw 0x01000000 -> color/depth/htile writes land 4 GiB from the validated window (groomable onto victim BOs) -> cross-process GPU memory corruption.
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
u64 fields and (u64) casts for every <<8 / *4 tracked-offset conversion (see fix.diff).
Verdict
r600 CS tracked offsets are computed in u32 while the GPU addresses 40 bits: vgt_strmout_size = raw*4 wraps (r600_cs.c:1096, u32 field) so VGT_STRMOUT_BUFFER_SIZE_0=0x40000000 validates as size 0 (check at :721-728) while the GPU executes a 4 GiB streamout bound; cb_color_bo_offset/db_offset/htile_offset/vgt_strmout_bo_offset do raw<<8 in u32 (:1084,:1281,:1295,:1308) so raw>=2^24 makes every CB/DB/htile BO-size check validate the WRONG offset (kernel 0 vs GPU 4 GiB, sim output in run.log). r600 analogue of the evergreen DF-3838 wraparound family. Hardware-gated: no radeon GPU on the audit guest.
No comments yet.