# DF-1881 — Verification Verdict

## Verdict: REPRODUCED (source-confirmed + refcount-harness)

The unconditional `kref_get` in `dma_fence_get_rcu` is confirmed at
`sys/dev/drm/include/linux/dma-fence.h:97-103`. The harness reproduces
the resulting double-free.

## Mechanism

```c
// dma-fence.h:97-103
static inline struct dma_fence *
dma_fence_get_rcu(struct dma_fence *fence)
{
    if (fence)
        kref_get(&fence->refcount);   // ALWAYS increments — never fails
    return fence;                      // NEVER returns NULL
}
```

The correct implementation (kref.h:85 `kref_get_unless_zero`) fails when
refcount is already 0, so RCU readers can detect a fence mid-teardown and
retry. With the buggy unconditional increment, every retry check at
`linux_reservation.c:357/384/444/470/511` (`if
(!dma_fence_get_rcu(...)) goto retry`) is **dead code**: the reader
always gets a reference, even on a fence whose refcount is already 0
(release has fired). The reader's eventual `dma_fence_put` drives 1→0 a
second time, re-firing `dma_fence_release` → double-free.

Compounding: `dma_fence_free` (linux_fence.c:339-343) calls `kfree()`
not `kfree_rcu()`, so the memory is freed immediately while an
`rcu_read_lock` reader may still be dereferencing it.

Lifecycle trigger:
`reservation_object_add_excl_fence` (linux_reservation.c:235-261)
installs a new fence then `dma_fence_put(old_fence)` (:260) drives 0 →
release → kfree immediately. Reader:
`amdgpu_gem_wait_idle_ioctl → reservation_object_wait_timeout_rcu`
(amdgpu_gem.c:445); `radeon_gem_set_domain_ioctl` (radeon_gem.c:117);
`i915_request_await_object` (i915_request.c:989). Writer: execbuf/CS
ioctl `reservation_object_add_excl_fence` (ttm_execbuf_util.c:206).

## Harness evidence

```
DF-1881: dma_fence_get_rcu (dma-fence.h:97-103)

initial state: refcount=0 freed=0 (release has fired)
BUGGY dma_fence_get_rcu returns 0x7fffffdfd770 (NULL=0) — got ref on freed fence, retry check if(!got) is DEAD CODE (won't retry)
         later dma_fence_put drives refs 1->0, re-firing release -> DOUBLE FREE (freed=1)
FIXED dma_fence_get_rcu returns NULL=1 — reader retries (correct)
```

## Why no live trigger on this guest

All readers/writers are in drm GPU drivers (`amdgpu`, `radeon`, `i915`).
The audit guest has no discrete GPU; the drm modules are present as
`.ko` files but not loaded. `/dev/dri/cardN` does not exist. Valid
Phase-6 hard blocker.

## Exploit chain

Not applicable (drm-HW-gated). No `uid=0` claim. On a host with a drm
GPU, `/dev/dri/cardN` is mode 0666 by default; the reader/writer race
is triggerable by any local user via GEM wait/execbuf ioctls. With slab
grooming of the reclaimed fence, the double-free becomes an arbitrary
write → kernel priv-esc.

## PoC changes

- Added `harness.c`: refcount model showing the double-free.
- Added `fix.diff`: `kref_get` → `kref_get_unless_zero` in
  `dma_fence_get_rcu`; `kfree` → `kfree_rcu` in `dma_fence_free`.

## Fix

`fix.diff` changes `dma_fence_get_rcu` to use `kref_get_unless_zero`
(returning NULL when refcount is 0), which makes the RCU readers' retry
logic live. It also changes `dma_fence_free` to use `kfree_rcu` so the
memory is not freed while an RCU reader dereferences it.

- BEFORE: harness shows the reader gets a ref on a freed fence and
  double-frees.
- AFTER: harness shows the reader correctly retries (returns NULL).
