# DF-5064 — drm_device_detach never unregisters or releases the drm_device: stale /dev/dri minors call into unloaded module memory + unbounded leak

## What

`drm_device_detach()` (sys/dev/drm/drm_dragonfly.c:251-295) is the **only**
newbus `device_detach` method for all three KMS drivers
(i915_pci.c:880, radeon_drv.c:865, amdgpu_drv.c:1421). It runs
`drm_sysctl_cleanup`, frees `dev->agp`, optionally calls
`dev->driver->unload`, disables busmaster, `lockuninit`s three locks and
frees the pci_dev (`drm_fini_pdev`). It **never calls
`drm_dev_unregister()` nor `drm_dev_put()`** — the DragonFly equivalents of
Linux's `i915_pci_remove()` → `i915_driver_unload(dev); drm_dev_put(dev);`
(i915_pci.c:680-691) and `drm_get_pci_dev`'s teardown. Nothing else in the
DF drm tree does it either (`drm_fini_pdev` appears only in the three
attach error paths and here; no `drm_dev_unregister` on any detach path).

Consequences after `kldunload i915kms` (drm.ko itself stays resident via
`MODULE_DEPEND(i915, drm, ...)`):

1. **Stale live entry points into freed module memory.** The
   `/dev/dri/cardN` / `renderDN` cdevs (created by
   `drm_sysfs_minor_alloc`, drm_sysfs.c:97, with `drm_cdevsw` owned by the
   still-loaded drm.ko) keep working: `drm_open` → `drm_minor_acquire`
   finds the never-unregistered minor in `drm_minors_idr` →
   `dev->driver->open` — a function pointer into the **unloaded** i915
   module's text (`.open = i915_driver_open`, i915_drv.c:3284ff). Any
   *unprivileged* user who opens the stale node afterwards executes freed
   kernel memory (UAF-class). The same holds for every `dev->driver->*`
   and `dev->dev_private` dereference on the leaked device.
2. **Unbounded leak per load/unload cycle**: the whole `drm_device` graph
   (drm_device + minors + `drm_sysfs_minor_alloc` kdev + vblank etc.) is
   never freed (`drm_dev_put` drops the only primary reference; release
   (`i915_driver_release` / `drm_dev_fini`) never runs).
3. **For i915 the driver itself is never torn down at all**: i915's
   `struct drm_driver` has **no `.unload`** member on DragonFly
   (i915_drv.c:3279-3313), so `drm_device_detach` skips the entire
   `i915_driver_unload()` path — GPU MMIO mappings/IRQs are left live
   while the module goes away.
4. `lockuninit()` on `struct_mutex`/`vbl_lock`/`event_lock`
   (drm_dragonfly.c:288-290) races any in-flight ioctl still holding them
   (no synchronization with open clients at detach).

## Reachability / threat model

Requires (a) a DRM adapter (i915/radeon/amdgpu hardware) and (b) root to
`kldunload` the kms module — then the UAF window (1) is triggerable by any
unprivileged user with access to /dev/dri (mode 0666 by default via
`DRM_DEV_MODE`). The QEMU audit guest has **no display-class PCI device**
(env.txt), so this is validated statically + the fix compile-validated
(drm.ko and i915.ko build clean with it, fix_build.log).

## Fix

`fix.diff`: after `drm_fini_pdev(&dev->pdev)`, call
`drm_dev_unregister(dev)` (minors become ENODEV for future opens —
`drm_minor_acquire` then fails cleanly) and `drm_dev_put(dev)` (drops the
primary reference; `drm_dev_release` frees the graph via the driver's
`.release`). Full i915 parity additionally wants wiring
`i915_driver_unload` (the DF driver table lacks `.unload`); that is noted
in the diff comment and left to maintainers, since a partial teardown is
strictly better than a guaranteed post-unload UAF.

## Artifacts

* `trace.md` — full static call-chain trace with path:line citations.
* `fix_build.log` — full drm tree build on the guest with the patched file
  (`BUILD_RC=0`, drm.ko + i915.ko linked).
* `env.txt` — guest evidence of the absent DRM adapter.
