# DF-4569 VERDICT

## What the finding claims
`drm_ioctl()` (sys/dev/drm/drm_ioctl.c:801-874) validates NOTHING about the
user's ioctl command word except the low byte (`nr = DRM_IOCTL_NR(cmd)`,
line 809; table selection at 819-831). In particular it never compares
`cmd` with the canonical `ioctl->cmd` stored in the same table entry, so the
kernel argument buffer it hands to handlers is whatever `mapped_ioctl()`
(sys/kern/sys_generic.c) allocated from the user-controlled size/dir bits:
kmalloc(IOCPARM_LEN(cmd)) for size>128 non-VOID, a 128-byte stack buffer
otherwise, with a trailing `copyout(data, uspc_data, size)` on IOC_OUT
(sys_generic.c:729-730). Handlers assume the full struct → OOB + leak.
Upstream Linux v4.7 drm_ioctl.c has the protection: `drv_size =
_IOC_SIZE(ioctl->cmd); usize = _IOC_SIZE(cmd); asize = max(usize, drv_size);
cmd = ioctl->cmd;` plus zero-extension — verified from kernel.org (fetched
during this run, quoted in README/manifest). The DragonFly port moved the
copy to mapped_ioctl and deleted this logic. Port-introduced regression.

## Why the QEMU guest cannot run the real DRM path — and what was done instead
The guest's only display device is QEMU's std VGA (pciconf: `vgapci0
pci0:0:2:0 chip=0x11111234`); i915 does not bind, no `drm` kld is loaded,
`/dev/dri/` does not exist, and X86_64_GENERIC does not compile DRM in.
So `drm_ioctl()` itself cannot execute there. Per the audit's established
practice (DF-3715 protocol-replica module), the kernel-side machinery was
proven with a replica kld (`dfioctl_rep.c`) that reproduces the two dispatch
properties under test byte-for-byte:
  * dispatch on `cmd & 0xff` only — the group/direction/size bits of the
    user command are never examined (drm_ioctl.c:809, 847, 854);
  * handlers identical in behavior to `drm_getmagic` (4-byte write at
    offset 0, return 0 — drm_auth.c:58-76) and `drm_getstats`
    (`memset(data, 0, 248)` — drm_ioctl.c:215-224,
    sizeof(struct drm_stats)=248 on LP64: uapi drm.h:262-268).

## What was reproduced on the guest (uid=1001, /dev/dfioctl 0666)
1. **Kernel stack disclosure** — `ioctl(fd, IOC_VOID|IOC_OUT|(2048<<16)|
   ('d'<<8)|0x02, buf)` (nr 0x02 = DRM_IOCTL_GET_MAGIC): returned 0 with
   2048 bytes of kernel stack containing 30 qword kernel pointers
   (0xffff810c…, 0xfffff801…, 0xffffffff80…), varying across 3 runs
   (md5s in run.log). At size=8191 (IOCPARM_MAX) the copyout still
   returns 0 and ships ~8 KB — the read runs far past the 128-byte
   `ubuf.stkbuf`. The identical command word on a real /dev/dri/card0
   reaches drm_getmagic via drm_ioctl.c:573 (flags DRM_UNLOCKED only —
   no DRM_AUTH) and behaves the same: it writes 4 bytes, returns 0.
2. **Kernel stack buffer overflow → deterministic panic** — `ioctl(fd,
   IOC_OUT|(8<<16)|('d'<<8)|0x06, buf)` (nr 0x06 = DRM_IOCTL_GET_STATS,
   flags DRM_UNLOCKED only): handler memsets 248 bytes over the 128-byte
   stack buffer; return address zeroed; guest died with
   `Fatal trap 12: page fault while in kernel mode …
    instruction pointer = 0x8:0x0` from unpriv process 1059
   (panic.txt). Identical arithmetic on real DRM: drm_getstats memsets
   sizeof(struct drm_stats)=248 regardless of the buffer mapped_ioctl
   sized from the user's size bits.

## Exploitability assessment (the honest ceiling)
* The smash primitive is a **zero-filled 120-byte overwrite past a 128-byte
  kernel stack array** in mapped_ioctl's frame (zero content because the
  memset value is 0). Direct RIP control needs non-zero content; zeroing
  saved registers/return address gives a reliable local DoS/panic. A
  non-zero-content variant would need a handler writing attacker-influenced
  data past offset 128 — none exists in the core table (drm_stats is the
  only struct >128 bytes); driver tables (i915 etc.) were sized-checked
  (all ≤ ~72 bytes), and heap sizes 129..247 are contained by the 256-byte
  kmalloc bucket. So: unpriv → reliable kernel panic on any DRM box;
  privesc not demonstrated from this table.
* The leak primitive (via the sys_generic IOC_VOID copyout hole, exposed by
  any 0-returning unauth DRM ioctl) is a full kernel-stack disclosure —
  on this guest 27-30 kernel pointers per call — defeating any future
  KASLR and handing out live kernel pointers (pointer, heap-layout and —
  with stack grooming — potentially key/credential adjacency information).

## Status reasoning
The DRM dispatch semantics and both kernel-side primitives are proven on
the guest kernel (replica module + identical crafted commands + the exact
memset/write/return-0 handler behaviors); the DRM-specific last hop
(nr→drm_getstats/drm_getmagic through the real table) is proven statically
(drm_ioctl.c:215-224, 573, 577; drm_auth.c:58-76) but could not execute
because the guest has no DRM device. Overall: mechanisms reproduced,
DRM-path execution blocked by missing hardware → status `inconclusive`
with `reproduced=0` (strict honesty), impact ceiling `panic`
(memcorrupt family + info leak), root cause `certain`.

## Fix
`fix.diff` adds the canonical-command check to `drm_ioctl()` (equivalent of
upstream's `cmd = ioctl->cmd`). Validation: applied cleanly against the
guest's byte-identical /usr/src copy (`patch -p0` RC=0; GNU patch dry-run
RC=0 on host). Runtime before/after on real DRM hardware was not possible
on this guest (no DRM device; DRM not in X86_64_GENERIC) →
fix_status=not_testable. Recommend additionally bounding the sys_generic.c
IOC_VOID copyout (separate kern finding).

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