amdgpu_do_test_moves success path leaks all n pinned+reserved+referenced GTT test BOs (~entire GTT) with reservation locks held forever
Summary
Each iteration creates+reserves+pins a 1MiB GTT BO then 'continue's past the error-only unwind - the epilogue releases only vram_obj and the pointer array. n ~= the whole GTT by construction: one successful run permanently exhausts GTT and leaves ~n reservation locks held forever (wedged teardown/unload). Root/boot-gated debug param (amdgpu_testing=1, default 0). Guest-unreachable (no AMD GPU - probe evidence). Guarded post-loop unwind fix, git-apply clean.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5105 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 4.3 KB | β raw | |
| probe.sh | β | 734 B | view raw | |
| build.sh | β | 94 B | view raw | |
| run.sh | β | 153 B | view raw | |
| run.log | β | 1.2 KB | view raw | |
| env.txt | β | 579 B | view raw | |
| fix.diff | β | 566 B | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| verdict.json | β | 4.3 KB | view raw |
DF-5105 β amdgpu_do_test_moves success path leaks every GTT test BO (pinned + reserved + referenced)
What runs it
amdgpu_test.c is compiled into the stock amdgpu.ko (Makefile:63) and
amdgpu_test_moves() is called from amdgpu_device_init()
(amdgpu_device.c:2684-2686) iff the load-time module parameter
amdgpu.amdgpu_testing & 1 is set (amdgpu_drv.c:85 default 0,
amdgpu_drv.c:192 mode 0444, i.e. root/boot-gated) and the GPU accel path is
up. No userspace trigger; root opts in at boot/module-load.
Build
Nothing to compile β this is a kernel-path finding. The probe script talks to the running kernel:
./build.sh # no-op (copies probe.sh, no compilation)
Run (inside the guest, as root)
sh /root/df5105/probe.sh # or ./run.sh via vm.sh
Expected on an AMD-GPU-equipped host with amdgpu.testing=1
On success the loop at amdgpu_test.c:85-234 continues (line 217) past the
error-only unwind labels (219-233); after the loop only vram_obj is
unpinned/unreserved/unreffed (236-240) and the pointer array is kfree'd
(242). The n created GTT BOs (each 1 MiB, pinned, reservation held, refcount
1) are never released β n β (gart_size β overhead)/1MiB, i.e. essentially the
whole GTT. Second-order: their amdgpu_bo_reserve() locks are held forever,
so any later eviction/teardown path that tries to reserve them blocks.
What this guest shows
This QEMU guest has no AMD display adapter (only the emulated bochs VGA
chip 0x11111234), so amdgpu_device_init() never runs and the test path is
unreachable here regardless of the parameter. The probe documents exactly
that: module loads, no device attaches. The leak itself is proven by direct
reading of sys/dev/drm/amd/amdgpu/amdgpu_test.c:85-242 (see VERDICT.md).
Artifacts
probe.shβ the guest proberun.logβ full untrimmed probe outputenv.txtβ guest environmentVERDICT.mdβ full narrative with line citationsfix.diffβ post-loop unwind of gtt_obj[] (both paths safe)verdict.json/manifest.json
VERDICT β DF-5105
Finding (re-derived, line-accurate)
amdgpu_do_test_moves() (sys/dev/drm/amd/amdgpu/amdgpu_test.c:32-246)
creates n 1 MiB GTT buffer objects inside its main loop:
- amdgpu_test.c:92
amdgpu_bo_create(adev, &bp, gtt_obj + i)β refcount 1, only pointer isgtt_obj[i] - amdgpu_test.c:98
amdgpu_bo_reserve(gtt_obj[i], false)β reservation lock ACQUIRED and held - amdgpu_test.c:101
amdgpu_bo_pin(gtt_obj[i], AMDGPU_GEM_DOMAIN_GTT)β GTT space pinned
On a fully successful iteration the code prints the success line and
continue;s:
- amdgpu_test.c:215-217
DRM_INFO(...); continue;
The unwind labels out_lclean_unpin/_unres/_unref/out_lclean
(amdgpu_test.c:219-233) are reachable only by falling into them from the
error branches above them (copy failure, kmap failure, data mismatchβ¦);
they end in break. A successful iteration never executes them.
After the loop finishes normally, the epilogue releases ONLY the VRAM object and the pointer array:
- amdgpu_test.c:236
amdgpu_bo_unpin(vram_obj); - amdgpu_test.c:238
amdgpu_bo_unreserve(vram_obj); - amdgpu_test.c:240
amdgpu_bo_unref(&vram_obj); - amdgpu_test.c:242
kfree(gtt_obj);
The kfree(gtt_obj) frees the array of pointers, not the BOs. Every
gtt_obj[i] still has refcount 1 (held by the now-freed array slot), is
still PINNED, and β worse β its amdgpu_bo_reserve() from line 98 was never
undone, so its reservation lock is held forever by a thread that has returned.
Consequence
n is computed at amdgpu_test.c:47-55 as
(gart_size β IB pool β ring sizes β wb β IH)/1 MiB β i.e. by construction
the test allocates essentially the entire GTT. One successful run therefore:
- permanently leaks ~
nMiB of pinned GTT address space (resource exhaustion: subsequent GTT placements for any amdgpu user fail), - leaves ~
nww_mutexes locked forever β later eviction, shrinker, or teardown paths that try toamdgpu_bo_reserve()any of these BOs block indefinitely (potential hang of the drm subsystem / unload).
Reachability
- Code is in the stock module: sys/dev/drm/amd/amdgpu/Makefile:63.
- Runs only when
amdgpu_testing & 1at device init: sys/dev/drm/amd/amdgpu/amdgpu_device.c:2684-2686. amdgpu_testingdefaults to 0 and is a load-time (0444) module parameter: sys/dev/drm/amd/amdgpu/amdgpu_drv.c:85,192 β root/boot-gated debug path.- Requires AMD GPU hardware with
adev->accel_working.
Verification on the audit guest (2026-09-27)
The QEMU/KVM guest has no AMD display adapter (pciconf shows only the
emulated bochs VGA, chip 0x11111234; no vendor-0x1002 device). amdgpu.ko
loads (kldload rc=0) and prints "[drm] amdgpu kernel modesetting enabled.",
but no device attaches, so amdgpu_device_init() never runs and
amdgpu_test_moves() cannot be executed here β with or without
amdgpu_testing=1.
Classification: not_reproduced (target hardware absent). The finding itself is certain from direct source reading β the leak needs no runtime condition beyond "the loop completes successfully", which is the test's designed-for success path. No memory-corruption primitive is involved (objects are leaked, not freed-then-used), so there is no escalation chain: the ceiling on AMD-equipped hosts is GTT exhaustion + permanently-held reservation locks β graphics-stack DoS / wedged unload, self-inflicted by the root-administered test parameter.
Fix
fix.diff adds a post-loop unwind of gtt_obj[]. It is safe on both exits:
the error path has already amdgpu_bo_unref()ed (and NULLed) every entry it
touched, amdgpu_bo_create failure leaves the slot NULL from kcalloc, and
the guard if (gtt_obj[i]) makes the loop a no-op there; on success it
releases all n BOs in reverse order before kfree(gtt_obj).
Fix validation: not_testable β the single-fix kernel build/re-run protocol is meaningless on a guest with no AMD GPU (the path cannot be exercised in either the baseline or the patched kernel).
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_testablenot_testable: single-fix kernel build + PoC re-run is meaningless on a guest with no AMD GPU β the code path cannot be exercised in either the baseline or the patched kernel; the fix is validated structurally (guard makes it a no-op on the error path where slots are already unreffed/NULLed by amdgpu_bo_unref, and handles n==0 via i<0)
fix.diff in this pack passes 'git apply --check' against the unmodified sys/ tree; error-path no-op argument documented in VERDICT.md
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/amdgpu_test.c:47-55
- sys/dev/drm/amd/amdgpu/amdgpu_test.c:85-101
- sys/dev/drm/amd/amdgpu/amdgpu_test.c:217
- sys/dev/drm/amd/amdgpu/amdgpu_test.c:219-233
- sys/dev/drm/amd/amdgpu/amdgpu_test.c:236-242
- sys/dev/drm/amd/amdgpu/amdgpu_device.c:2684-2686
- sys/dev/drm/amd/amdgpu/amdgpu_drv.c:85
- sys/dev/drm/amd/amdgpu/amdgpu_drv.c:192
- sys/dev/drm/amd/amdgpu/Makefile:63
Detail
Evidence (decisive lines)
VERDICT.md (line-accurate derivation); run.log (probe output: no AMD VGA in pciconf, kldload amdgpu rc=0 with 'kernel modesetting enabled' but zero device-attach lines); amdgpu_test.c:217 'continue' vs 219-233 error-only unwind and 236-242 epilogue freeing only vram_obj + kfree(gtt_obj) of the pointer array; fix.diff applies cleanly (git apply --check OK) and is a no-op on the error path because amdgpu_bo_unref NULLs slots
PoC changes
No seed PoC existed; authored probe.sh fresh (kernel-internal path, so the 'PoC' is a reachability probe: pciconf + kldload + dmesg, all captured untrimmed in run.log)
Verified recommended fix
Add a post-loop guarded unwind of gtt_obj[] (unpin/unreserve/unref for every non-NULL slot) before kfree(gtt_obj) β see fix.diff, git-apply-able, no-op on the error path
Verdict
The leak is certain from source: a successful iteration of the move-test loop 'continue's past the error-only unwind labels (amdgpu_test.c:217 vs 219-233), and the epilogue releases only vram_obj plus the pointer array (236-242), so all n GTT BOs β each created (92), reserved (98) and pinned (101), refcount held solely by the kfree'd array β leak with their reservation locks locked forever; n is by construction (47-55) essentially the whole GTT. Runtime verification was genuinely attempted but the QEMU guest has no AMD display adapter (pciconf shows only the emulated bochs VGA chip 0x11111234): amdgpu.ko loads (kldload rc=0) but no device attaches, so amdgpu_device_init() and thus amdgpu_test_moves() (amdgpu_device.c:2684-2686) never execute here β hence not_reproduced/impact=none ON THIS GUEST. Realistic ceiling on AMD-equipped hosts: one root-gated (amdgpu_testing load-time param, amdgpu_drv.c:85/192) successful test run permanently consumes ~all GTT and leaves ~n ww_mutexes held -> graphics-stack DoS and wedged unload. No memory-corruption primitive (objects are leaked, never used-after-free), so no escalation chain exists for this class.
No comments yet.