# DF-4515 — drm_prime PRIME fd_to_handle unbalanced dma_buf_put → file refcount underflow → UAF

## What
`drm_gem_prime_fd_to_handle()` (sys/dev/drm/drm_prime.c:792) calls `dma_buf_get(prime_fd)`
at drm_prime.c:802 and executes a trailing `dma_buf_put(dma_buf)` at drm_prime.c:848/857/864
— the exact pairing of upstream Linux, where `dma_buf_get()` is `fget()` and *returns the
dma_buf with a file reference held*. On this port the shim
`dma_buf_get()` (sys/dev/drm/linux_dma-buf.c:168-187) calls `dropfp()` **before returning**,
so the pointer handed to drm_prime.c is unreferenced and the trailing `dma_buf_put()` is an
**unbalanced fdrop**.

Two consequences, both proven on the QEMU guest against the shipped drm.ko:

1. Every DRM_IOCTL_PRIME_FD_TO_HANDLE that hits the already-imported fast path
   (drm_prime.c:811-815 → out_put at :862-865) decrements the file refcount with no
   matching increment. A handful of repeated ioctls drive `f_count` to 0 while the
   prime fd is still installed and the GEM handle / rb-tree member still exist:
   `dmabuf_close()` fires (`release` → GEM object put + `kfree(dmabuf)`), and the
   next touch of the installed fd is a use-after-free.
2. Even on the first import, the returned `dma_buf` is unprotected: a racing
   `close(prime_fd)` from another thread frees it mid-ioctl (classic TOCTOU UAF
   across drm_prime.c:811-866 and drm_gem_prime_import_dev drm_prime.c:718-761).

The guest has no GPU (/dev/dri absent), so the unprivileged-ioctl entry could not be
exercised directly. The PoC therefore drives the **exact call sequence of
drm_prime.c:802/811-815/864** (and the export side :599-696) through the *real, shipped*
`drm.ko` symbols via a KLD, which reproduces the whole failure chain deterministically:
underflow → premature free → use-after-free → kernel panic.

## Files
- `df4515mod.c` + `Makefile` — KLD; `sysctl hw.df4515mod.fire=5` runs the full sequence
  (export → dma_buf_fd → 3× F2H lookup-hit cycle → UAF touch)
- `build.sh` / `run.sh` — exact guest commands
- `run.log`, `panic.txt` — baseline reproduction (panic: `fdrop: invalid f_count -1`)
- `fix_run.log` — same PoC after applying `fix.diff` (f_count stable at 2, no panic)
- `fix.diff` — one-line fix: keep the `holdfp()` reference in `dma_buf_get()`
- `VERDICT.md`, `manifest.json`, `verdict.json`

## Reproduce (as root in the guest)
```
cd /usr/src/sys/dev/drm/drm && make obj && make        # builds drm.ko (~4 min)
cd /root/df4515 && make                                # builds df4515mod.ko
kldload /usr/obj/usr/src/sys/dev/drm/drm/drm.ko
kldload df4515mod.ko
sysctl hw.df4515mod.fire=5        # expect: f_count 2→1→0, "release fired", then PANIC
```
Expected decisive output (see run.log / panic.txt):
```
df4515mod: [5] F2H lookup-hit cycle #3 done; f_count now 0  <== UNDERFLOW: file freed, fd still installed
df4515mod: [5] survived UAF touch (dma_buf_get -> 0)
panic: fdrop: invalid f_count -1
fdrop() <- closef() <- fdfree() <- exit1() <- sys_exit()
```
