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

evergreen_copy_dma discards radeon_sync_resv()/radeon_sync_rings() returns β€” Evergreen/Cayman twin of DF-4871/DF-5089: DMA copy committed without cross-ring ordering on sync failure, silent bo data corruption

Summary

The routed lead twin: both sync returns ignored inside the ring lock (resv waits also serialize every ring) - the copy commits unordered w.r.t. pending writes on degraded-driver states, silently corrupting migrated bo contents under a healthy fence. Check+move-resv-before-lock fix (mirror of the accepted DF-4871 fix), git-apply-verified. Hardware-gated (QEMU std-VGA proven this run).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5170 Β· 7 files
FileTypeDescriptionSize
README.md β€” 1.4 KB ↓ raw
VERDICT.md β€” 7.2 KB ↓ raw
fix.diff β€” 1.1 KB view raw
env.txt β€” 436 B view raw
run.log β€” 439 B view raw
manifest.json β€” 1.5 KB view raw
verdict.json β€” 5.3 KB view raw

DF-5170 β€” evergreen_copy_dma discards radeon_sync_resv()/radeon_sync_rings() returns

The Evergreen/Cayman twin of DF-4871 (r600_dma.c:466-467) and DF-5089 (si_dma.c:255-256): evergreen_copy_dma() (evergreen_dma.c:105-158) takes the DMA ring lock, then calls the sync layer and drops both int return values, then emits and commits the copy packets unconditionally.

Reproduce

There is no userland PoC: the defect is reachable only on real Evergreen/Cayman (AMD HD 5xxx/6xxx) hardware, as the ttm bo-migration copy callback (.copy.copy = &evergreen_copy_dma, radeon_asic.c:1373/1467/1560/ 1708/1828). The verification guest has QEMU std-VGA and no DRM stack β€” see env.txt / run.log.

dfbsd-qemu/vm.sh status                       # guest up
dfbsd-qemu/vm.sh run_root 'pciconf -l | grep -i vga; kldstat | grep -ci drm; ls /dev/dri'

Expected (the hardware gate): vgapci0 ... chip=0x11111234 (QEMU std VGA), 0, ls: /dev/dri: No such file or directory.

Source-level proof

See VERDICT.md for the full line-accurate trace (failure modes of radeon_sync.c, the checked-and-unwind contrast in radeon_ib.c:149-154, and the pass-2 negative-result table for the rest of evergreen_dma.c).

Fix

fix.diff β€” check both returns and unwind (unlock_undo + sync_free + ERR_PTR), moving radeon_sync_resv() before radeon_ring_lock() because its dma_fence_wait(..., true) may sleep. Mirrors the accepted DF-4871 fix and DF-5089's fix.diff shape.

VERDICT.md
↓ download raw

DF-5170 VERDICT β€” evergreen_copy_dma commits the DMA copy even when cross-ring sync fails

Status: untested (hardware-gated; no Evergreen/Cayman GPU on the verification guest). Impact: silent GPU bo-content corruption on degraded EG/Cayman hardware (I:L). Confidence: certain β€” the ignored int returns are unambiguous line-level facts; the failure modes of the sync layer are the same ones traced for DF-4871/DF-5089 and re-verified in this tree below.

1. The defect

evergreen_copy_dma() (sys/dev/drm/radeon/evergreen_dma.c:105-158) β€” the ttm bo-migration copy callback for all Evergreen/Cayman asics (.copy.dma/.copy.copy = &evergreen_copy_dma at radeon_asic.c:1373, :1467, :1560, :1708, :1828; ring index R600_RING_TYPE_DMA_INDEX; reached from radeon_move_bl()/radeon_ttm.c on every VRAM<->GTT move):

121:    size_in_dw = (num_gpu_pages << RADEON_GPU_PAGE_SHIFT) / 4;
122:    num_loops = DIV_ROUND_UP(size_in_dw, 0xfffff);
123:    r = radeon_ring_lock(rdev, ring, num_loops * 5 + 11);
124:    if (r) { ... radeon_sync_free(...); return ERR_PTR(r); }
...
130:    radeon_sync_resv(rdev, &sync, resv, false);
131:    radeon_sync_rings(rdev, &sync, ring->idx);
132:
133:    for (i = 0; i < num_loops; i++) { ...emit copy packets... }
...
147:    r = radeon_fence_emit(rdev, &fence, ring->idx);
154:    radeon_ring_unlock_commit(rdev, ring, false);

Both functions return int (radeon.h:628, radeon.h:632); both returns are discarded at evergreen_dma.c:130-131. On failure the copy packets are still emitted and committed at :154 β€” the exact twin of r600_dma.c:466-467 (DF-4871) and si_dma.c:255-256 (DF-5089). DF-5089's own verdict/notes flag this line pair as the then-unfiled sibling; this filing closes it.

2. How the sync layer fails (verified in this tree)

radeon_sync_resv() (radeon_sync.c:89-125): returns the error from dma_fence_wait(f, true) on any non-radeon exclusive/shared resv fence (:106, :119 β€” interruptible wait; e.g. signal during a long foreign-fence wait under memory pressure). Note this happens while rdev->ring_lock is already held (taken at evergreen_dma.c:123) β€” a sleep under the lock that serializes every ring; the fix moves it before the lock.

radeon_sync_rings() (radeon_sync.c:137-201) β€” documented "The caller must hold the ring lock" (:135): - -EINVAL when a source ring is not ready (:153-156, "Syncing to a disabled ring!") β€” a ring becomes !ready after a failed ib test or GPU reset handling, exactly the degraded state a bo migration can race with; - error from the manual radeon_fence_wait() fallback when RADEON_NUM_SYNCS is exhausted (:158-163); - error from radeon_semaphore_create() (SA allocator, -ENOMEM) (:165-167) or radeon_ring_alloc() on the source ring (:172-174); - error from the manual wait when semaphore emission failed (:177-193).

On any of these, the correct behavior is to unwind (radeon_ring_unlock_undo + radeon_sync_free + ERR_PTR(r)) β€” the pattern the sibling caller radeon_ib_schedule() implements for the very same call (radeon_ib.c:149-154). Instead the copy runs unordered w.r.t. a pending write on the same bo: silent GPU-side corruption of the migrated bo's contents under a healthy fence (the fence emitted at :147 only orders the copy itself). Kernel memory is never corrupted; ceiling is unprivileged- triggerable bo content corruption / potential GPU hang on degraded hardware β€” Low, matching DF-4871/DF-5089.

3. Why runtime verification is not possible here

Guest probe (this run, run.log/env.txt): DragonFly dfbsd 6.5-DEVELOPMENT ... X86_64_GENERIC x86_64; VGA is vgapci0@pci0:0:2:0 ... chip=0x11111234 (QEMU std VGA); kldstat | grep -ci "radeon\|drm" = 0; /dev/dri absent. evergreen_copy_dma is only reachable as the asic copy callback after the radeon driver binds to real Evergreen/Cayman hardware; no deterministic userland trigger exists without it. Hardware-gated Low severity, per contract PoC-skippable; recorded untested (persist enum has no not_testable/skipped). Attempts: 1 (env probe).

4. Pass-2 negative results (rest of evergreen_dma.c, with weight)

  • DF-2167 known, not re-reported: (num_gpu_pages << 12) / 4 at :121 wraps 32-bit for num_gpu_pages >= 2^20 (>=4GB bo) β€” the EG sibling of DF-2091; already filed (Medium/memcorrupt).
  • Packet count field is exact: DMA_PACKET(cmd, sub_cmd, n) places (n) & 0xFFFFF in a 20-bit field (evergreend.h:1416-1418); the cur_size_in_dw > 0xFFFFF clamp at :135-136 matches the field width exactly. No silent truncation.
  • No ring OOB write is possible: radeon_ring_write masks the write index with ring->ptr_mask and only prints DRM_ERROR when count_dw <= 0 (radeon.h:2708-2718). The +11 slack at :123 covers the 7-dword fence packet (evergreen_dma_fence_ring_emit :45-54 writes 7); the semaphore- wait dwords radeon_sync_rings emits into this ring are NOT covered by the reservation (radeon_sync.c:186-187 "we assume caller has already allocated space on waiters ring" β€” ib_schedule reserves RADEON_NUM_SYNCS * 8 for them, radeon_ib.c:135, but evergreen_copy_dma does not) β€” worst case is in-ring command corruption + DRM_ERROR spam on

    =2 concurrent sync waits, bounded by ptr_mask. No kernel memory safety.

  • IB path reservation is ample: evergreen_dma_ring_ib_execute emits <= 14 dwords (4 wb + <= 7 NOP pad + 3 IB, :75-88); radeon_ib_schedule locks 64 + RADEON_NUM_SYNCS * 8 (radeon_ib.c:135). ib->length_dw is bounded by the SA-manager IB pool (radeon_ib.c:201/210, RADEON_IB_POOL_SIZE*64KB) far below 2^20, so (ib->length_dw << 12) at :88 cannot alias other packet fields.
  • 64-bit offsets do not wrap in u32 arithmetic: per-iteration src_offset += cur_size_in_dw * 4 (:143-144) with cur_size_in_dw <= 0xFFFFF keeps the product <= 0x3FFFFC in u32 before promotion; upper_32_bits(...) & 0xff (:141-142) matches the 40-bit GPU VA space.
  • Reservation size is sanity-checked: even with DF-2167's wrapped size_in_dw, num_loops <= ceil(0xFFFFFFFF/0xFFFFF) = 1025, so num_loops * 5 + 11 <= 5136 dwords; radeon_ring_alloc rejects ndw > ring->ring_size / 4 with -ENOMEM (radeon_ring.c:112-113).
  • wb-enabled rptr tracking is self-consistent (:71-78): next_rptr computed from the same alignment invariant the padding loop enforces (:84-85); u32 wptr with ptr_mask wrap.
  • Lockup checker (:169-177) is read-only.
  • No fill/linear/broadcast op exists in this file (that is cik_sdma); the file exports only fence/ib/copy/lockup.

5. Fix validation

fix.diff authored from line-accurate source reading, mirroring the accepted DF-4871 fix and DF-5089's fix.diff: check radeon_sync_resv() before radeon_ring_lock() (its dma_fence_wait may sleep), check radeon_sync_rings() after the lock and unwind on failure. Kernel fix-build validation impossible on this guest β€” no Evergreen/Cayman hardware to drive the DMA migration path; 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 and verified to apply cleanly against sys/dev/drm/radeon/evergreen_dma.c (git apply --check, sys/ untouched); mirrors the accepted DF-4871 fix shape and the checked sibling (radeon_ib.c:149-154). Kernel fix-build validation impossible on this guest β€” no Evergreen/Cayman hardware to drive the DMA migration path.

findings/poc/DF-5170/fix.diff
↓ fix.diffper-fix-DF-5170

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5170/VERDICT.md (trace: radeon_ttm.c radeon_move_bl -> evergreen_dma.c:121-154 -> radeon_sync.c:89-199 failure modes -> unchecked commit at :154; contrast radeon_ib.c:135,149-154; plus the full pass-2 negative-result table for the rest of evergreen_dma.c incl. ptr_mask-bounded ring writes radeon.h:2708-2718 and the exact 20-bit DMA_PACKET count field evergreend.h:1416-1418); env.txt + run.log (hardware gate probe, this run); fix.diff (git-apply-verified against sys/, mirrors accepted DF-4871 / DF-5089 fix shape)

PoC changes

n/a β€” no PoC seed; no deterministic userland trigger without Evergreen/Cayman-class hardware in a degraded state

Verified recommended fix

Check radeon_sync_resv()/radeon_sync_rings() returns in evergreen_copy_dma() and unwind (unlock_undo + sync_free + ERR_PTR) on failure, moving sync_resv before radeon_ring_lock() β€” mirror of the accepted DF-4871 fix

Verdict

evergreen_copy_dma() discards the return values of radeon_sync_resv() (evergreen_dma.c:130) and radeon_sync_rings() (evergreen_dma.c:131) β€” the Evergreen/Cayman twin of DF-4871 (r600_dma.c:466-467) and DF-5089 (si_dma.c:255-256), and the exact site DF-5089's notes flagged as unfiled. radeon_sync_rings() fails with -EINVAL when a source ring was disabled (radeon_sync.c:153-156, reachable after a failed ib test / GPU reset) and on semaphore-create/ring-alloc/interrupted-manual-wait errors (:158-193); radeon_sync_resv() propagates interrupted dma_fence_wait() on foreign resv fences (radeon_sync.c:106,119) β€” currently called while already holding rdev->ring_lock (taken at evergreen_dma.c:123), so that sleep also serializes every ring. On failure the copy packets are still emitted and committed (evergreen_dma.c:133-145, :154), so the DMA copy runs unordered w.r.t. a pending write on the same bo β€” silent GPU-side corruption of the migrated bo's contents under a healthy fence (the fence at :147 only orders the copy itself). The checked-and-unwind pattern exists in the sibling caller radeon_ib_schedule() for the same call (radeon_ib.c:149-154). Runtime verification is hardware-gated (needs real Evergreen/Cayman hardware in a degraded state; guest is QEMU std-VGA chip=0x11111234, 0 drm modules, no /dev/dri β€” env.txt/run.log); hardware-gated Low, per contract PoC-skippable, recorded 'untested' (persist enum has no not_testable/skipped). Impact ceiling: unprivileged-triggerable bo content corruption on degraded hardware (I:L); no kernel memory corruption.