# DF-4569 — drm_ioctl.c dispatch never validates the user ioctl command

DragonFly `drm_ioctl()` (sys/dev/drm/drm_ioctl.c:801) dispatches purely on
`nr = DRM_IOCTL_NR(cmd)` = `cmd & 0xff`. It never compares the user-supplied
command word against the canonical `ioctl->cmd` from the dispatch table —
neither the group byte, nor the IOC direction bits, nor the IOCPARM_LEN size
bits. On DragonFly the kernel argument buffer is allocated by
`mapped_ioctl()` (sys/kern/sys_generic.c:668, 674-680) **from the user's size
bits** (kmalloc(size) when size > 128, else a 128-byte on-stack buffer;
IOC_VOID commands always get the stack buffer), and the final copyout ships
`size` bytes back (sys_generic.c:729-730). Every DRM handler interprets
`data` as the full compile-time structure, so a crafted command word turns
handlers into OOB accesses and leak oracles.

Upstream Linux v4.7 (the generation this port derives from) contains the
exact protection the port dropped: `drv_size = _IOC_SIZE(ioctl->cmd);
usize = _IOC_SIZE(cmd); asize = max(usize, drv_size); cmd = ioctl->cmd;`
with zero-extension between `usize` and `asize`.

## Impact
* **Kernel stack buffer overflow (write)** — `DRM_IOCTL_GET_STATS` (nr 0x06,
  flags `DRM_UNLOCKED` only, no DRM_AUTH): `drm_getstats()` does
  `memset(stats, 0, sizeof(struct drm_stats))` = 248 bytes
  (drm_ioctl.c:215-224, struct at uapi drm.h:262-268). With a user size ≤ 128
  that memsets 120 bytes past mapped_ioctl()'s 128-byte `ubuf.stkbuf`,
  zeroing saved registers and the return address → deterministic panic
  (proven on the guest kernel via exact-semantics replica: RIP=0).
  Reachable by any user who can open `/dev/dri/cardN` (root:video 0660,
  drm_drv.c:1178 + drmP.h:108-110 — the standard desktop session user).
* **Kernel stack disclosure (read)** — `DRM_IOCTL_GET_MAGIC` (nr 0x02, no
  auth): `drm_getmagic()` writes 4 bytes and returns 0 (drm_auth.c:58-76).
  Issued as IOC_VOID|IOC_OUT with size 1024..8191, the trailing copyout
  leaks up to ~8 KB of kernel stack (return addresses, pointers, trapframe
  data) to userspace. (The over-large copyout itself is the
  sys/kern/sys_generic.c IOC_VOID hole — related to pass-1 DF-2734 — but DRM
  is what makes it trivially reachable: any 0-returning unauthenticated DRM
  ioctl is an oracle.)
* All other `_IOWR` core handlers (drm_version, drm_getcap, drm_getclient,
  mode-*, gem-*, syncobj-*, and every driver ioctl dispatched at
  drm_ioctl.c:819-825) read/write stale stack or out-of-region heap fields
  when given a short size; on this table only `struct drm_stats` (248) and
  the heap path (size 129..247, contained by the 256-byte kmalloc bucket)
  matter for corruption — the stack path is the killer.

## Reproduce (guest has no GPU — replica proves the kernel machinery)
```
# see build.sh / run.sh; on a machine WITH /dev/dri/card0:
cc -O2 -o drm_size_confusion drm_size_confusion.c
./drm_size_confusion /dev/dri/card0 leak 2048   # kernel stack disclosure
./drm_size_confusion /dev/dri/card0 smash       # panic: RIP=0
# on the QEMU guest (no DRM device): dfioctl_rep.ko replicates the dispatch
make && kldload ./dfioctl_rep.ko
/tmp/df_primitive leak 2048 ; /tmp/df_primitive leak 8191
/tmp/df_primitive smash 8                        # panic (panic.txt)
```
Expected: leak → 27-30 kernel pointer qwords, different every run;
smash → `Fatal trap 12 ... instruction pointer = 0x8:0x0`.

## Fix
`fix.diff` — require the exact canonical command word in `drm_ioctl()`
(port-side equivalent of upstream's `cmd = ioctl->cmd`). The
sys/kern/sys_generic.c copyout hole should also be bounded (recommend:
for IOC_VOID commands never copy out more than a pointer word) — that part
belongs to a separate kern fix.
