Merge patch series "eventpoll: fix ep_remove() UAF and follow-up cleanup" Christian Brauner <brauner@kernel.org> says: ep_remove() (via __ep_remove_file()) cleared file->f_ep under file->f_lock but then kept using @file in the same critical section: is_file_epoll(), hlist_del_rcu() through the head, spin_unlock. A concurrent __fput() on the watched eventpoll caught the transient NULL in eventpoll_release()'s lockless fast path, skipped eventpoll_release_file() entirely, and ran to ep_eventpoll_release() -> ep_clear_and_put() -> ep_free(). That kfree()s the struct eventpoll whose embedded ->refs hlist_head is exactly where epi->fllink.pprev points and the subsequent hlist_del_rcu()'s "*pprev = next" scribbles into freed kmalloc-192 memory, which is the slab-use-after-free KASAN caught. struct file is SLAB_TYPESAFE_BY_RCU on top of that so the same window also lets the slot recycle while ep_remove() is still nominally inside file->f_lock. The upshot is an attacker-influencable kmem_cache_free() against the wrong slab cache. The comment on eventpoll_release()'s fast path - "False positives simply cannot happen because the file in on the way to be removed and nobody ( but eventpoll ) has still a reference to this file" - was itself the wrong invariant this race exploits. The fix pins @file via epi_fget() at the top of ep_remove() and gates the f_ep clear / hlist_del_rcu() on the pin succeeding. With the pin held __fput() cannot start which transitively keeps the watched struct eventpoll alive across the critical section and also prevents the struct file slot from recycling. Both UAFs are closed. If the pin fails __fput() is already in flight on @file. Because we bail before clearing f_ep that path takes eventpoll_release()'s slow path into eventpoll_release_file() which blocks on ep->mtx until ep_clear_and_put() drops it and then cleans up the orphaned epi. The bailed epi's share of ep->refcount stays intact so ep_clear_and_put()'s trailing ep_refcount_dec_and_test() cannot free the eventpoll out from under eventpoll_release_file(). With epi_fget() now gating every ep_remove() call the epi->dying flag becomes vestigial. epi->dying == true always coincides with file_ref_get() == false because __fput() is reachable only once the refcount hits zero and the refcount is monotone there. The last patch drops the flag and leaves a single coordination mechanism instead of two. * patches from https://patch.msgid.link/20260423-work-epoll-uaf-v1-0-2470f9eec0f5@kernel.org: eventpoll: drop vestigial epi->dying flag eventpoll: drop dead bool return from __ep_remove_epi() eventpoll: refresh eventpoll_release() fast-path comment eventpoll: move f_lock acquisition into __ep_remove_file() eventpoll: fix ep_remove struct eventpoll / struct file UAF eventpoll: move epi_fget() up eventpoll: rename ep_remove_safe() back to ep_remove() eventpoll: kill __ep_remove() eventpoll: split __ep_remove() eventpoll: use hlist_is_singular_node() in __ep_remove() Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-0-2470f9eec0f5@kernel.org Signed-off-by: Christian Brauner <brauner@kernel.org>