# DF-4510 — drm_syncobj lockless fence lifetime: `drm_syncobj_fence_get` × `drm_syncobj_replace_fence` → synchronous kfree ⇒ dma_fence UAF

* **File:** `sys/dev/drm/drm_syncobj.c:170-216` (+ `include/drm/drm_syncobj.h:120-130` reader)
* **Class:** kernel heap use-after-free (read+write), unprivileged via render node
* **Reachability:** i915 (`i915_drv.c:3283-3285`) / amdgpu (`amdgpu_drv.c:1302-1306`)
  set `DRIVER_SYNCOBJ`; the syncobj ioctls are `DRM_UNLOCKED|DRM_RENDER_ALLOW`
  (`drm_ioctl.c:685-698`).  Needs ≥2 CPUs for the interleave.

## The bug in one paragraph

`drm_syncobj_fence_get()` loads `syncobj->fence` and takes a reference with a
*bare* `kref_get` (`dma-fence.h:189-199`), under an `rcu_read_lock()` that on
this port is **only `preempt_disable()`** (`rcupdate.h:46-56`) — no grace
period.  `drm_syncobj_replace_fence()` swaps the pointer under
`syncobj->lock`, then drops the old fence's reference **after releasing the
lock** (`drm_syncobj.c:195`).  A syncobj whose fence was installed by
`SYNCOBJ_SIGNAL` holds the *only* reference to that stub fence, so the put
frees it **immediately** (`linux_fence.c:43-51` → `kfree`).  A waiter on
another CPU that loaded the pointer just before the swap executes
`kref_get` on freed memory and then *uses* the freed fence
(`dma_fence_is_signaled` dispatches `fence->ops`; `dma_fence_add_callback`
writes `fence->cb_list`).  Groomable heap UAF; full chain in `VERDICT.md`.

## Reproduce (i915/amdgpu hardware, ≥2 CPUs, unprivileged)

```
cc -O2 -pthread -o syncobj_race syncobj_race.c
./syncobj_race 30      # thread A: SYNCOBJ_SIGNAL loop; thread B: SYNCOBJ_WAIT(0) loop
```
Expected: kernel panic / slab or refcount corruption splat during the run, or
silent corruption detectable in dmesg.

## On the audit guest

`run.log`: no `/dev/dri/*` nodes exist (the guest's only display device is
QEMU virtio-gpu `chip=0x11111234`, unsupported by this i915/radeon/amdgpu
port).  The trigger compiles cleanly (`build.log`) but the race cannot be
started — verdict **inconclusive** (device absent), not a false positive.

## Fix

`fix.diff` — both halves: `kref_get_unless_zero` + retry in
`dma_fence_get_rcu`/`_rcu_safe`, and `kfree_rcu(fence, rcu)` in
`dma_fence_release`/`dma_fence_free` (the port's `__kfree_rcu` delay-line
provides a ≥1 s grace period).
