# DF-5222 — drm_legacy_ioremapfree bypasses linuxkpi iounmap() bookkeeping

File: `sys/dev/drm/drm_memory.c` (pass-2 GLM 5.3 audit)
Severity: Info (hardening / defense-in-depth). Not attacker-reachable in-tree.

## What

`drm_legacy_ioremap()` / `drm_legacy_ioremap_wc()` (sys/dev/drm/drm_memory.c:64-74)
map MMIO via the linuxkpi `ioremap()` family, which is implemented by
`__ioremap_common()` (sys/dev/drm/linux_iomapping.c:41-59):

  * kmallocs a `struct iomap` (linux_iomapping.c:50),
  * maps with `pmap_mapdev_attr()` (:53),
  * **registers the mapping in the global `iomap_list` under `iomap_lock`** (:54-56).

The only code that ever removes an entry from `iomap_list`, frees the `struct iomap`,
and restores PAT attributes for RAM-backed ranges is `iounmap()`
(linux_iomapping.c:61-103).

`drm_legacy_ioremapfree()` (drm_memory.c:76-83) instead calls
`pmap_unmapdev((vm_offset_t)map->handle, map->size)` **directly**
(drm_memory.c:81), skipping `iounmap()` entirely. Consequence for any caller
pairing the two exported wrappers:

  1. the `struct iomap` entry is leaked from `iomap_list` forever
     (kernel-memory leak + stale SLIST node whose `pmap_addr` points at freed KVA),
  2. the PAT-attribute restore for RAM-backed mappings (linux_iomapping.c:88-91)
     never happens,
  3. the device mapping's KVA is returned to `kernel_map` while a stale node
     still names it — a bookkeeping inconsistency that any future mixed
     ioremapfree/iounmap use turns into wrong-range unmap bookkeeping.

## Why Info (not higher)

The whole wrapper family is **orphaned in-tree**: a whole-tree search finds
zero callers of `drm_legacy_ioremap`, `drm_legacy_ioremap_wc` or
`drm_legacy_ioremapfree` (only the definitions at drm_memory.c:64-83 and the
prototypes in include/drm/drm_legacy.h:191-193). The DRM core paths that DO map
device memory use `ioremap()`/`ioremap_wc()`/`iounmap()` directly:

  * drm_bufs.c:219-222 (`drm_addmap_core` _DRM_REGISTERS),
  * drm_bufs.c:341, 358 (error unwinds),
  * drm_bufs.c:524 (`drm_legacy_rmmap`).

The only user-controlled entry into this area, `DRM_IOCTL_ADD_MAP`, is gated
`DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY` (drm_ioctl.c:590) — root only — and does
not go through these wrappers anyway. Reaching the mismatch therefore requires
root to load a hand-written kmod that calls the exported wrappers — i.e. root
attacking itself. No unprivileged route exists.

## Reproduce (conceptual — intentionally NOT run)

PoC execution was skipped per audit contract (Low/Info hardening finding).
A kmod proof would be: `kldload` a module that calls
`drm_legacy_ioremap(&map, dev)` with a page-aligned MMIO range, then
`drm_legacy_ioremapfree(&map)`, then walk `iomap_list` via ddb — the entry
remains. `fix.diff` removes the hazard by routing the free through `iounmap()`.

## Fix

One line — see `fix.diff`: replace the direct `pmap_unmapdev()` call with
`iounmap(map->handle)`. `iounmap` is already declared for this file via
drmP.h:43 → linux/io.h:32 → asm/io.h:146.
