# DF-4007 — i915 execbuf eb_move_to_gpu error path double-unreserves vmas (pin/fence/object-refcount underflow → UAF)

**File:** `sys/dev/drm/i915/i915_gem_execbuffer.c:1860-1876` (bug), unwind at `:857-879` via `:2442-2444`
**Class:** kernel refcount underflow → premature unbind/free → UAF (memcorrupt)
**Severity:** Medium · **Confidence:** certain (source; carried 4.19-vintage shape) · runtime hardware-gated (no GPU on guest)

## Build / Run / Expected
```
./build.sh   # builds model_df4007 + poc_df4007
./run.sh
```
Expected (`run.log`):
```
[DF-4007 BASELINE] move_to_active fails at index 1 (BASELINE)
    vma[0] objA  pin=-1 fence=-1 objrefs=2  <-- FENCE PIN UNDERFLOW (also pin underflow)
[DF-4007 BASELINE] move_to_active fails at index 2 (BASELINE)
    vma[0] objA  pin=-1 fence=-1 objrefs=2
    vma[1] shadow pin=-1 fence=0 objrefs=1  <-- OBJ REF UNDERFLOW (premature GEM object free -> UAF)
[DF-4007 control] ... perfectly balanced
[DF-4007 PATCHED ] ... balanced even on failure
---- ioctl PoC (no GPU on this guest) ----
poc exit=77
```

## Source chain (unprivileged ioctl path)
1. `eb_submit` → `eb_move_to_gpu` second loop (`i915_gem_execbuffer.c:1860-1875`):
   on `i915_vma_move_to_active(vma, eb->request, flags)` failure (`:1864`) it
   returns at `:1867` — for entries already processed it called
   `__eb_unreserve_vma(vma, flags)` **by value** (`:1870`) and `i915_vma_put` for
   `__EXEC_OBJECT_HAS_REF` (`:1873-1874`), but **never cleared those bits in
   `eb->flags[i]`** and left `eb->exec` set (`eb->exec = NULL` only at `:1876`).
2. `i915_vma_move_to_active` can fail: `active_instance()` returns `-ENOMEM` when
   its rbtree `kmalloc` fails (`i915_vma.c:981-989`) — needs the vma active from a
   second context (last_active cache miss) + DRM-slab exhaustion, i.e. an
   unprivileged client spraying execbufs under memory pressure.
3. The ioctl error path (`i915_gem_do_execbuffer:2442-2444`) then calls
   `eb_release_vmas` (`:857-879`), which for every entry with
   `flags & __EXEC_OBJECT_HAS_PIN` **unreserves again** (`:873-874`) and for
   `__EXEC_OBJECT_HAS_REF` **puts again** (`:876-877`).
4. `__i915_vma_unpin` is a raw `vma->flags--` (`i915_vma.h:323-326`),
   `__i915_vma_unpin_fence` a raw `vma->fence->pin_count--` (`:392-396`),
   `i915_vma_put` drops the GEM object ref (`:222`) — all underflow → vma
   unbound while the (skipped) request believes it pinned / object freed
   prematurely → UAF class. (GEM_BUG_ON guards are no-ops in non-DEBUG builds,
   `i915_gem.h:60-62`.)

Carried from the upstream 4.19-vintage file (verified identical control flow in
torvalds/linux v4.19 `i915_gem_execbuffer.c`); upstream later restructured the
loop (v5.2 ww-mutex rework unreserves unconditionally; v5.6 makes
i915_vma_move_to_active() void), removing the mid-loop error return.

## Fix
`fix.diff`: switch the loop tail to the by-ref `eb_unreserve_vma(vma, &eb->flags[i])`
(clears `__EXEC_OBJECT_RESERVED`) and clear `__EXEC_OBJECT_HAS_REF` after the put,
making any later `eb_release_vmas` a no-op for processed entries. Behavior is
model-verified balanced in `model_df4007.c` (PATCHED run) and compile-validated
by nativekernel rebuild (fix_build.log).
