amdgpu_cs_wait_all_fences reads fence->error after the final dma_fence_put (use-after-free read, value returned as ioctl errno)
Summary
The wait_all loop drops the fence reference and then dereferences fence->error; when the ioctl's reference is the last one (context fence slot recycled after 32 further submissions), this reads freed memory and returns the value as the ioctl's errno. The sibling amdgpu_cs_wait_ioctl has the correct order, proving the local defect. RCU-deferred free narrows but does not eliminate the window. 4-byte heap-content oracle; read-only. Reorder-put fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4265 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.6 KB | β raw | |
| VERDICT.md | β | 3.2 KB | β raw | |
| wait_all_fences_uaf.c | β | 5.1 KB | view raw | |
| build.sh | β | 65 B | view raw | |
| run.sh | β | 89 B | view raw | |
| build.log | β | 782 B | view raw | |
| run.log | β | 869 B | view raw | |
| env.txt | β | 516 B | view raw | |
| kmod_baseline_build.log | β | 4.0 KB | view raw | |
| fix_build.log | β | 4.0 KB | view raw | |
| fix.diff | β | 516 B | view raw | |
| verdict.json | β | 2.9 KB | view raw |
DF-4265 β amdgpu_cs_wait_all_fences reads fence->error after the final dma_fence_put (UAF read)
File: sys/dev/drm/amd/amdgpu/amdgpu_cs.c (pass 2)
Severity: Low (use-after-free read; value returned as ioctl errno) β CWE-416
Confidence: likely (defect certain; runtime manifestation narrowed by
RCU-deferred freeing on this tree)
Build
cc -O2 -Wall -o wait_all_fences_uaf wait_all_fences_uaf.c
Run (as any user on amdgpu hardware)
./wait_all_fences_uaf
Submits 33 minimal GFX CS on one context (so the context fence slot for
seq 1 β replaced only after amdgpu_sched_jobs=32 further submissions,
amdgpu_ctx.c:449-461 β is recycled while the wait ioctl holds its own
reference), then calls DRM_IOCTL_AMDGPU_WAIT_FENCES with
wait_all=1 on seq 1.
Root cause (path:line)
amdgpu_cs_wait_all_fences() (amdgpu_cs.c:1481-1516):
r = dma_fence_wait_timeout(fence, true, timeout);
dma_fence_put(fence); /* :1501 may drop the LAST ref */
if (r < 0) return r;
if (r == 0) break;
if (fence->error) /* :1508 READ AFTER PUT */
return fence->error;
If the reference obtained from amdgpu_cs_get_fence() (amdgpu_ctx.c:489)
is the last one (context slot replaced, scheduler/job refs dropped),
:1501 frees the fence and :1508 reads freed memory. Compare the correct
ordering in the sibling amdgpu_cs_wait_ioctl() (:1367-1370: read
fence->error before the put).
On this tree the free is deferred: drm_sched_fence_release_finished β
dma_fence_put(scheduled) β drm_sched_fence_release_scheduled β
call_rcu(&fence->finished.rcu, drm_sched_fence_free)
(sys/dev/drm/scheduler/sched_fence.c:107-113), so a plain
single-threaded run reads pre-free memory; manifestation needs the RCU
grace period (or preemption) between :1501 and :1508, and the stale
value is returned to userspace as the ioctl errno β a 4-byte heap
oracle (whether the slot got reallocated by a concurrently-submitted
job, and the new fence's error code). Impact ceiling: info oracle +
theoretical crash; kept Low.
Guest verification (this audit)
Hardware-gated like DF-4262..64 (no AMD GPU): trigger builds
(build.log), run exits at open (run.log); baseline + patched
in-tree compile rc=0 with -Werror (kmod_baseline_build.log,
fix_build.log β reorder so the put happens after the error read, all
exit paths covered).
Fix
fix.diff: restructure the loop body so fence->error is read before
dma_fence_put() and every exit path (r<0 return, r==0 break, error
return, loop continue) puts the reference exactly once.
DF-4265 evidence pack β verdict
Reproduced? NOT at runtime β hardware-gated (no AMD GPU on the audit guest). Defect certain at code level; realistic manifestation is a narrow race (RCU-deferred free), hence confidence=likely for end-to-end impact.
Proof chain
amdgpu_cs_wait_fences_ioctl()(amdgpu_cs.c:1590-1622) withwait_all=1reachesamdgpu_cs_wait_all_fences()(:1481-1516).fence = amdgpu_cs_get_fence(...)(:1494) returns a referenced fence:amdgpu_ctx_get_fence()doesdma_fence_get(centity->fences[seq & (amdgpu_sched_jobs-1)])(amdgpu_ctx.c:489) β our own reference on top of the context array's.- Body of the loop (:1500-1509): r = dma_fence_wait_timeout(fence, true, timeout); dma_fence_put(fence); / :1501 / ... if (fence->error) / :1508 β read AFTER put / return fence->error;
- The context array reference is dropped when the slot is overwritten by
a later submission:
amdgpu_ctx_add_fence()(amdgpu_ctx.c:440-464) replacescentity->fences[seq & 31]anddma_fence_put(other)(:461). After 32 further submissions on the entity (plus normal scheduler teardown of the finished job), the reference dropped at :1501 can be the final one. - Free path on this tree:
dma_fence_putβ kref βdrm_sched_fence_release_finished(sched_fence.c:122-127) β put of the embeddedscheduledfence βdrm_sched_fence_release_scheduled(sched_fence.c:107-113) βcall_rcu(..., drm_sched_fence_free)β the slab object is returned tosched_fence_slabonly after an RCU grace period. Between :1501 and :1508 there are no RCU read-side critical sections holding it, so the read is a genuine use-after-free in the C sense; practical manifestation requires the grace period to elapse inside the window (preemption + idle machine makes this possible but rare). If freed+reallocated (attacker submits jobs concurrently βdrm_sched_fence_createallocates from the same slab), :1508 reads the NEW object'serrorfield and returns it as the ioctl's errno β 4-byte heap oracle. If freed and not reused, the read touches slab memory (typically non-fatal). - The correct ordering exists in the sibling ioctl
amdgpu_cs_wait_ioctl()(:1367-1371: error read precedes the put) β this is a local defect of wait_all_fences only.
Guest verification performed
- trigger compiles (build.log); runs to the hardware gate (run.log)
- baseline + patched (reordered put) in-tree compile rc=0 with -Werror (kmod_baseline_build.log / fix_build.log; fix hunk at :1498+)
Exploit chain: read-only oracle; no write primitive; no escalation path.
Recommended fix: read fence->error before dma_fence_put(fence),
covering all exit paths (fix.diff).
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_testableReordering fix.diff applied on the guest stacked with the other amdgpu_cs.c fixes (hunk @1498) and compiled in-tree with -Werror rc=0 (fix_build.log). Runtime comparison impossible (no AMD GPU); compile-only validation.
fix_build.log rc=0; guest patch output 'Hunk #1 succeeded at 1498 (offset -15 lines)'.
Confirmed kernel references
Detail
Exploit chain
Read-only oracle on drm_sched_fence slab reuse; no write primitive.
Evidence (decisive lines)
wait_all_fences_uaf.c (33 GFX submissions then WAIT_FENCES all on seq 1); run.log (hardware gate); kmod_baseline_build.log + fix_build.log; VERDICT.md has the full refcount/rcu analysis.
PoC changes
Trigger written from scratch; SCHED_JOBS=32 matches amdgpu_sched_jobs default (amdgpu_drv.c:108) so the slot holding seq 1 is recycled by the 33rd submission.
Verified recommended fix
Read fence->error before dma_fence_put() and put the reference on every exit path (r<0, r==0 break, error return, loop continue).
Verdict
Genuine use-after-free READ ordering defect in amdgpu_cs_wait_all_fences(): dma_fence_put(fence) at amdgpu_cs.c:1501 precedes the fence->error read at :1508; when our reference is the last one (context slot replaced after 32 further submissions - amdgpu_ctx.c:449-461 - and scheduler refs dropped) the read touches freed memory. On this tree the free is RCU-deferred (sched_fence.c:107-113 call_rcu), so single-threaded manifestation is unlikely and the practical ceiling is a 4-byte heap oracle returned as the ioctl errno (plus theoretical crash) - hence Low severity / confidence likely for end-to-end impact while the code defect itself is certain (the sibling amdgpu_cs_wait_ioctl at :1367-1370 has the correct order). Runtime reproduction hardware-gated (no AMD GPU on guest); fix (reordered put covering all exit paths) compiles in-tree with -Werror.
No comments yet.