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

DF-4871 family completion: seven more callers discard radeon_sync_resv()/radeon_sync_rings() returns β€” GPU work submitted without its cross-ring dependency on sync failure (silent bo data corruption, racing VM page-table updates)

Summary

Mechanical enumeration of every caller found 7 more unfiled bare-call sites (r600.c/cik.c/evergreen_dma.c/rv770_dma.c/cik_sdma.c copy paths + radeon_vm.c:705/:833 where r is additionally clobbered) - on -ERESTARTSYS/-ENOMEM/ring-alloc failure the copy or PT-update IB is still committed without the dependency. check_static.sh classifies all 11 sites (7 new + 2 known + 2 correct). 7-site check-and-rollback fix (git apply clean). Hardware-gated for runtime.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5131 Β· 7 files
FileTypeDescriptionSize
README.md β€” 3.8 KB ↓ raw
VERDICT.md β€” 3.3 KB ↓ raw
check_static.sh β€” 1.7 KB view raw
check_static.log β€” 1.7 KB view raw
fix.diff β€” 4.6 KB view raw
manifest.json β€” 1.4 KB view raw
verdict.json β€” 4.0 KB view raw

DF-5131 β€” radeon_sync_resv()/radeon_sync_rings() returns discarded by 7 remaining callers (DF-4871 family completion)

File: sys/dev/drm/radeon/radeon_sync.c (pass 2) β€” findings live at the call sites enumerated below; the sync engine (radeon_sync.c:89-201) correctly returns the error, the callers drop it.

What this is

radeon_sync_resv() (radeon_sync.c:89-125) and radeon_sync_rings() (radeon_sync.c:137-201) return int precisely so a submitter can abort when a cross-ring dependency cannot be established (interrupted fence wait -ERESTARTSYS, semaphore SA-bo allocation -ENOMEM, radeon_ring_alloc() fence-wait failure, disabled source ring -EINVAL). Pass 1 already filed two of the offenders:

This pass found seven more call sites that discard the return, none covered by an existing finding (checked against findings for cik_sdma.c, r600.c, rv770_dma.c, evergreen_dma.c, cik.c, radeon_vm.c):

| # | Site | Function | What is dropped | |---|------|---------------------------| | 1 | sys/dev/drm/radeon/r600.c:2997-2998 | r600_copy_blit | resv + rings | | 2 | sys/dev/drm/radeon/cik.c:3695-3696 | cik_copy_blit | resv + rings | | 3 | sys/dev/drm/radeon/evergreen_dma.c:130-131 | evergreen_copy_dma | resv + rings | | 4 | sys/dev/drm/radeon/rv770_dma.c:66-67 | rv770_copy_dma | resv + rings | | 5 | sys/dev/drm/radeon/cik_sdma.c:601-602 | cik_copy_dma | resv + rings | | 6 | sys/dev/drm/radeon/radeon_vm.c:705 | radeon_vm_update_page_tables | resv (PD fence) | | 7 | sys/dev/drm/radeon/radeon_vm.c:833 | radeon_vm_update_ptes | resv β€” and r is immediately clobbered by the following reservation_object_reserve_shared() call, so the failure could never be observed even by accident |

All five copy sites hold rdev->ring_lock at the point of the call (so the sync engine's cross-ring radeon_ring_alloc() is serialized β€” not a locking bug), and all proceed to emit the copy/PT-update commands and commit the ring after the unchecked sync failure.

Impact

On any sync failure the DMA copy / blit / page-table update is submitted without the cross-ring dependency it exists to enforce:

  • copy paths: silent stale-data bo corruption (the GPU reads the destination before the signaling ring finished writing it) β€” integrity impact;
  • VM paths (6,7): page-table updates racing the fences on the PD/PT resv β€” GPU-visible memory-consistency violation, i.e. the GPU can walk page tables mid-update;
  • the -ENOMEM / disabled-ring cases then also leak nothing (sync_free is still called on the success tail), but the ordering guarantee is silently gone.

Severity Low (family precedent DF-4871/DF-5089): hardware-gated to radeon GPUs, needs a sync failure to coincide with a cross-ring dependency β€” but the code path is certain, static, and trivially fixed.

Reproduce (static)

./check_static.sh

enumerates every radeon_sync_resv/radeon_sync_rings call in sys/dev/drm/radeon/ and classifies checked vs bare β€” expect exactly the 7 sites above reported BARE (plus the 2 known DF-4871/DF-5089 sites), and radeon_ib.c:149 / radeon_cs.c:256 reported CHECKED.

Runtime reproduction requires a physical radeon GPU with β‰₯2 initialized rings plus an induced radeon_sync_rings() failure (e.g. signal-pending dma_fence_wait); the QEMU audit guest has no AMD GPU (pciconf shows none), so no runtime PoC is possible on it β€” see VERDICT.md.

Fix

fix.diff β€” check both returns at all 7 sites with the canonical rollback (radeon_ring_unlock_undo() + radeon_sync_free() + ERR_PTR(r) for the copy paths; propagate r for the two VM sites, un-clobbering the variable at radeon_vm.c:833).

VERDICT.md
↓ download raw

DF-5131 β€” VERDICT

status: untested (statically certain / runtime not testable on audit guest) reproduced: 0 Β· impact: none (demonstrable only as silent data-ordering loss on real radeon HW)

What was verified, and how

This is a pure static-contract defect: seven call sites invoke radeon_sync_resv() / radeon_sync_rings() as bare statements and proceed to emit + commit GPU work regardless of the returned error. The engine side (sys/dev/drm/radeon/radeon_sync.c:89-125, 137-201) is correct:

  • radeon_sync_resv returns the dma_fence_wait() result on both the exclusive (radeon_sync.c:106) and shared (radeon_sync.c:119-122) paths;
  • radeon_sync_rings returns -EINVAL (disabled source ring, radeon_sync.c:153-156), the radeon_fence_wait() failure (radeon_sync.c:160-163, 180-183, 190-193), radeon_semaphore_create() failure (radeon_sync.c:165-167) and radeon_ring_alloc() failure (radeon_sync.c:172-174).

The callers drop it. check_static.sh (this pack, run against the read-only sys/ tree, output in check_static.log) mechanically confirms:

  • BARE (defective): r600.c:2997, cik.c:3695, evergreen_dma.c:130, rv770_dma.c:66, cik_sdma.c:601, radeon_vm.c:705, radeon_vm.c:833 (+ the already-filed r600_dma.c:466 / si_dma.c:255 = DF-4871/DF-5089);
  • CHECKED (correct): radeon_ib.c:149 (r = radeon_sync_rings(...) with rollback), radeon_cs.c:256 (r = radeon_sync_resv(...) propagated).

Why no runtime PoC

  1. The QEMU audit guest (DragonFly 6.5-DEVELOPMENT x86_64, uname in verdict.json) has no AMD/ATI PCI device (pciconf -l | grep -ic 'ati|amd|1002' β†’ 0), so neither radeon nor amdgpu can attach; the code cannot execute.
  2. Even on hardware, the observable is silent loss of GPU execution ordering (a stale-data race), not a crash: triggering needs a sync failure (-ERESTARTSYS on a signal-pending fence wait, -ENOMEM in radeon_semaphore_create, or a radeon_ring_alloc() fence-drain failure) to coincide with a cross-ring dependency on a bo the process can observe. That is a real but probabilistic corruption window β€” exactly why DF-4871 and DF-5089 were also filed Low on static certainty.

Per the pass-2 contract this is a Low finding outside the mandatory-verify buckets (not hammer2/memcorrupt/privesc, not Critical/High, not a trivially runnable local program) β†’ verification honestly recorded as untested, with the machine-checkable static proof attached instead of a fabricated runtime claim.

Exploit chain

(none β€” GPU-internal ordering loss; worst case silent bo data corruption or a racing page-table update on radeon hardware)

Fix validation

Not applicable (no runtime baseline exists without hardware). fix.diff was authored after the static verification above: identical rollback pattern to the one radeon_ib_schedule() already uses for the same call (sys/dev/drm/radeon/radeon_ib.c:149-155). It applies cleanly to the unmodified tree (git apply --check verified) but was intentionally not applied to sys/.

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 validation not applicable: no runtime baseline can exist on the QEMU audit guest (no AMD GPU) and the defect's observable is a probabilistic hardware ordering race; fix.diff was verified to apply cleanly (git apply --check) to the unmodified tree and mirrors the already-correct radeon_ib.c:149-155 pattern

git apply --check findings/poc/DF-5131/fix.diff => clean; diff hunks at cik.c @@ -3692,8 +3692,19 @@, radeon_vm.c @@ -702,7 +702,11 @@ and @@ -830,7 +834,9 @@
↓ fix.diffper-fix-DF-5131

Confirmed kernel references

Detail

Evidence (decisive lines)

check_static.log lines 'BARE cik.c:3695' through 'BARE radeon_vm.c:833' (7 unfiled sites), 'CHECKED radeon_ib.c:149' / 'CHECKED radeon_cs.c:256' (correct pattern), and fix.diff hunks @@ -3692,8 +3692,19 @@ cik.c / @@ -702,7 +702,11 @@ radeon_vm.c showing the canonical rollback fix; VERDICT.md for the full trace incl. the radeon_vm.c:833 r-clobber detail

PoC changes

no seed PoC existed (filed during pass 2 with untested verdict); authored check_static.sh as the machine-checkable proof since no runtime trigger is possible without radeon hardware

Verified recommended fix

Check both sync returns at all 7 sites with the radeon_ib_schedule-style rollback (radeon_ring_unlock_undo + radeon_sync_free + ERR_PTR for copy paths; propagate r at the two radeon_vm.c sites, un-clobbering r at :833) - see fix.diff

Verdict

Static contract defect, mechanically certain: seven call sites (r600.c:2997-2998, cik.c:3695-3696, evergreen_dma.c:130-131, rv770_dma.c:66-67, cik_sdma.c:601-602, radeon_vm.c:705, radeon_vm.c:833) invoke radeon_sync_resv()/radeon_sync_rings() as bare statements and submit + commit GPU work regardless of failure, silently dropping the cross-ring ordering guarantee (silent bo data corruption / racing page-table updates on radeon hardware). Runtime reproduction is impossible on the audit guest (no AMD GPU: pciconf shows zero 1002: devices) and on hardware the observable is a probabilistic stale-data race, matching the Low/hardware-gated family precedent (DF-4871, DF-5089). Verification therefore honestly recorded as untested; the machine-checkable static proof (check_static.sh -> check_static.log: exactly the 7 predicted unfiled bare sites, both known sites, and both correct sites) stands in for a runtime PoC.