# DF-4362 — DRM atomic-ioctl out-fence path leaks struct files against unimplemented fd/sync_file shims

## Verdict summary

* **Status: `untested`** (runtime verification hardware-gated: the QEMU audit
  guest has no DRM device, and the only `DRIVER_ATOMIC` drivers in this tree —
  i915 and amdgpu — need real Intel/AMD graphics hardware that QEMU cannot
  emulate). Source-level certainty is **certain**: every link of the chain was
  verified in-tree, and the trigger builds cleanly on the guest.
* **Claimed impact (on i915/amdgpu machines): local DoS + permanent kernel
  memory leak**, reachable by an unprivileged local user who opens the card
  before the display server does (auto-master), and by any running display
  server (DRM master).

## Root cause (path:line)

`sys/dev/drm/drm_atomic_uapi.c` — `prepare_signaling()` CRTC out-fence block:

1. `drm_atomic_crtc_set_property()` (sys/dev/drm/drm_atomic_uapi.c:460-469)
   accepts `OUT_FENCE_PTR` values (a `DRM_MODE_PROP_ATOMIC` range property
   `0..U64_MAX`, created unconditionally in
   sys/dev/drm/drm_mode_config.c:288-292), writes `-1` to the user pointer and
   stores it in the CRTC state.
2. `drm_mode_atomic_ioctl()` (:1237) — any DRM-master client with
   `file_priv->atomic` (set via unprivileged `DRM_IOCTL_SET_CLIENT_CAP`,
   sys/dev/drm/drm_ioctl.c:327-337) — calls `prepare_signaling()` (:1360).
3. `prepare_signaling()` (:1108-1133) sees the stored fence pointer, then
   `setup_out_fence()` (:1045-1060):
   * `get_unused_fd_flags()` — **REAL allocation** in this port
     (sys/dev/drm/include/linux/file.h:38-50): `falloc()` allocates a
     `struct file` (`f_count=1`, on the global file list, `nfiles++`) and
     reserves an fd number. The `struct file *` is discarded by the shim.
   * `put_user(fd, out_fence_ptr)` (:1052) — publishes the fd number to
     userspace. The fd is **never installed**: `fd_install()` is a no-op
     kprintf stub (file.h:52-56).
   * `sync_file_create()` (:1055) — stub that kprintf's and returns `NULL`
     (sys/dev/drm/include/linux/sync_file.h:48-53).
   * → `setup_out_fence` returns `-ENOMEM`.
4. Failure cleanup `complete_signaling(install_fds=false)` (:1222-1232) calls
   `fput()` and `put_unused_fd()` — **both no-op kprintf stubs**
   (file.h:58-68). The `struct file` allocated in step 3 is **never dropped**:
   `nfiles` is permanently incremented and ~`sizeof(struct file)` of kernel
   memory is leaked per call. The fd number handed to userspace refers to
   nothing.

## Consequences

* **Permanent struct-file leak, system-wide**: `falloc()` refuses to allocate
  once `nfiles >= kern.maxfiles` (sys/kern/kern_descrip.c, falloc: "kern.maxfiles
  limit exceeded"). Each out-fence attempt leaks one file; a trivial loop
  (`while(1) ioctl(MODE_ATOMIC, {OUT_FENCE_PTR})`) burns the global file table
  in seconds (default `kern.maxfiles = 130112`). Afterwards **no process on the
  system can open() / socket() / pipe()** (ENFILE) until reboot — logins, sshd,
  cron, everything. The loop does not even need to stop: `get_unused_fd_flags`
  kprintf's *before* calling falloc, so console/log spam continues forever.
* **Console/log spam from unprivileged context**: two kprintf lines per call
  ("get_unused_fd_flags: is incomplete", "sync_file_create(): not implemented")
  → unbounded dmesg/serial flooding.
* **Dangling fd published to userspace**: the number written via `put_user`
  has no installed file (fdalloc only reserved the index).

## Reachability / threat model

* Requires a `DRIVER_ATOMIC` device: only i915 and amdgpu in this tree
  (sys/dev/drm/i915/i915_drv.c:3283, sys/dev/drm/amd/amdgpu/amdgpu_drv.c:1302).
  Standard desktop hardware; absent on servers without Intel/AMD graphics.
* `DRM_IOCTL_MODE_ATOMIC` is gated `DRM_MASTER|DRM_UNLOCKED`
  (sys/dev/drm/drm_ioctl.c:681). `DRM_IOCTL_SET_MASTER` is `DRM_ROOT_ONLY`
  (drm_ioctl.c:579 area), **but** `drm_master_open()`
  (sys/dev/drm/drm_auth.c:261-274) makes the *first opener* of the primary
  node master automatically, with no privilege check. An unprivileged local
  user racing the display server to `open("/dev/dri/card0")` (before it
  starts, or after it crashes) becomes master. A running X/wayland server
  (or a compromised renderer inside it) is master as-is.
* `DRM_IOCTL_SET_CLIENT_CAP(ATOMIC)` needs no privilege (drm_ioctl.c:312-337).
* Not a remote bug; local-only, hardware-gated.

## Verification run (QEMU audit guest — hardware-gated)

* `trigger.c` built cleanly on the guest (`cc -Wall -Wextra -O2`) — see
  `build.log`.
* Run result: `open /dev/dri/card0: No such file or directory` → exit 1
  (`run.log`). Guest has no `/dev/dri`; the tree's atomic drivers cannot bind
  to emulated QEMU hardware (no virtio-gpu/vkms in tree; radeon is not
  DRIVER_ATOMIC). This matches the repo's precedent for hardware-gated
  findings (e.g. DF-4160 xe_mii): source-level certainty, runtime untested.
* On affected hardware the success criterion is:
  `sysctl kern.openfiles` climbs by 1 per iteration and never decreases (even
  after the process exits and fds are "closed"), console shows the two stub
  kprintf lines per iteration, ioctl returns ENOMEM.

## Distinctness from knowns

* DF-1555 = leaked `drm_pending_vblank_event` on a `complete_signaling` error
  path. This finding is the **`struct file` lifecycle** (falloc vs stubbed
  fd_install/fput/put_unused_fd) — a different object, different lines
  (:1048/:1052/:1055 vs the event handling), different fix.
* The same stubs also make `amdgpu_cs.c:1457`, `drm_syncobj.c:521` and
  `i915_gem_execbuffer.c:2403` leak on their out-fence paths; same root cause,
  noted here, fix belongs in the shims / this file's gating.

## Recommended fix

Minimal security fix (see `fix.diff`): refuse non-NULL `OUT_FENCE_PTR` /
`WRITEBACK_OUT_FENCE_PTR` values with `-EOPNOTSUPP` in
`drm_atomic_crtc_set_property()` / `drm_atomic_connector_set_property()` until
`sync_file` + the fd shims are actually implemented. The complete fix is to
implement `sync_file_create`/`sync_file_get_fence` and
`fd_install`/`fput`/`put_unused_fd` (requires threading the `struct file *`
from `falloc` through `get_unused_fd_flags`, e.g. storing it in
`struct drm_out_fence_state`).

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