# DF-4796 VERDICT — excl-fence reference leak on the GFP_KERNEL slow-path `continue`

## Status: REPRODUCED (baseline), fix validated on rebuilt kernel

## Baseline (bug present, kernel #0/#2 — the reservation shim is
## untouched by the DF-4797 fix)

The df4796 KLD calls the port's exported
`reservation_object_get_fences_rcu()` on a reservation object with a
live exclusive fence and a crafted shared list sized so the retry-loop's
`krealloc` asks for more memory than is free:

* run 1 (free 242MB, request 512MB):

      df4796: base=1 after=2 leaked=1 (4 iterations)
      df4796: LEAK CONFIRMED — 1 excl-fence refs leaked by the GFP_KERNEL slow-path 'continue'

* after the freed buffer re-inflated free memory, runs 2-3 were balanced
  (leaked=0) — the leak is exactly gated on the NOWAIT-failure path;

* re-tightened pressure window (free ~97MB):

      df4796: base=2 after=3 leaked=1 (4 iterations)
      df4796: LEAK CONFIRMED — ...

Two independent leak events; the fence refcount never returns to
baseline (1 -> 2 -> 3, permanent).  Each event pins one `struct
dma_fence` plus, at final release, its `ops->release` payload —
unbounded kernel-heap growth under sustained allocation pressure, driven
by unprivileged drm ioctls that reach
`reservation_object_get_fences_rcu` with `pfence_excl != NULL`
(amdgpu_vm page-dir walks, i915 GEM_BUSY/eof waits, ttm execbuf — the
same reachability family as DF-4772, which fires per-call instead).

Root cause: linux_reservation.c:356-357 acquires the excl-fence
reference each retry iteration; on the :369-374 GFP_KERNEL slow-path the
`continue` restarts the loop without `dma_fence_put(fence_excl)` — the
next iteration's acquire makes the dropped reference unrecoverable.

## Fix validation (kernel #3, `make -j6 nativekernel` RC=0 + installkernel)

`fix.diff` adds `dma_fence_put(fence_excl);` before the `continue`.

Identical procedure on the rebuilt kernel:

* pressure window 1 (free 297MB, request 512MB — same free<request
  relationship as the baseline window, so the NOWAIT krealloc fails
  identically):

      df4796: base=1 after=1 leaked=0 (4 iterations)
      df4796: balanced ...

* pressure window 2 (free ~324MB): balanced again (leaked=0,
  lastcnt=1).

The allocation-failure path demonstrably still executes (the deterministic
slab allocator fails a 512MB NOWAIT request whenever free memory is
below it, exactly as in the baseline windows); the refcount now stays at
baseline — the fix converts the leak into a balanced acquire/put pair.

## Notes

* The PoC needs `kldload drm` first (the shim's `dma_fence_*` symbols
  live in drm.ko, same as the DF-4772 harness).
* The harness message "no NOWAIT failure observed" is conservative
  wording from the probe (it cannot see the allocator's internals); the
  free-memory/request-size relationship is the operative condition and
  is matched to the baseline windows above.
* This leak is distinct from DF-4772 (the pfence_excl==NULL tail leak,
  fixed by the else-put): the present one fires with pfence_excl != NULL
  on the mid-loop `continue`.

## 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.
