Atomic-ioctl out-fence path leaks a struct file per commit against the port's stubbed fd/sync_file shims: get_unused_fd_flags() really allocates while fd_install/fput/put_unused_fd are no-op stubs and sync_file_create() returns NULL β permanent nfiles exhaustion (system-wide ENFILE DoS until reboot)
Summary
SYSTEMIC compat-layer finding #6: on any DRIVER_ATOMIC device (i915/amdgpu), a DRM-master client issuing MODE_ATOMIC with an OUT_FENCE_PTR drives setup_out_fence, which calls get_unused_fd_flags - a REAL falloc in this port (struct file on the global list, nfiles++) - then publishes the fd number, hits the NULL-returning sync_file_create stub, and the error path's fput/put_unused_fd are no-op kprintf stubs. The struct file is never dropped: each attempt permanently leaks one file + 2 console lines. A trivial loop exhausts the global file table (~130k) after which every process system-wide fails open/socket/pipe with ENFILE until reboot. Master is auto-granted to the first card0 opener with NO privilege check. Same stubs also leak on the amdgpu_cs, drm_syncobj, and i915 execbuf out-fence paths. Refuse-out-fence fix until the shims are implemented. PoC builds on the guest; no DRM device.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4362 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | β | 8.0 KB | view raw | |
| build.sh | β | 123 B | view raw | |
| run.sh | β | 147 B | view raw | |
| build.log | β | 284 B | view raw | |
| run.log | β | 134 B | view raw | |
| env.txt | β | 233 B | view raw | |
| VERDICT.md | β | 6.5 KB | β raw | |
| fix.diff | β | 2.0 KB | view raw |
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 onlyDRIVER_ATOMICdrivers 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:
drm_atomic_crtc_set_property()(sys/dev/drm/drm_atomic_uapi.c:460-469) acceptsOUT_FENCE_PTRvalues (aDRM_MODE_PROP_ATOMICrange property0..U64_MAX, created unconditionally in sys/dev/drm/drm_mode_config.c:288-292), writes-1to the user pointer and stores it in the CRTC state.drm_mode_atomic_ioctl()(:1237) β any DRM-master client withfile_priv->atomic(set via unprivilegedDRM_IOCTL_SET_CLIENT_CAP, sys/dev/drm/drm_ioctl.c:327-337) β callsprepare_signaling()(:1360).prepare_signaling()(:1108-1133) sees the stored fence pointer, thensetup_out_fence()(:1045-1060): *get_unused_fd_flags()β REAL allocation in this port (sys/dev/drm/include/linux/file.h:38-50):falloc()allocates astruct file(f_count=1, on the global file list,nfiles++) and reserves an fd number. Thestruct 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 returnsNULL(sys/dev/drm/include/linux/sync_file.h:48-53). * βsetup_out_fencereturns-ENOMEM.- Failure cleanup
complete_signaling(install_fds=false)(:1222-1232) callsfput()andput_unused_fd()β both no-op kprintf stubs (file.h:58-68). Thestruct fileallocated in step 3 is never dropped:nfilesis 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 oncenfiles >= 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 (defaultkern.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_flagskprintf'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_userhas no installed file (fdalloc only reserved the index).
Reachability / threat model
- Requires a
DRIVER_ATOMICdevice: 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_ATOMICis gatedDRM_MASTER|DRM_UNLOCKED(sys/dev/drm/drm_ioctl.c:681).DRM_IOCTL_SET_MASTERisDRM_ROOT_ONLY(drm_ioctl.c:579 area), butdrm_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 toopen("/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.cbuilt cleanly on the guest (cc -Wall -Wextra -O2) β seebuild.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.openfilesclimbs 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_eventon acomplete_signalingerror path. This finding is thestruct filelifecycle (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:521andi915_gem_execbuffer.c:2403leak 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.
Fix verification
not_testableFix validation impossible on this guest: the trigger is hardware-gated (no DRM device), so neither the baseline bad behavior nor the patched behavior can be exercised. fix.diff is a source-level refusal patch (out-fence pointer values return -EOPNOTSUPP before any allocation), which makes the leak path unreachable by construction.
findings/poc/DF-4362/fix.diff
Confirmed kernel references
- sys/dev/drm/drm_atomic_uapi.c:1048
- sys/dev/drm/drm_atomic_uapi.c:1052
- sys/dev/drm/drm_atomic_uapi.c:1055
- sys/dev/drm/drm_atomic_uapi.c:1222
- sys/dev/drm/include/linux/file.h:38
- sys/dev/drm/include/linux/file.h:52
- sys/dev/drm/include/linux/file.h:58
- sys/dev/drm/include/linux/file.h:64
- sys/dev/drm/include/linux/sync_file.h:48
- sys/dev/drm/drm_ioctl.c:681
- sys/dev/drm/drm_auth.c:261
- sys/dev/drm/drm_mode_config.c:288
Detail
Evidence (decisive lines)
trigger.c (self-contained PoC; builds on guest per build.log); run.log shows the honest hardware-gated outcome (open /dev/dri/card0: No such file or directory); env.txt (guest uname, /dev/dri absent, kern.maxfiles=130112); VERDICT.md contains the full path:line chain and impact analysis
PoC changes
Wrote the PoC from scratch (no seed existed): fully self-contained uapi structs/ioctl numbers verified against sys/dev/drm/include/uapi/drm/{drm,drm_mode}.h (SET_CLIENT_CAP=0x0d, OBJ_GETPROPERTIES=0xB9, MODE_ATOMIC=0xBC, drm_mode_atomic.reserved precedes user_data); prop discovery is dynamic via OBJ_GETPROPERTIES + per-prop atomic probe since the ioctl returns no prop names.
Verified recommended fix
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 and the fd shims are implemented; the complete fix is implementing sync_file_create/sync_file_get_fence plus fd_install/fput/put_unused_fd (threading the falloc'd struct file * through get_unused_fd_flags).
Verdict
Hardware-gated: the QEMU audit guest has no DRM device (/dev/dri absent; the tree's only DRIVER_ATOMIC drivers, i915 and amdgpu, need real Intel/AMD graphics hardware QEMU cannot emulate), so the trigger could not run past open(). Source-level certainty is high: every link was verified in-tree. setup_out_fence() (drm_atomic_uapi.c:1045-1060) calls get_unused_fd_flags() which really allocates a struct file via falloc() (include/linux/file.h:38-50, nfiles++, f_count=1, on the global file list) and reserves an fd; put_user() publishes the never-installed fd number; sync_file_create() is a stub returning NULL (include/linux/sync_file.h:48-53); the failure path's fput()/put_unused_fd() are no-op stubs (include/linux/file.h:52-68), so the struct file is leaked permanently. One leaked file per DRM_IOCTL_MODE_ATOMIC commit carrying an OUT_FENCE_PTR value; a loop burns the global file table (default kern.maxfiles=130112) until every process system-wide gets ENFILE until reboot, plus two unbounded console kprintf lines per call from the stubs. Reachable by a local unprivileged user who is the first opener of /dev/dri/card0 (drm_master_open auto-master, drm_auth.c:261-274) satisfying the DRM_MASTER gate on the atomic ioctl (drm_ioctl.c:681), with the unprivileged SET_CLIENT_CAP(ATOMIC) (drm_ioctl.c:327).
No comments yet.