AMDGPU_FENCE_TO_HANDLE default case returns -EINVAL without dma_fence_put β one leaked fence reference per invalid call
Summary
The ioctl takes a fence reference; the three valid 'what' cases all put it, but the default case returns without the put - every invalid call permanently keeps a drm_sched_fence slab object (and its parent chain) alive. Unprivileged render-node; slow but zero-cost, silent, unbounded exhaustion. One-line put fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4264 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.6 KB | β raw | |
| VERDICT.md | β | 2.1 KB | β raw | |
| fence_to_handle_leak.c | β | 4.2 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 | β | 181 B | view raw | |
| verdict.json | β | 2.3 KB | view raw |
DF-4264 β AMDGPU_FENCE_TO_HANDLE default case leaks the dma_fence reference
File: sys/dev/drm/amd/amdgpu/amdgpu_cs.c (pass 2)
Severity: Low (unprivileged kernel memory leak / slow DoS) β CWE-401
Confidence: certain
Build
cc -O2 -Wall -o fence_to_handle_leak fence_to_handle_leak.c
Run (as any user on amdgpu hardware)
./fence_to_handle_leak
Allocates a context, submits one GFX CS (so the context records fence
seq 1), then issues 10 000 DRM_IOCTL_AMDGPU_FENCE_TO_HANDLE calls with
in.what = 0xFFFFFFFF. Every call leaks one dma_fence reference.
Root cause (path:line)
amdgpu_cs_fence_to_handle_ioctl() (amdgpu_cs.c:1417-1471) takes the
fence reference at :1427 (amdgpu_cs_get_fence β
amdgpu_ctx_get_fence, amdgpu_ctx.c:489 takes a dma_fence_get). The
three valid what cases each dma_fence_put() the reference
(:1434/:1343-1447, :1453/:1458, :1464); the default case
(:1468-1470) returns -EINVAL without the put. Each invalid call
permanently keeps alive a drm_sched_fence slab object and, through the
parent chain, everything the release path would have dropped.
Reachability: AMDGPU_FENCE_TO_HANDLE is DRM_AUTH|DRM_RENDER_ALLOW
(amdgpu_kms.c:1202).
Guest verification (this audit)
Same hardware gate as DF-4262 (no AMD GPU β no /dev/dri): trigger
builds (build.log), run exits at open (run.log); baseline + patched
in-tree compile of amdgpu_cs.c rc=0 with -Werror
(kmod_baseline_build.log, fix_build.log β the one-line fix adds
dma_fence_put(fence); before the -EINVAL return).
Fix
fix.diff: default: dma_fence_put(fence); return -EINVAL;
DF-4264 evidence pack β verdict
Reproduced? NOT at runtime β hardware-gated (no AMD GPU on the audit guest, /dev/dri absent β env.txt/run.log). Defect: PROVEN at code level (certain): one dma_fence reference leaked per invalid AMDGPU_FENCE_TO_HANDLE ioctl.
Proof chain
amdgpu_cs_fence_to_handle_ioctl()(amdgpu_cs.c:1417-1471) obtains a reference: :1427fence = amdgpu_cs_get_fence(adev, filp, &info->in.fence);βamdgpu_cs_get_fence()(:1391-1415) returns the fence fromamdgpu_ctx_get_fence()which doesdma_fence_get(...)(amdgpu_ctx.c:489).- Switch on
info->in.what(:1431): - GET_SYNCOBJ / GET_SYNCOBJ_FD / GET_SYNC_FILE_FD all reach adma_fence_put(fence)on every exit path (:1434, :1443, :1453, :1458). -default:(:1468-1470) isreturn -EINVAL;with NO put. - Therefore each call with
whatβ {0,1,2} drops one reference on the floor. The leaked fence keeps adrm_sched_fenceslab object (and its scheduled/finished fence pair + parent chain) alive forever. Looping the ioctl is an unbounded kernel-memory leak (Low severity: slow, ~hundreds of bytes per call, but zero-cost and silent). - Reachability: DRM_AUTH|DRM_RENDER_ALLOW (amdgpu_kms.c:1202) β any local user with the render node; the fence must be valid (a real submitted seq), which the PoC sets up with one minimal GFX CS.
Guest verification performed
- trigger compiles (build.log); runs to the hardware gate (run.log)
- baseline + patched in-tree compile rc=0 with -Werror (kmod_baseline_build.log / fix_build.log; fix hunk at :1466)
Exploit chain: none beyond memory exhaustion.
Recommended fix: put the reference in the default case (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_testableOne-line fix.diff applied on the guest stacked with the other amdgpu_cs.c fixes (hunk @1466) 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 1466 (offset -14 lines)'.
Confirmed kernel references
Detail
Exploit chain
Unbounded but slow kernel-memory exhaustion; no memory-safety violation.
Evidence (decisive lines)
fence_to_handle_leak.c (ctx + 1 CS + 10000 invalid what=0xFFFFFFFF calls); run.log (hardware gate); kmod_baseline_build.log + fix_build.log (in-tree -Werror compile before/after one-line fix).
PoC changes
Trigger written from scratch; sets up a real fence (ctx + minimal GFX CS) so amdgpu_cs_get_fence returns a referenced object before the leak.
Verified recommended fix
Add dma_fence_put(fence) to the default case of the what switch (amdgpu_cs.c:1468).
Verdict
Code-certain dma_fence reference leak: amdgpu_cs_fence_to_handle_ioctl() takes the fence reference at amdgpu_cs.c:1427 and all three valid 'what' cases dma_fence_put() it, but the default case (:1468-1470) returns -EINVAL without the put - one leaked fence (and its drm_sched_fence slab object + parent chain) per invalid AMDGPU_FENCE_TO_HANDLE call by an unprivileged render-node client. Runtime reproduction hardware-gated on this guest (no AMD GPU); defect and fix proven at code/compile level.
No comments yet.