# 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 is `gtt_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:

1. permanently leaks ~`n` MiB of pinned GTT address space (resource
   exhaustion: subsequent GTT placements for any amdgpu user fail),
2. leaves ~`n` ww_mutexes locked forever — later eviction, shrinker, or
   teardown paths that try to `amdgpu_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 & 1` at device init:
  sys/dev/drm/amd/amdgpu/amdgpu_device.c:2684-2686.
- `amdgpu_testing` defaults 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.
