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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4871 Β· 7 files
FileTypeDescriptionSize
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 of radeon_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 reserves num_loops * 4 + 16 β€” this tree both ignores the errors and keeps the smaller + 8 reservation (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)

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

VERDICT.md
↓ download raw

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)

  1. 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:302 radeon_copy(rdev, ..., bo->resv) β†’ asic copy.copy = r600_copy_dma (registered e.g. radeon_asic.c for R600..NI DMA rings). resv is always the embedded &bo->tbo.resv (never NULL on this path β€” the Linux NULL-resv crash class does not apply here).
  2. 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 does dma_fence_wait(); an interrupted wait returns an error and the sync is incomplete β€” ignored here.
  3. 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:271 ring->ready = false) or GPU reset while fences on it are still pending; - :160-163 / :180-183 / :190-193 manual radeon_fence_wait() fallback errors; - :165-174 semaphore create / ring alloc failures. On the -EINVAL path the loop aborts before waiting on the remaining sync_to[] fences.
  4. 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.
  5. Contrast: radeon_ib.c:149-154 β€” the only other caller of radeon_sync_rings() β€” treats failure as fatal (unlock_undo + return). Upstream Linux additionally checks radeon_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_testable
baseline no→ patch + rebuild →patched clean

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

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.