]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.1-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 9 Jul 2026 14:34:09 +0000 (16:34 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 9 Jul 2026 14:34:09 +0000 (16:34 +0200)
added patches:
eventpoll-don-t-decrement-ep-refcount-while-still-holding-the-ep-mutex.patch
eventpoll-drop-vestigial-__-prefix-from-ep_remove_-file-epi.patch
eventpoll-fix-ep_remove-struct-eventpoll-struct-file-uaf.patch
eventpoll-kill-__ep_remove.patch
eventpoll-move-epi_fget-up.patch
eventpoll-rename-ep_remove_safe-back-to-ep_remove.patch
eventpoll-split-__ep_remove.patch
eventpoll-use-hlist_is_singular_node-in-__ep_remove.patch
file-add-fput-cleanup-helper.patch

queue-6.1/eventpoll-don-t-decrement-ep-refcount-while-still-holding-the-ep-mutex.patch [new file with mode: 0644]
queue-6.1/eventpoll-drop-vestigial-__-prefix-from-ep_remove_-file-epi.patch [new file with mode: 0644]
queue-6.1/eventpoll-fix-ep_remove-struct-eventpoll-struct-file-uaf.patch [new file with mode: 0644]
queue-6.1/eventpoll-kill-__ep_remove.patch [new file with mode: 0644]
queue-6.1/eventpoll-move-epi_fget-up.patch [new file with mode: 0644]
queue-6.1/eventpoll-rename-ep_remove_safe-back-to-ep_remove.patch [new file with mode: 0644]
queue-6.1/eventpoll-split-__ep_remove.patch [new file with mode: 0644]
queue-6.1/eventpoll-use-hlist_is_singular_node-in-__ep_remove.patch [new file with mode: 0644]
queue-6.1/file-add-fput-cleanup-helper.patch [new file with mode: 0644]
queue-6.1/series

diff --git a/queue-6.1/eventpoll-don-t-decrement-ep-refcount-while-still-holding-the-ep-mutex.patch b/queue-6.1/eventpoll-don-t-decrement-ep-refcount-while-still-holding-the-ep-mutex.patch
new file mode 100644 (file)
index 0000000..f7e730b
--- /dev/null
@@ -0,0 +1,116 @@
+From stable+bounces-269540-greg=kroah.com@vger.kernel.org Sun Jun 28 18:22:25 2026
+From: Wentao Guan <guanwentao@uniontech.com>
+Date: Mon, 29 Jun 2026 00:19:27 +0800
+Subject: eventpoll: don't decrement ep refcount while still holding the ep mutex
+To: carnil@debian.org
+Cc: benh@debian.org, brauner@kernel.org, foss+kernel@0leil.net, gregkh@linuxfoundation.org, guanwentao@uniontech.com, sashal@kernel.org, stable@vger.kernel.org, Linus Torvalds <torvalds@linux-foundation.org>, Jann Horn <jannh@google.com>, Alexander Viro <viro@zeniv.linux.org.uk>, Jan Kara <jack@suse.cz>
+Message-ID: <20260628161933.532572-2-guanwentao@uniontech.com>
+
+From: Linus Torvalds <torvalds@linux-foundation.org>
+
+commit 8c2e52ebbe885c7eeaabd3b7ddcdc1246fc400d2 upstream.
+
+Jann Horn points out that epoll is decrementing the ep refcount and then
+doing a
+
+    mutex_unlock(&ep->mtx);
+
+afterwards. That's very wrong, because it can lead to a use-after-free.
+
+That pattern is actually fine for the very last reference, because the
+code in question will delay the actual call to "ep_free(ep)" until after
+it has unlocked the mutex.
+
+But it's wrong for the much subtler "next to last" case when somebody
+*else* may also be dropping their reference and free the ep while we're
+still using the mutex.
+
+Note that this is true even if that other user is also using the same ep
+mutex: mutexes, unlike spinlocks, can not be used for object ownership,
+even if they guarantee mutual exclusion.
+
+A mutex "unlock" operation is not atomic, and as one user is still
+accessing the mutex as part of unlocking it, another user can come in
+and get the now released mutex and free the data structure while the
+first user is still cleaning up.
+
+See our mutex documentation in Documentation/locking/mutex-design.rst,
+in particular the section [1] about semantics:
+
+       "mutex_unlock() may access the mutex structure even after it has
+        internally released the lock already - so it's not safe for
+        another context to acquire the mutex and assume that the
+        mutex_unlock() context is not using the structure anymore"
+
+So if we drop our ep ref before the mutex unlock, but we weren't the
+last one, we may then unlock the mutex, another user comes in, drops
+_their_ reference and releases the 'ep' as it now has no users - all
+while the mutex_unlock() is still accessing it.
+
+Fix this by simply moving the ep refcount dropping to outside the mutex:
+the refcount itself is atomic, and doesn't need mutex protection (that's
+the whole _point_ of refcounts: unlike mutexes, they are inherently
+about object lifetimes).
+
+Reported-by: Jann Horn <jannh@google.com>
+Link: https://docs.kernel.org/locking/mutex-design.html#semantics [1]
+Cc: Alexander Viro <viro@zeniv.linux.org.uk>
+Cc: Christian Brauner <brauner@kernel.org>
+Cc: Jan Kara <jack@suse.cz>
+Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/eventpoll.c |   12 +++++-------
+ 1 file changed, 5 insertions(+), 7 deletions(-)
+
+--- a/fs/eventpoll.c
++++ b/fs/eventpoll.c
+@@ -777,7 +777,7 @@ static bool __ep_remove(struct eventpoll
+       call_rcu(&epi->rcu, epi_rcu_free);
+       percpu_counter_dec(&ep->user->epoll_watches);
+-      return ep_refcount_dec_and_test(ep);
++      return true;
+ }
+ /*
+@@ -785,14 +785,14 @@ static bool __ep_remove(struct eventpoll
+  */
+ static void ep_remove_safe(struct eventpoll *ep, struct epitem *epi)
+ {
+-      WARN_ON_ONCE(__ep_remove(ep, epi, false));
++      if (__ep_remove(ep, epi, false))
++              WARN_ON_ONCE(ep_refcount_dec_and_test(ep));
+ }
+ static void ep_clear_and_put(struct eventpoll *ep)
+ {
+       struct rb_node *rbp, *next;
+       struct epitem *epi;
+-      bool dispose;
+       /* We need to release all tasks waiting for these file */
+       if (waitqueue_active(&ep->poll_wait))
+@@ -825,10 +825,8 @@ static void ep_clear_and_put(struct even
+               cond_resched();
+       }
+-      dispose = ep_refcount_dec_and_test(ep);
+       mutex_unlock(&ep->mtx);
+-
+-      if (dispose)
++      if (ep_refcount_dec_and_test(ep))
+               ep_free(ep);
+ }
+@@ -1008,7 +1006,7 @@ again:
+               dispose = __ep_remove(ep, epi, true);
+               mutex_unlock(&ep->mtx);
+-              if (dispose)
++              if (dispose && ep_refcount_dec_and_test(ep))
+                       ep_free(ep);
+               goto again;
+       }
diff --git a/queue-6.1/eventpoll-drop-vestigial-__-prefix-from-ep_remove_-file-epi.patch b/queue-6.1/eventpoll-drop-vestigial-__-prefix-from-ep_remove_-file-epi.patch
new file mode 100644 (file)
index 0000000..04027c5
--- /dev/null
@@ -0,0 +1,69 @@
+From stable+bounces-269544-greg=kroah.com@vger.kernel.org Sun Jun 28 18:22:40 2026
+From: Wentao Guan <guanwentao@uniontech.com>
+Date: Mon, 29 Jun 2026 00:19:37 +0800
+Subject: eventpoll: drop vestigial __ prefix from ep_remove_{file,epi}()
+To: carnil@debian.org
+Cc: benh@debian.org, brauner@kernel.org, foss+kernel@0leil.net, gregkh@linuxfoundation.org, guanwentao@uniontech.com, sashal@kernel.org, stable@vger.kernel.org, Quentin Schulz <quentin.schulz@cherry.de>
+Message-ID: <20260628161933.532572-7-guanwentao@uniontech.com>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 0feaf644f7180c4a91b6b405a881afbfd958f1cf ]
+
+With __ep_remove() gone, the double-underscore on __ep_remove_file()
+and __ep_remove_epi() no longer contrasts with a __-less parent and
+just reads as noise. Rename both to ep_remove_file() and
+ep_remove_epi(). No functional change.
+
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Stable-dep-of: a6dc643c6931 ("eventpoll: fix ep_remove struct eventpoll / struct file UAF")
+Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
+Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/eventpoll.c |   12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+--- a/fs/eventpoll.c
++++ b/fs/eventpoll.c
+@@ -719,7 +719,7 @@ static void ep_free(struct eventpoll *ep
+  * Called with &file->f_lock held,
+  * returns with it released
+  */
+-static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi,
++static void ep_remove_file(struct eventpoll *ep, struct epitem *epi,
+                            struct file *file)
+ {
+       struct epitems_head *to_free = NULL;
+@@ -743,7 +743,7 @@ static void __ep_remove_file(struct even
+       free_ephead(to_free);
+ }
+-static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi)
++static bool ep_remove_epi(struct eventpoll *ep, struct epitem *epi)
+ {
+       lockdep_assert_held(&ep->mtx);
+@@ -789,9 +789,9 @@ static void ep_remove_safe(struct eventp
+               spin_unlock(&file->f_lock);
+               return;
+       }
+-      __ep_remove_file(ep, epi, file);
++      ep_remove_file(ep, epi, file);
+-      if (__ep_remove_epi(ep, epi))
++      if (ep_remove_epi(ep, epi))
+               WARN_ON_ONCE(ep_refcount_dec_and_test(ep));
+ }
+@@ -1013,8 +1013,8 @@ again:
+               ep_unregister_pollwait(ep, epi);
+               spin_lock(&file->f_lock);
+-              __ep_remove_file(ep, epi, file);
+-              dispose = __ep_remove_epi(ep, epi);
++              ep_remove_file(ep, epi, file);
++              dispose = ep_remove_epi(ep, epi);
+               mutex_unlock(&ep->mtx);
diff --git a/queue-6.1/eventpoll-fix-ep_remove-struct-eventpoll-struct-file-uaf.patch b/queue-6.1/eventpoll-fix-ep_remove-struct-eventpoll-struct-file-uaf.patch
new file mode 100644 (file)
index 0000000..2354831
--- /dev/null
@@ -0,0 +1,99 @@
+From stable+bounces-269546-greg=kroah.com@vger.kernel.org Sun Jun 28 18:22:50 2026
+From: Wentao Guan <guanwentao@uniontech.com>
+Date: Mon, 29 Jun 2026 00:19:43 +0800
+Subject: eventpoll: fix ep_remove struct eventpoll / struct file UAF
+To: carnil@debian.org
+Cc: benh@debian.org, brauner@kernel.org, foss+kernel@0leil.net, gregkh@linuxfoundation.org, guanwentao@uniontech.com, sashal@kernel.org, stable@vger.kernel.org, Jaeyoung Chung <jjy600901@snu.ac.kr>
+Message-ID: <20260628161933.532572-10-guanwentao@uniontech.com>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit a6dc643c69311677c574a0f17a3f4d66a5f3744b ]
+
+ep_remove() (via ep_remove_file()) cleared file->f_ep under
+file->f_lock but then kept using @file inside the critical section
+(is_file_epoll(), hlist_del_rcu() through the head, spin_unlock).
+A concurrent __fput() taking the eventpoll_release() fastpath in
+that window observed the transient NULL, skipped
+eventpoll_release_file() and ran to f_op->release / file_free().
+
+For the epoll-watches-epoll case, f_op->release is
+ep_eventpoll_release() -> ep_clear_and_put() -> ep_free(), which
+kfree()s the watched struct eventpoll. Its embedded ->refs
+hlist_head is exactly where epi->fllink.pprev points, so the
+subsequent hlist_del_rcu()'s "*pprev = next" scribbles into freed
+kmalloc-192 memory.
+
+In addition, struct file is SLAB_TYPESAFE_BY_RCU, so the slot
+backing @file could be recycled by alloc_empty_file() --
+reinitializing f_lock and f_ep -- while ep_remove() is still
+nominally inside that lock. The upshot is an attacker-controllable
+kmem_cache_free() against the wrong slab cache.
+
+Pin @file via epi_fget() at the top of ep_remove() and gate the
+critical section on the pin succeeding. With the pin held @file
+cannot reach refcount zero, which holds __fput() off and
+transitively keeps the watched struct eventpoll alive across the
+hlist_del_rcu() and the f_lock use, closing both UAFs.
+
+If the pin fails @file has already reached refcount zero and its
+__fput() is in flight. Because we bailed before clearing f_ep,
+that path takes the eventpoll_release() slow path into
+eventpoll_release_file() and blocks on ep->mtx until the waiter
+side's ep_clear_and_put() drops it. The bailed epi's share of
+ep->refcount stays intact, so the trailing ep_refcount_dec_and_test()
+in ep_clear_and_put() cannot free the eventpoll out from under
+eventpoll_release_file(); the orphaned epi is then cleaned up
+there.
+
+A successful pin also proves we are not racing
+eventpoll_release_file() on this epi, so drop the now-redundant
+re-check of epi->dying under f_lock. The cheap lockless
+READ_ONCE(epi->dying) fast-path bailout stays.
+
+Fixes: 58c9b016e128 ("epoll: use refcount to reduce ep_mutex contention")
+Reported-by: Jaeyoung Chung <jjy600901@snu.ac.kr>
+Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-6-2470f9eec0f5@kernel.org
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+(cherry picked from commit a6dc643c69311677c574a0f17a3f4d66a5f3744b)
+Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/eventpoll.c |   16 ++++++++++------
+ 1 file changed, 10 insertions(+), 6 deletions(-)
+
+--- a/fs/eventpoll.c
++++ b/fs/eventpoll.c
+@@ -801,22 +801,26 @@ static bool ep_remove_epi(struct eventpo
+  */
+ static void ep_remove(struct eventpoll *ep, struct epitem *epi)
+ {
+-      struct file *file = epi->ffd.file;
++      struct file *file __free(fput) = NULL;
+       lockdep_assert_irqs_enabled();
+       lockdep_assert_held(&ep->mtx);
+       ep_unregister_pollwait(ep, epi);
+-      /* sync with eventpoll_release_file() */
++      /* cheap sync with eventpoll_release_file() */
+       if (unlikely(READ_ONCE(epi->dying)))
+               return;
+-      spin_lock(&file->f_lock);
+-      if (epi->dying) {
+-              spin_unlock(&file->f_lock);
++      /*
++       * If we manage to grab a reference it means we're not in
++       * eventpoll_release_file() and aren't going to be.
++       */
++      file = epi_fget(epi);
++      if (!file)
+               return;
+-      }
++
++      spin_lock(&file->f_lock);
+       ep_remove_file(ep, epi, file);
+       if (ep_remove_epi(ep, epi))
diff --git a/queue-6.1/eventpoll-kill-__ep_remove.patch b/queue-6.1/eventpoll-kill-__ep_remove.patch
new file mode 100644 (file)
index 0000000..97ec3d7
--- /dev/null
@@ -0,0 +1,132 @@
+From stable+bounces-269543-greg=kroah.com@vger.kernel.org Sun Jun 28 18:22:30 2026
+From: Wentao Guan <guanwentao@uniontech.com>
+Date: Mon, 29 Jun 2026 00:19:35 +0800
+Subject: eventpoll: kill __ep_remove()
+To: carnil@debian.org
+Cc: benh@debian.org, brauner@kernel.org, foss+kernel@0leil.net, gregkh@linuxfoundation.org, guanwentao@uniontech.com, sashal@kernel.org, stable@vger.kernel.org, Quentin Schulz <quentin.schulz@cherry.de>
+Message-ID: <20260628161933.532572-6-guanwentao@uniontech.com>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit e9e5cd40d7c403e19f21d0f7b8b8ba3a76b58330 ]
+
+Remove the boolean conditional in __ep_remove() and restructure the code
+so the check for racing with eventpoll_release_file() are only done in
+the ep_remove_safe() path where they belong.
+
+Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-3-2470f9eec0f5@kernel.org
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Stable-dep-of: a6dc643c6931 ("eventpoll: fix ep_remove struct eventpoll / struct file UAF")
+Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
+Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/eventpoll.c |   67 +++++++++++++++++++++++++--------------------------------
+ 1 file changed, 30 insertions(+), 37 deletions(-)
+
+--- a/fs/eventpoll.c
++++ b/fs/eventpoll.c
+@@ -715,49 +715,18 @@ static void ep_free(struct eventpoll *ep
+       kfree_rcu(ep, rcu);
+ }
+-static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, struct file *file);
+-static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi);
+-
+-/*
+- * Removes a "struct epitem" from the eventpoll RB tree and deallocates
+- * all the associated resources. Must be called with "mtx" held.
+- * If the dying flag is set, do the removal only if force is true.
+- * This prevents ep_clear_and_put() from dropping all the ep references
+- * while running concurrently with eventpoll_release_file().
+- * Returns true if the eventpoll can be disposed.
+- */
+-static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force)
+-{
+-      struct file *file = epi->ffd.file;
+-
+-      lockdep_assert_irqs_enabled();
+-
+-      /*
+-       * Removes poll wait queue hooks.
+-       */
+-      ep_unregister_pollwait(ep, epi);
+-
+-      /* Remove the current item from the list of epoll hooks */
+-      spin_lock(&file->f_lock);
+-      if (epi->dying && !force) {
+-              spin_unlock(&file->f_lock);
+-              return false;
+-      }
+-
+-      __ep_remove_file(ep, epi, file);
+-      return __ep_remove_epi(ep, epi);
+-}
+-
+ /*
+  * Called with &file->f_lock held,
+  * returns with it released
+  */
+-static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, struct file *file)
++static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi,
++                           struct file *file)
+ {
+       struct epitems_head *to_free = NULL;
+       struct hlist_head *head = file->f_ep;
+       lockdep_assert_held(&ep->mtx);
++      lockdep_assert_held(&file->f_lock);
+       if (hlist_is_singular_node(&epi->fllink, head)) {
+               /* See eventpoll_release() for details. */
+@@ -804,7 +773,25 @@ static bool __ep_remove_epi(struct event
+  */
+ static void ep_remove_safe(struct eventpoll *ep, struct epitem *epi)
+ {
+-      if (__ep_remove(ep, epi, false))
++      struct file *file = epi->ffd.file;
++
++      lockdep_assert_irqs_enabled();
++      lockdep_assert_held(&ep->mtx);
++
++      ep_unregister_pollwait(ep, epi);
++
++      /* sync with eventpoll_release_file() */
++      if (unlikely(READ_ONCE(epi->dying)))
++              return;
++
++      spin_lock(&file->f_lock);
++      if (epi->dying) {
++              spin_unlock(&file->f_lock);
++              return;
++      }
++      __ep_remove_file(ep, epi, file);
++
++      if (__ep_remove_epi(ep, epi))
+               WARN_ON_ONCE(ep_refcount_dec_and_test(ep));
+ }
+@@ -1013,7 +1000,7 @@ again:
+       spin_lock(&file->f_lock);
+       if (file->f_ep && file->f_ep->first) {
+               epi = hlist_entry(file->f_ep->first, struct epitem, fllink);
+-              epi->dying = true;
++              WRITE_ONCE(epi->dying, true);
+               spin_unlock(&file->f_lock);
+               /*
+@@ -1022,7 +1009,13 @@ again:
+                */
+               ep = epi->ep;
+               mutex_lock(&ep->mtx);
+-              dispose = __ep_remove(ep, epi, true);
++
++              ep_unregister_pollwait(ep, epi);
++
++              spin_lock(&file->f_lock);
++              __ep_remove_file(ep, epi, file);
++              dispose = __ep_remove_epi(ep, epi);
++
+               mutex_unlock(&ep->mtx);
+               if (dispose && ep_refcount_dec_and_test(ep))
diff --git a/queue-6.1/eventpoll-move-epi_fget-up.patch b/queue-6.1/eventpoll-move-epi_fget-up.patch
new file mode 100644 (file)
index 0000000..ab98de1
--- /dev/null
@@ -0,0 +1,100 @@
+From stable+bounces-269545-greg=kroah.com@vger.kernel.org Sun Jun 28 18:22:38 2026
+From: Wentao Guan <guanwentao@uniontech.com>
+Date: Mon, 29 Jun 2026 00:19:41 +0800
+Subject: eventpoll: move epi_fget() up
+To: carnil@debian.org
+Cc: benh@debian.org, brauner@kernel.org, foss+kernel@0leil.net, gregkh@linuxfoundation.org, guanwentao@uniontech.com, sashal@kernel.org, stable@vger.kernel.org, Quentin Schulz <quentin.schulz@cherry.de>
+Message-ID: <20260628161933.532572-9-guanwentao@uniontech.com>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 86e87059e6d1fd5115a31949726450ed03c1073b ]
+
+We'll need it when removing files so move it up. No functional change.
+
+Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-5-2470f9eec0f5@kernel.org
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Stable-dep-of: a6dc643c6931 ("eventpoll: fix ep_remove struct eventpoll / struct file UAF")
+[file_ref_get(&file->f_ref) from original commit left as
+ atomic_long_inc_not_zero(&file->f_count) due to v6.12.y missing commit
+ 90ee6ed776c0 ("fs: port files to file_ref") and its dependent commit
+ 08ef26ea9ab3 ("fs: add file_ref")]
+Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
+Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/eventpoll.c |   56 ++++++++++++++++++++++++++++----------------------------
+ 1 file changed, 28 insertions(+), 28 deletions(-)
+
+--- a/fs/eventpoll.c
++++ b/fs/eventpoll.c
+@@ -716,6 +716,34 @@ static void ep_free(struct eventpoll *ep
+ }
+ /*
++ * The ffd.file pointer may be in the process of being torn down due to
++ * being closed, but we may not have finished eventpoll_release() yet.
++ *
++ * Normally, even with the atomic_long_inc_not_zero, the file may have
++ * been free'd and then gotten re-allocated to something else (since
++ * files are not RCU-delayed, they are SLAB_TYPESAFE_BY_RCU).
++ *
++ * But for epoll, users hold the ep->mtx mutex, and as such any file in
++ * the process of being free'd will block in eventpoll_release_file()
++ * and thus the underlying file allocation will not be free'd, and the
++ * file re-use cannot happen.
++ *
++ * For the same reason we can avoid a rcu_read_lock() around the
++ * operation - 'ffd.file' cannot go away even if the refcount has
++ * reached zero (but we must still not call out to ->poll() functions
++ * etc).
++ */
++static struct file *epi_fget(const struct epitem *epi)
++{
++      struct file *file;
++
++      file = epi->ffd.file;
++      if (!atomic_long_inc_not_zero(&file->f_count))
++              file = NULL;
++      return file;
++}
++
++/*
+  * Called with &file->f_lock held,
+  * returns with it released
+  */
+@@ -887,34 +915,6 @@ static __poll_t __ep_eventpoll_poll(stru
+ }
+ /*
+- * The ffd.file pointer may be in the process of being torn down due to
+- * being closed, but we may not have finished eventpoll_release() yet.
+- *
+- * Normally, even with the atomic_long_inc_not_zero, the file may have
+- * been free'd and then gotten re-allocated to something else (since
+- * files are not RCU-delayed, they are SLAB_TYPESAFE_BY_RCU).
+- *
+- * But for epoll, users hold the ep->mtx mutex, and as such any file in
+- * the process of being free'd will block in eventpoll_release_file()
+- * and thus the underlying file allocation will not be free'd, and the
+- * file re-use cannot happen.
+- *
+- * For the same reason we can avoid a rcu_read_lock() around the
+- * operation - 'ffd.file' cannot go away even if the refcount has
+- * reached zero (but we must still not call out to ->poll() functions
+- * etc).
+- */
+-static struct file *epi_fget(const struct epitem *epi)
+-{
+-      struct file *file;
+-
+-      file = epi->ffd.file;
+-      if (!atomic_long_inc_not_zero(&file->f_count))
+-              file = NULL;
+-      return file;
+-}
+-
+-/*
+  * Differs from ep_eventpoll_poll() in that internal callers already have
+  * the ep->mtx so we need to start from depth=1, such that mutex_lock_nested()
+  * is correctly annotated.
diff --git a/queue-6.1/eventpoll-rename-ep_remove_safe-back-to-ep_remove.patch b/queue-6.1/eventpoll-rename-ep_remove_safe-back-to-ep_remove.patch
new file mode 100644 (file)
index 0000000..b72f973
--- /dev/null
@@ -0,0 +1,96 @@
+From stable+bounces-269542-greg=kroah.com@vger.kernel.org Sun Jun 28 18:22:31 2026
+From: Wentao Guan <guanwentao@uniontech.com>
+Date: Mon, 29 Jun 2026 00:19:39 +0800
+Subject: eventpoll: rename ep_remove_safe() back to ep_remove()
+To: carnil@debian.org
+Cc: benh@debian.org, brauner@kernel.org, foss+kernel@0leil.net, gregkh@linuxfoundation.org, guanwentao@uniontech.com, sashal@kernel.org, stable@vger.kernel.org, Quentin Schulz <quentin.schulz@cherry.de>
+Message-ID: <20260628161933.532572-8-guanwentao@uniontech.com>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 0bade234723e40e4937be912e105785d6a51464e ]
+
+The current name is just confusing and doesn't clarify anything.
+
+Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-4-2470f9eec0f5@kernel.org
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Stable-dep-of: a6dc643c6931 ("eventpoll: fix ep_remove struct eventpoll / struct file UAF")
+Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
+Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/eventpoll.c |   16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+--- a/fs/eventpoll.c
++++ b/fs/eventpoll.c
+@@ -771,7 +771,7 @@ static bool ep_remove_epi(struct eventpo
+ /*
+  * ep_remove variant for callers owing an additional reference to the ep
+  */
+-static void ep_remove_safe(struct eventpoll *ep, struct epitem *epi)
++static void ep_remove(struct eventpoll *ep, struct epitem *epi)
+ {
+       struct file *file = epi->ffd.file;
+@@ -818,7 +818,7 @@ static void ep_clear_and_put(struct even
+       /*
+        * Walks through the whole tree and try to free each "struct epitem".
+-       * Note that ep_remove_safe() will not remove the epitem in case of a
++       * Note that ep_remove() will not remove the epitem in case of a
+        * racing eventpoll_release_file(); the latter will do the removal.
+        * At this point we are sure no poll callbacks will be lingering around.
+        * Since we still own a reference to the eventpoll struct, the loop can't
+@@ -827,7 +827,7 @@ static void ep_clear_and_put(struct even
+       for (rbp = rb_first_cached(&ep->rbr); rbp; rbp = next) {
+               next = rb_next(rbp);
+               epi = rb_entry(rbp, struct epitem, rbn);
+-              ep_remove_safe(ep, epi);
++              ep_remove(ep, epi);
+               cond_resched();
+       }
+@@ -1505,21 +1505,21 @@ static int ep_insert(struct eventpoll *e
+               mutex_unlock(&tep->mtx);
+       /*
+-       * ep_remove_safe() calls in the later error paths can't lead to
++       * ep_remove() calls in the later error paths can't lead to
+        * ep_free() as the ep file itself still holds an ep reference.
+        */
+       ep_get(ep);
+       /* now check if we've created too many backpaths */
+       if (unlikely(full_check && reverse_path_check())) {
+-              ep_remove_safe(ep, epi);
++              ep_remove(ep, epi);
+               return -EINVAL;
+       }
+       if (epi->event.events & EPOLLWAKEUP) {
+               error = ep_create_wakeup_source(epi);
+               if (error) {
+-                      ep_remove_safe(ep, epi);
++                      ep_remove(ep, epi);
+                       return error;
+               }
+       }
+@@ -1543,7 +1543,7 @@ static int ep_insert(struct eventpoll *e
+        * high memory pressure.
+        */
+       if (unlikely(!epq.epi)) {
+-              ep_remove_safe(ep, epi);
++              ep_remove(ep, epi);
+               return -ENOMEM;
+       }
+@@ -2222,7 +2222,7 @@ int do_epoll_ctl(int epfd, int op, int f
+                        * The eventpoll itself is still alive: the refcount
+                        * can't go to zero here.
+                        */
+-                      ep_remove_safe(ep, epi);
++                      ep_remove(ep, epi);
+                       error = 0;
+               } else {
+                       error = -ENOENT;
diff --git a/queue-6.1/eventpoll-split-__ep_remove.patch b/queue-6.1/eventpoll-split-__ep_remove.patch
new file mode 100644 (file)
index 0000000..877a620
--- /dev/null
@@ -0,0 +1,82 @@
+From stable+bounces-269541-greg=kroah.com@vger.kernel.org Sun Jun 28 18:22:27 2026
+From: Wentao Guan <guanwentao@uniontech.com>
+Date: Mon, 29 Jun 2026 00:19:33 +0800
+Subject: eventpoll: split __ep_remove()
+To: carnil@debian.org
+Cc: benh@debian.org, brauner@kernel.org, foss+kernel@0leil.net, gregkh@linuxfoundation.org, guanwentao@uniontech.com, sashal@kernel.org, stable@vger.kernel.org, Linus Torvalds <torvalds@linux-foundation.org>, Quentin Schulz <quentin.schulz@cherry.de>
+Message-ID: <20260628161933.532572-5-guanwentao@uniontech.com>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 0f7bdfd413000985de09fc39eb9efa1e091a3ce0 ]
+
+Split __ep_remove() to delineate file removal from epoll item removal.
+
+Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
+Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-2-2470f9eec0f5@kernel.org
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Stable-dep-of: a6dc643c6931 ("eventpoll: fix ep_remove struct eventpoll / struct file UAF")
+Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
+Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/eventpoll.c |   27 +++++++++++++++++++++++----
+ 1 file changed, 23 insertions(+), 4 deletions(-)
+
+--- a/fs/eventpoll.c
++++ b/fs/eventpoll.c
+@@ -715,6 +715,9 @@ static void ep_free(struct eventpoll *ep
+       kfree_rcu(ep, rcu);
+ }
++static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, struct file *file);
++static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi);
++
+ /*
+  * Removes a "struct epitem" from the eventpoll RB tree and deallocates
+  * all the associated resources. Must be called with "mtx" held.
+@@ -726,8 +729,6 @@ static void ep_free(struct eventpoll *ep
+ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force)
+ {
+       struct file *file = epi->ffd.file;
+-      struct epitems_head *to_free;
+-      struct hlist_head *head;
+       lockdep_assert_irqs_enabled();
+@@ -743,8 +744,21 @@ static bool __ep_remove(struct eventpoll
+               return false;
+       }
+-      to_free = NULL;
+-      head = file->f_ep;
++      __ep_remove_file(ep, epi, file);
++      return __ep_remove_epi(ep, epi);
++}
++
++/*
++ * Called with &file->f_lock held,
++ * returns with it released
++ */
++static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, struct file *file)
++{
++      struct epitems_head *to_free = NULL;
++      struct hlist_head *head = file->f_ep;
++
++      lockdep_assert_held(&ep->mtx);
++
+       if (hlist_is_singular_node(&epi->fllink, head)) {
+               /* See eventpoll_release() for details. */
+               WRITE_ONCE(file->f_ep, NULL);
+@@ -758,6 +772,11 @@ static bool __ep_remove(struct eventpoll
+       hlist_del_rcu(&epi->fllink);
+       spin_unlock(&file->f_lock);
+       free_ephead(to_free);
++}
++
++static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi)
++{
++      lockdep_assert_held(&ep->mtx);
+       rb_erase_cached(&epi->rbn, &ep->rbr);
diff --git a/queue-6.1/eventpoll-use-hlist_is_singular_node-in-__ep_remove.patch b/queue-6.1/eventpoll-use-hlist_is_singular_node-in-__ep_remove.patch
new file mode 100644 (file)
index 0000000..5063937
--- /dev/null
@@ -0,0 +1,37 @@
+From stable+bounces-269538-greg=kroah.com@vger.kernel.org Sun Jun 28 18:22:11 2026
+From: Wentao Guan <guanwentao@uniontech.com>
+Date: Mon, 29 Jun 2026 00:19:31 +0800
+Subject: eventpoll: use hlist_is_singular_node() in __ep_remove()
+To: carnil@debian.org
+Cc: benh@debian.org, brauner@kernel.org, foss+kernel@0leil.net, gregkh@linuxfoundation.org, guanwentao@uniontech.com, sashal@kernel.org, stable@vger.kernel.org, Quentin Schulz <quentin.schulz@cherry.de>
+Message-ID: <20260628161933.532572-4-guanwentao@uniontech.com>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 3d9fd0abc94d8cd430cc7cd7d37ce5e5aae2cd2b ]
+
+Replace the open-coded "epi is the only entry in file->f_ep" check
+with hlist_is_singular_node(). Same semantics, and the helper avoids
+the head-cacheline access in the common false case.
+
+Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-1-2470f9eec0f5@kernel.org
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Stable-dep-of: a6dc643c6931 ("eventpoll: fix ep_remove struct eventpoll / struct file UAF")
+Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
+Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/eventpoll.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/fs/eventpoll.c
++++ b/fs/eventpoll.c
+@@ -745,7 +745,7 @@ static bool __ep_remove(struct eventpoll
+       to_free = NULL;
+       head = file->f_ep;
+-      if (head->first == &epi->fllink && !epi->fllink.next) {
++      if (hlist_is_singular_node(&epi->fllink, head)) {
+               /* See eventpoll_release() for details. */
+               WRITE_ONCE(file->f_ep, NULL);
+               if (!is_file_epoll(file)) {
diff --git a/queue-6.1/file-add-fput-cleanup-helper.patch b/queue-6.1/file-add-fput-cleanup-helper.patch
new file mode 100644 (file)
index 0000000..2474d14
--- /dev/null
@@ -0,0 +1,42 @@
+From stable+bounces-269539-greg=kroah.com@vger.kernel.org Sun Jun 28 18:22:11 2026
+From: Wentao Guan <guanwentao@uniontech.com>
+Date: Mon, 29 Jun 2026 00:19:29 +0800
+Subject: file: add fput() cleanup helper
+To: carnil@debian.org
+Cc: benh@debian.org, brauner@kernel.org, foss+kernel@0leil.net, gregkh@linuxfoundation.org, guanwentao@uniontech.com, sashal@kernel.org, stable@vger.kernel.org, Josef Bacik <josef@toxicpanda.com>, Jeff Layton <jlayton@kernel.org>
+Message-ID: <20260628161933.532572-3-guanwentao@uniontech.com>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 257b1c2c78c25643526609dee0c15f1544eb3252 ]
+
+Add a simple helper to put a file reference.
+
+Link: https://lore.kernel.org/r/20240719-work-mount-namespace-v1-4-834113cab0d2@kernel.org
+Reviewed-by: Josef Bacik <josef@toxicpanda.com>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/file.h |    2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/include/linux/file.h
++++ b/include/linux/file.h
+@@ -11,6 +11,7 @@
+ #include <linux/posix_types.h>
+ #include <linux/errno.h>
+ #include <linux/cleanup.h>
++#include <linux/err.h>
+ struct file;
+@@ -93,6 +94,7 @@ extern void put_unused_fd(unsigned int f
+ DEFINE_CLASS(get_unused_fd, int, if (_T >= 0) put_unused_fd(_T),
+            get_unused_fd_flags(flags), unsigned flags)
++DEFINE_FREE(fput, struct file *, if (!IS_ERR_OR_NULL(_T)) fput(_T))
+ extern void fd_install(unsigned int fd, struct file *file);
index 4bd54a042a5e88210844beb81c88795659639dc2..ac66580f6b76299c9b909aa5145fee7f585cd862 100644 (file)
@@ -35,3 +35,12 @@ nfsd-change-nfs4_client_to_reclaim-to-allocate-data.patch
 mm-vmscan-flush-deferred-tlb-before-freeing-large-fo.patch
 bluetooth-iso-copy-base-if-service-data-matches-eir_.patch
 perf-trace-beauty-fcntl-fix-build-with-older-kernel-headers.patch
+eventpoll-don-t-decrement-ep-refcount-while-still-holding-the-ep-mutex.patch
+file-add-fput-cleanup-helper.patch
+eventpoll-use-hlist_is_singular_node-in-__ep_remove.patch
+eventpoll-split-__ep_remove.patch
+eventpoll-kill-__ep_remove.patch
+eventpoll-drop-vestigial-__-prefix-from-ep_remove_-file-epi.patch
+eventpoll-rename-ep_remove_safe-back-to-ep_remove.patch
+eventpoll-move-epi_fget-up.patch
+eventpoll-fix-ep_remove-struct-eventpoll-struct-file-uaf.patch