# DF-4533 VERDICT

**Classification:** REAL BUG, `inconclusive` at runtime (hardware-gated),
source-certain.

## Root cause chain

1. `sys/dev/drm/drm_file.c:325` — `drm_open()` acquires the minor
   (`drm_minor_acquire()` → `drm_dev_get()`, `drm_drv.c:271-290`).
2. `drm_file.c:343-358` — on failure, `err_undo` drops it (`:357`). On
   **success** the function returns with the reference deliberately held —
   this mirrors Linux, where the *file* owns the reference and
   `drm_release()` (the fops `->release`) drops it via
   `file_priv->minor`.
3. On DragonFly the release fop equivalent is `drm_close()`:
   - `drm_file.c:373` — acquires a **fresh** minor reference
     (this is the DF-1678-era safety acquire),
   - `drm_file.c:445` — releases that fresh reference.
   The open-held reference from step 2 is never released: net **+1 dev kref
   per open/close cycle**.
4. The two other would-be balances are dead:
   - `drm_release()` (`drm_file.c:595-604`) returns 0 immediately on
     `__DragonFly__` before any release;
   - `drm_cdevpriv_dtor()` (`drm_drv.c:1194-1236`) is never registered
     (no `devfs_set_cdevpriv()` caller in `sys/dev/drm` — mechanically
     proven by `static_proof.sh`).

## Consequence

- `dev->ref` grows without bound; `drm_dev_release()` (`drm_drv.c:737-747`)
  never fires, so at driver unload the entire `struct drm_device` (and the
  driver-private memory hanging off it) leaks instead of being finalized by
  `drm_dev_fini()` (`drm_drv.c:666-692`).
- Any video-group user can grow the count by cycling `open()`/`close()`
  (trigger.c); ~2^31 cycles would wrap the int-based atomic counter
  (theoretical, hours of continuous cycling).

Severity: **Low** — bounded real-world impact (one-time dev leak at unload +
refcount growth); no memory-safety consequence demonstrated. It sits squarely
in the DF-1678 family ("drm_open/release vs unload — the minor refcount
protocol"), which pass 2 was asked to extend.

## What was run

| Step | Result |
|---|---|
| `static_proof.sh` on the repo | **PASS** — all four acquire/release sites enumerated; `drm_release()` dead on DFly; dtor unregistered |
| `trigger.c` built on guest (`-Wall -Wextra -O2`) | clean, RC=0 |
| `trigger 1000` on guest | hardware-gated: `/dev/dri/card0: No such file or directory` |
| `fix.diff` applies to pristine `sys/dev/drm/drm_file.c` | verified (`patch --dry-run` + apply on scratch copy) |
| patched `drm_file.c` compiled with exact kernel CFLAGS | **RC=0** (`fix_build.log`, object `/tmp/drm_file_4533.o`) |
| guest source restored | md5 matches repo |

## Fix

`fix.diff`: save `file_priv->minor` into `open_minor` at `drm_close()` entry
(before the kfree at `:430`) and `drm_minor_release(open_minor)` at the tail,
next to the existing release of the close's own acquire. Keeps the DF-1678
safety acquire intact while restoring the Linux refcount pairing.
Same-anchor note: DF-4532's fix.diff anchors at the same tail — merge by hand
if both are applied.

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