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

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)

No comments yet.

PoC verification

Evidence pack

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

VERDICT.md
↓ download raw

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.

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-4031

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.