r600_copy_dma discards radeon_sync_resv()/radeon_sync_rings() failures β copy committed without cross-ring ordering, silent bo data corruption
Summary
Both sync returns are ignored (the sibling ib_schedule checks and unwinds; upstream checks resv here - this tree diverges): on failure the pending cross-ring deps are skipped and the copy is still emitted and committed - the destination silently holds stale/partial data behind a good fence. Unpriv GEM clients reach it via bo migration, but deterministic triggering needs a degraded GPU (disabled ring with pending fences). Missing-sync is w.r.t. the same resv only (no other-user data). Check-and-unwind + move-resv-before-ring-lock fix. Sibling ignored-return sites logged as leads (evergreen_dma/rv770_dma/si_dma/r600.c).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4871 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.3 KB | β raw | |
| VERDICT.md | β | 3.8 KB | β raw | |
| fix.diff | β | 1.1 KB | view raw | |
| env.txt | β | 1.3 KB | view raw | |
| run.log | β | 1.3 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 3.4 KB | view raw |
DF-4871 β r600_copy_dma ignores radeon_sync_resv()/radeon_sync_rings() failures
File: sys/dev/drm/radeon/r600_dma.c (pass 2, GLM 5.3)
Severity: Low Β· CWE-754 (Improper Check for Unusual or Exceptional Conditions)
What
r600_copy_dma() β the ttm/GEM buffer-object migration copy engine used by
unprivileged GEM clients via radeon_bo_move β radeon_move_blit β
radeon_copy(rdev, ..., bo->resv) (radeon_ttm.c:302) β discards both
synchronization return values:
466: radeon_sync_resv(rdev, &sync, resv, false); /* int return ignored */
467: radeon_sync_rings(rdev, &sync, ring->idx); /* int return ignored */
radeon_sync_rings() (radeon_sync.c:137-201) can fail with -EINVAL
("Syncing to a disabled ring!", radeon_sync.c:153-156), fence-wait
interruption (radeon_sync.c:160-163, 180-183, 190-193), or semaphore/alloc
failure (radeon_sync.c:165-174). radeon_sync_resv() can fail on
dma_fence_wait() errors (radeon_sync.c:106, 119). On any of these the
function returns early β possibly after committing semaphore signals on
other rings β and the pending cross-ring dependencies are not waited on;
r600_copy_dma then emits the DMA copy packets and commits anyway.
Consequence: the copy can execute before a pending write to the same buffer object on another ring completes β silent GPU-side data corruption of the migrated bo (stale/partial source contents land in the destination). This is data corruption of the buffer's own contents, not kernel memory corruption.
Contrast with sibling code (why this is a defect, not a style choice)
radeon_ib_schedule()β the other user ofradeon_sync_rings()β checks the return and unwinds: radeon_ib.c:149-154 (failed to sync ringsβradeon_ring_unlock_undo+ return).- Upstream Linux moved
radeon_sync_resv()before the ring lock with full error handling (if (r) goto error_free;) in this exact function and reservesnum_loops * 4 + 16β this tree both ignores the errors and keeps the smaller+ 8reservation (the reservation half of that is the known DF-1943; the ignored-return half is this finding).
Siblings carrying the identical ignored-return pattern (cross-ref)
sys/dev/drm/radeon/evergreen_dma.c:130-131sys/dev/drm/radeon/rv770_dma.c:66-67sys/dev/drm/radeon/si_dma.c:255-256sys/dev/drm/radeon/r600.c:2997-2998(blit copy path)
Trigger / preconditions
Unprivileged GEM client causes bo migrations (VRAMβGTT moves, eviction)
whose resv carries fences on another ring; combined with a GPU state where
radeon_sync_rings() fails (e.g. a ring disabled by a previous failed ib
test/reset: ring->ready = false in radeon_ib.c:271 β then any pending
fence on it makes sync_rings return -EINVAL). Realistically requires a
r6xx+ GPU in a degraded state; deterministic construction is not possible
from userland alone.
Verdict
Not runtime-verified: requires real r6xx-class Radeon hardware; the audit
QEMU guest has none (see env.txt). Code-evident divergence from the
checked sibling path. Status: untested (hardware-gated), confidence
likely.
Fix
See fix.diff β check both returns, unwind ring lock and sync state on
failure; radeon_sync_resv() is additionally moved before
radeon_ring_lock() so its potentially-sleeping fence waits do not happen
under the global rdev->ring_lock mutex (mirrors upstream).
DF-4871 VERDICT β r600_copy_dma ignores sync_resv/sync_rings failures
Bottom line
Not reproduced on the audit guest: triggering requires real r6xx-class Radeon hardware (the DMA copy engine) in a state where cross-ring synchronization fails while a bo migration is in flight β the guest has no Radeon hardware (env.txt). The defect is evident from source and from the divergence with the checked sibling path; impact ceiling is silent GPU-side data corruption of the migrated buffer object's own contents (I:L, no kernel memory corruption).
Why the code path is real (line-by-line trace)
- Unprivileged GEM clients cause bo moves constantly (VRAMβGTT migration,
eviction under memory pressure): radeon_ttm.c
radeon_bo_moveβradeon_move_blitβ radeon_ttm.c:302radeon_copy(rdev, ..., bo->resv)β asiccopy.copy=r600_copy_dma(registered e.g. radeon_asic.c for R600..NI DMA rings).resvis always the embedded&bo->tbo.resv(never NULL on this path β the Linux NULL-resv crash class does not apply here). - r600_dma.c:466
radeon_sync_resv(rdev, &sync, resv, false);β return value discarded. radeon_sync.c:106/119: on a non-radeon fence it doesdma_fence_wait(); an interrupted wait returns an error and the sync is incomplete β ignored here. - r600_dma.c:467
radeon_sync_rings(rdev, &sync, ring->idx);β return value discarded. Failure modes (radeon_sync.c): - :153-156-EINVAL"Syncing to a disabled ring!" β reachable when a source ring was disabled by an earlier failed ib test (radeon_ib.c:271ring->ready = false) or GPU reset while fences on it are still pending; - :160-163 / :180-183 / :190-193 manualradeon_fence_wait()fallback errors; - :165-174 semaphore create / ring alloc failures. On the-EINVALpath the loop aborts before waiting on the remaining sync_to[] fences. - r600_dma.c:469-481, 490: the copy packets are emitted and
radeon_ring_unlock_commit()fires regardless β the DMA copy is not ordered after the pending write on the other ring β the destination bo receives stale/partial source data, with a successfully-signaled fence. Silent, undetectable-from-userspace data corruption of the client's own buffer. - Contrast: radeon_ib.c:149-154 β the only other caller of
radeon_sync_rings()β treats failure as fatal (unlock_undo + return). Upstream Linux additionally checksradeon_sync_resv()in this exact function (if (r) goto error_free;) and moved it before the ring lock.
Why it is not the known DF-1942/DF-1943
DF-1942 = size_in_dw wrap (copy length math); DF-1943 = insufficient ring
dword reservation (num_loops * 4 + 8). This finding is the ignored error
returns of the two sync calls β orthogonal defect, orthogonal fix (which
DF-1943's reservation bump does not repair).
Exploit chain
None: no kernel memory is corrupted; corruption is confined to the bo's own GPU contents. A determined unprivileged user on degraded hardware could corrupt its own buffers' contents (not another user's β the missing sync is only w.r.t. fences on the same resv).
Fix validation
Not possible on this guest (no hardware). fix.diff authored from the trace
above: check both returns; unwind (unlock_undo + sync_free) on failure;
move radeon_sync_resv() before radeon_ring_lock() so its sleeping fence
waits don't run under the global ring mutex β mirroring upstream.
fix_status: not_testable.
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 from line-accurate source reading, mirroring the checked sibling (radeon_ib.c:149-154) and upstream Linux structure; kernel fix-build validation impossible on this guest β no Radeon hardware to drive the copy path
findings/poc/DF-4871/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4871/VERDICT.md (trace: radeon_ttm.c:302 radeon_copy -> r600_dma.c:466-467 -> radeon_sync.c:137-201 failure modes -> unchecked commit at r600_dma.c:490; contrast radeon_ib.c:149-154); env.txt (hardware gate); fix.diff (checks both returns, unwinds, moves sync_resv before the ring lock per upstream)
PoC changes
n/a β no PoC seed existed; no deterministic userland trigger (needs degraded real hardware)
Verified recommended fix
Check the radeon_sync_resv()/radeon_sync_rings() returns in r600_copy_dma() and unwind (unlock_undo + sync_free + ERR_PTR) on failure, moving sync_resv before radeon_ring_lock()
Verdict
r600_copy_dma() discards the return values of radeon_sync_resv() (r600_dma.c:466) and radeon_sync_rings() (r600_dma.c:467). radeon_sync_rings() can fail with -EINVAL when a source ring was disabled (radeon_sync.c:153-156, reachable after a failed ib test sets ring->ready=false per radeon_ib.c:271) or on fence-wait errors; on failure the copy packets are still emitted and committed (r600_dma.c:469-490), so the DMA copy runs unordered w.r.t. a pending write on the same bo β silent GPU-side data corruption of the migrated buffer's own contents. Sibling caller radeon_ib_schedule() checks the identical call and unwinds (radeon_ib.c:149-154), and upstream Linux checks radeon_sync_resv() in this exact function β this tree diverges. Runtime verification is hardware-gated (needs real r6xx+ hardware in a degraded state); the audit guest has only Bochs VGA, no /dev/dri, no radeon.ko (env.txt). Impact ceiling: unprivileged-triggerable bo content corruption on degraded hardware (I:L); no kernel memory corruption.
No comments yet.