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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4264 Β· 12 files
FileTypeDescriptionSize
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;

VERDICT.md
↓ download raw

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

  1. amdgpu_cs_fence_to_handle_ioctl() (amdgpu_cs.c:1417-1471) obtains a reference: :1427 fence = amdgpu_cs_get_fence(adev, filp, &info->in.fence); β€” amdgpu_cs_get_fence() (:1391-1415) returns the fence from amdgpu_ctx_get_fence() which does dma_fence_get(...) (amdgpu_ctx.c:489).
  2. Switch on info->in.what (:1431): - GET_SYNCOBJ / GET_SYNCOBJ_FD / GET_SYNC_FILE_FD all reach a dma_fence_put(fence) on every exit path (:1434, :1443, :1453, :1458). - default: (:1468-1470) is return -EINVAL; with NO put.
  3. Therefore each call with what βˆ‰ {0,1,2} drops one reference on the floor. The leaked fence keeps a drm_sched_fence slab 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).
  4. 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_testable
baseline no→ patch + rebuild →patched clean

One-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)'.
↓ fix.diffper-fix-DF-4264

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.