From 2042c1b1052885328202e855a78255558661b900 Mon Sep 17 00:00:00 2001 From: Andrew Lukoshko Date: Wed, 8 Jul 2026 15:20:58 +0000 Subject: [PATCH] Recreate RHEL 5.14.0-687.23.1 from CS9/CS10/upstream backports - Drop AlmaLinux ahead-of-RHEL eventpoll CVE-2026-46242 fix (1712), superseded by the RHEL eventpoll series in 687.23.1 - Temporarily drop the rtmutex remove_waiter() fixes (1736, 1738) - Add RHEL 687.23.1 backports recreated from CS10/upstream (1739-1755) --- ...ove-struct-eventpoll-struct-file-uaf.patch | 99 ------------- ...tex-use-waiter-task-in-remove-waiter.patch | 90 ------------ ...skip-remove-waiter-when-not-enqueued.patch | 77 ---------- ...3-fix-race-for-querying-speed-duplex.patch | 35 +++++ ...e-make-ebt-snat-arp-rewrite-writable.patch | 53 +++++++ SOURCES/1741-epoll-annotate-racy-check.patch | 66 +++++++++ ...t-eventpoll-free-to-rcu-grace-period.patch | 53 +++++++ ...-hlist-is-singular-node-in-ep-remove.patch | 39 +++++ SOURCES/1744-eventpoll-split-ep-remove.patch | 84 +++++++++++ SOURCES/1745-eventpoll-kill-ep-remove.patch | 134 ++++++++++++++++++ ...ame-ep-remove-safe-back-to-ep-remove.patch | 98 +++++++++++++ SOURCES/1747-eventpoll-move-epi-fget-up.patch | 95 +++++++++++++ ...igial-prefix-from-ep-remove-file-epi.patch | 78 ++++++++++ ...ove-struct-eventpoll-struct-file-uaf.patch | 104 ++++++++++++++ ...lock-acquisition-into-ep-remove-file.patch | 70 +++++++++ ...-eventpoll-release-fast-path-comment.patch | 66 +++++++++ ...-dead-bool-return-from-ep-remove-epi.patch | 92 ++++++++++++ ...ntpoll-drop-vestigial-epi-dying-flag.patch | 110 ++++++++++++++ ...teger-overflow-in-ep-loop-check-proc.patch | 57 ++++++++ ...esh-epi-fget-ep-remove-file-comments.patch | 101 +++++++++++++ SPECS/kernel.spec | 70 +++++++-- 21 files changed, 1397 insertions(+), 274 deletions(-) delete mode 100644 SOURCES/1712-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch delete mode 100644 SOURCES/1736-rtmutex-use-waiter-task-in-remove-waiter.patch delete mode 100644 SOURCES/1738-rtmutex-skip-remove-waiter-when-not-enqueued.patch create mode 100644 SOURCES/1739-tg3-fix-race-for-querying-speed-duplex.patch create mode 100644 SOURCES/1740-netfilter-bridge-make-ebt-snat-arp-rewrite-writable.patch create mode 100644 SOURCES/1741-epoll-annotate-racy-check.patch create mode 100644 SOURCES/1742-eventpoll-defer-struct-eventpoll-free-to-rcu-grace-period.patch create mode 100644 SOURCES/1743-eventpoll-use-hlist-is-singular-node-in-ep-remove.patch create mode 100644 SOURCES/1744-eventpoll-split-ep-remove.patch create mode 100644 SOURCES/1745-eventpoll-kill-ep-remove.patch create mode 100644 SOURCES/1746-eventpoll-rename-ep-remove-safe-back-to-ep-remove.patch create mode 100644 SOURCES/1747-eventpoll-move-epi-fget-up.patch create mode 100644 SOURCES/1748-eventpoll-drop-vestigial-prefix-from-ep-remove-file-epi.patch create mode 100644 SOURCES/1749-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch create mode 100644 SOURCES/1750-eventpoll-move-f-lock-acquisition-into-ep-remove-file.patch create mode 100644 SOURCES/1751-eventpoll-refresh-eventpoll-release-fast-path-comment.patch create mode 100644 SOURCES/1752-eventpoll-drop-dead-bool-return-from-ep-remove-epi.patch create mode 100644 SOURCES/1753-eventpoll-drop-vestigial-epi-dying-flag.patch create mode 100644 SOURCES/1754-eventpoll-fix-integer-overflow-in-ep-loop-check-proc.patch create mode 100644 SOURCES/1755-eventpoll-refresh-epi-fget-ep-remove-file-comments.patch diff --git a/SOURCES/1712-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch b/SOURCES/1712-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch deleted file mode 100644 index 3028c0685..000000000 --- a/SOURCES/1712-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch +++ /dev/null @@ -1,99 +0,0 @@ -From: Christian Brauner -Date: Thu, 23 Apr 2026 11:56:09 +0200 -Subject: [PATCH] eventpoll: fix ep_remove struct eventpoll / struct file UAF - -CVE: CVE-2026-46242 - -Upstream commit a6dc643c69311677c574a0f17a3f4d66a5f3744b, adapted to the -pre-e9e5cd40d7c4 ("eventpoll: kill __ep_remove()") code shape still used -by this kernel, where the removal path is the monolithic __ep_remove(ep, -epi, force) rather than upstream's ep_remove()/ep_remove_epi()/ -ep_remove_file() split. - -ep_remove() used epi->ffd.file across the file->f_lock critical section -without holding a reference. A concurrent __fput() taking the -eventpoll_release() fastpath could free the file (and, for the -epoll-watches-epoll case, the watched struct eventpoll) mid-section, -leading to a use-after-free / attacker-controllable kmem_cache_free(). - -Pin the file with epi_fget() before entering the critical section and -release it afterwards. A failed pin means __fput() is already in flight -and will remove the epi via eventpoll_release_file() (which blocks on -ep->mtx), so it is safe to bail. The pin is taken only on the !force -path: the force path is reached from eventpoll_release_file() itself, -i.e. from within __fput() where f_count has already dropped to zero and -epi_fget() would necessarily fail; there @file is still valid and no -concurrent __fput() can race, so it keeps using epi->ffd.file directly. -A successful pin also makes the in-lock epi->dying recheck redundant; the -cheap lockless READ_ONCE(epi->dying) fast-path bailout is kept. - -Fixes: 58c9b016e128 ("epoll: use refcount to reduce ep_mutex contention") -Reported-by: Jaeyoung Chung -Signed-off-by: Christian Brauner ---- -diff --git a/fs/eventpoll.c b/fs/eventpoll.c -index 14e7663..9c56062 100644 ---- a/fs/eventpoll.c -+++ b/fs/eventpoll.c -@@ -711,6 +711,8 @@ static void ep_free(struct eventpoll *ep) - kfree(ep); - } - -+static struct file *epi_fget(const struct epitem *epi); -+ - /* - * Removes a "struct epitem" from the eventpoll RB tree and deallocates - * all the associated resources. Must be called with "mtx" held. -@@ -722,6 +724,7 @@ 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 file *to_put = NULL; - struct epitems_head *to_free; - struct hlist_head *head; - -@@ -732,13 +735,27 @@ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) - */ - 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; -+ if (!force) { -+ /* cheap sync with eventpoll_release_file() */ -+ if (unlikely(READ_ONCE(epi->dying))) -+ return false; -+ /* -+ * Grabbing a reference proves we are not racing -+ * eventpoll_release_file() and are not going to: a concurrent -+ * __fput() cannot free @file (nor, for the epoll-on-epoll -+ * case, the watched eventpoll) across the f_lock section -+ * below. A failed pin means @file already reached refcount -+ * zero and __fput() is in flight; it will remove this epi via -+ * eventpoll_release_file(), so bail out here. -+ */ -+ file = epi_fget(epi); -+ if (!file) -+ return false; -+ to_put = file; - } - -+ /* Remove the current item from the list of epoll hooks */ -+ spin_lock(&file->f_lock); - to_free = NULL; - head = file->f_ep; - if (head->first == &epi->fllink && !epi->fllink.next) { -@@ -772,6 +789,8 @@ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) - call_rcu(&epi->rcu, epi_rcu_free); - - percpu_counter_dec(&ep->user->epoll_watches); -+ if (to_put) -+ fput(to_put); - return true; - } - --- -2.43.0 - diff --git a/SOURCES/1736-rtmutex-use-waiter-task-in-remove-waiter.patch b/SOURCES/1736-rtmutex-use-waiter-task-in-remove-waiter.patch deleted file mode 100644 index 87ed85eb4..000000000 --- a/SOURCES/1736-rtmutex-use-waiter-task-in-remove-waiter.patch +++ /dev/null @@ -1,90 +0,0 @@ -From 3bfdc63936dd4773109b7b8c280c0f3b5ae7d349 Mon Sep 17 00:00:00 2001 -From: Keenan Dong -Date: Wed, 8 Apr 2026 16:46:00 +0800 -Subject: [PATCH] rtmutex: Use waiter::task instead of current in - remove_waiter() - -remove_waiter() is used by the slowlock paths, but it is also used for -proxy-lock rollback in rt_mutex_start_proxy_lock() when invoked from -futex_requeue(). - -In the latter case waiter::task is not current, but remove_waiter() -operates on current for the dequeue operation. That results in several -problems: - - 1) the rbtree dequeue happens without waiter::task::pi_lock being held - - 2) the waiter task's pi_blocked_on state is not cleared, which leaves a - dangling pointer primed for UAF around. - - 3) rt_mutex_adjust_prio_chain() operates on the wrong top priority waiter - task - -Use waiter::task instead of current in all related operations in -remove_waiter() to cure those problems. - -[ tglx: Fixup rt_mutex_adjust_prio_chain(), add a comment and amend the - changelog ] - -Fixes: 8161239a8bcc ("rtmutex: Simplify PI algorithm and make highest prio task get lock") -Reported-by: Yuan Tan -Reported-by: Yifan Wu -Reported-by: Juefei Pu -Reported-by: Xin Liu -Signed-off-by: Keenan Dong -Signed-off-by: Thomas Gleixner -Cc: stable@vger.kernel.org - -[ AlmaLinux: adapted to a9 (5.14.0-687.5.1.el9_8). No functional change vs - upstream commit 3bfdc63936dd; only diff hunk offsets differ. The - scoped_guard(raw_spinlock, ...) form is used verbatim: it is available in - this tree via include/linux/cleanup.h and DEFINE_LOCK_GUARD_1(raw_spinlock, - ...) in include/linux/spinlock.h. ] ---- - kernel/locking/rtmutex.c | 13 ++++++++----- - 1 file changed, 8 insertions(+), 5 deletions(-) - -diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c -index 1eb4f9499..e881d9548 100644 ---- a/kernel/locking/rtmutex.c -+++ b/kernel/locking/rtmutex.c -@@ -1534,20 +1534,23 @@ static bool rtmutex_spin_on_owner(struct rt_mutex_base *lock, - * - * Must be called with lock->wait_lock held and interrupts disabled. It must - * have just failed to try_to_take_rt_mutex(). -+ * -+ * When invoked from rt_mutex_start_proxy_lock() waiter::task != current ! - */ - static void __sched remove_waiter(struct rt_mutex_base *lock, - struct rt_mutex_waiter *waiter) - { - bool is_top_waiter = (waiter == rt_mutex_top_waiter(lock)); - struct task_struct *owner = rt_mutex_owner(lock); -+ struct task_struct *waiter_task = waiter->task; - struct rt_mutex_base *next_lock; - - lockdep_assert_held(&lock->wait_lock); - -- raw_spin_lock(¤t->pi_lock); -- rt_mutex_dequeue(lock, waiter); -- current->pi_blocked_on = NULL; -- raw_spin_unlock(¤t->pi_lock); -+ scoped_guard(raw_spinlock, &waiter_task->pi_lock) { -+ rt_mutex_dequeue(lock, waiter); -+ waiter_task->pi_blocked_on = NULL; -+ } - - /* - * Only update priority if the waiter was the highest priority -@@ -1583,7 +1586,7 @@ static void __sched remove_waiter(struct rt_mutex_base *lock, - raw_spin_unlock_irq(&lock->wait_lock); - - rt_mutex_adjust_prio_chain(owner, RT_MUTEX_MIN_CHAINWALK, lock, -- next_lock, NULL, current); -+ next_lock, NULL, waiter_task); - - raw_spin_lock_irq(&lock->wait_lock); - } --- -2.43.7 - diff --git a/SOURCES/1738-rtmutex-skip-remove-waiter-when-not-enqueued.patch b/SOURCES/1738-rtmutex-skip-remove-waiter-when-not-enqueued.patch deleted file mode 100644 index 9959b05ad..000000000 --- a/SOURCES/1738-rtmutex-skip-remove-waiter-when-not-enqueued.patch +++ /dev/null @@ -1,77 +0,0 @@ -From 40a25d59e85b3c8709ac2424d44f65610467871e Mon Sep 17 00:00:00 2001 -From: Davidlohr Bueso -Date: Thu, 7 May 2026 04:29:13 -0700 -Subject: [PATCH] locking/rtmutex: Skip remove_waiter() when waiter is not - enqueued - -syzbot triggered the following splat in remove_waiter() via -FUTEX_CMP_REQUEUE_PI: - - KASAN: null-ptr-deref in range [0x0000000000000a88-0x0000000000000a8f] - class_raw_spinlock_constructor - remove_waiter+0x159/0x1200 kernel/locking/rtmutex.c:1561 - rt_mutex_start_proxy_lock+0x103/0x120 - futex_requeue+0x10e4/0x20d0 - __x64_sys_futex+0x34f/0x4d0 - -task_blocks_on_rt_mutex() does not arm the waiter upon deadlock detection, -leaving waiter->task nil, where 3bfdc63936dd ("rtmutex: Use waiter::task instead -of current in remove_waiter()") made this fatal. - -Furthermore, rt_mutex_start_proxy_lock() should not be calling into remove_waiter() -upon a successfully grabbing the rtmutex. 1a1fb985f2e2 ("futex: Handle early deadlock -return correctly"), moved the remove_waiter() out of __rt_mutex_start_proxy_lock() -(where 'ret' was only ever 0 or < 0) into the wrapper. Tighten this check to -account for try_to_take_rt_mutex(). - -Fixes: 3bfdc63936dd ("rtmutex: Use waiter::task instead of current in remove_waiter()") -Reported-by: syzbot+78147abe6c524f183ee9@syzkaller.appspotmail.com -Signed-off-by: Davidlohr Bueso -Signed-off-by: Thomas Gleixner -Cc: stable@vger.kernel.org -Closes: https://lore.kernel.org/all/69f114ac.050a0220.ac8b.0003.GAE@google.com/ -Link: https://patch.msgid.link/20260507112913.1019537-1-dave@stgolabs.net - -[ AlmaLinux: adapted to a9 (5.14.0-687.5.1.el9_8). No change vs upstream - commit 40a25d59e85b. Both hunks apply cleanly: kernel/locking/rtmutex.c - remove_waiter() (the waiter_task local comes from the earlier - "rtmutex: Use waiter::task ..." patch) and kernel/locking/rtmutex_api.c - rt_mutex_start_proxy_lock(). The "ret < 0" tightening is meaningful in this - tree: __rt_mutex_start_proxy_lock() returns 1 on try_to_take_rt_mutex() - success, so the prior "if (unlikely(ret))" wrongly invoked remove_waiter() - after acquiring the lock. ] ---- - kernel/locking/rtmutex.c | 3 +++ - kernel/locking/rtmutex_api.c | 2 +- - 2 files changed, 4 insertions(+), 1 deletion(-) - -diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c -index e881d9548..1cecc1092 100644 ---- a/kernel/locking/rtmutex.c -+++ b/kernel/locking/rtmutex.c -@@ -1547,6 +1547,9 @@ static void __sched remove_waiter(struct rt_mutex_base *lock, - - lockdep_assert_held(&lock->wait_lock); - -+ if (!waiter_task) /* never enqueued */ -+ return; -+ - scoped_guard(raw_spinlock, &waiter_task->pi_lock) { - rt_mutex_dequeue(lock, waiter); - waiter_task->pi_blocked_on = NULL; -diff --git a/kernel/locking/rtmutex_api.c b/kernel/locking/rtmutex_api.c -index 6614ccdc1..341a540f6 100644 ---- a/kernel/locking/rtmutex_api.c -+++ b/kernel/locking/rtmutex_api.c -@@ -347,7 +347,7 @@ int __sched rt_mutex_start_proxy_lock(struct rt_mutex_base *lock, - - raw_spin_lock_irq(&lock->wait_lock); - ret = __rt_mutex_start_proxy_lock(lock, waiter, task, &wake_q); -- if (unlikely(ret)) -+ if (unlikely(ret < 0)) - remove_waiter(lock, waiter); - preempt_disable(); - raw_spin_unlock_irq(&lock->wait_lock); --- -2.43.7 - diff --git a/SOURCES/1739-tg3-fix-race-for-querying-speed-duplex.patch b/SOURCES/1739-tg3-fix-race-for-querying-speed-duplex.patch new file mode 100644 index 000000000..d2315a7ee --- /dev/null +++ b/SOURCES/1739-tg3-fix-race-for-querying-speed-duplex.patch @@ -0,0 +1,35 @@ +From 38b778cd8963336ed70b618be269b65ffeb0ae42 Mon Sep 17 00:00:00 2001 +From: Thomas Bogendoerfer +Date: Wed, 25 Mar 2026 12:20:53 +0100 +Subject: [PATCH] tg3: Fix race for querying speed/duplex + +[ Upstream commit bb417456c7814d1493d98b7dd9c040bf3ce3b4ed ] + +When driver signals carrier up via netif_carrier_on() its internal +link_up state isn't updated immediately. This leads to inconsistent +speed/duplex in /proc/net/bonding/bondX where the speed and duplex +is shown as unknown while ethtool shows correct values. Fix this by +using netif_carrier_ok() for link checking in get_ksettings function. + +Fixes: 84421b99cedc ("tg3: Update link_up flag for phylib devices") +Signed-off-by: Thomas Bogendoerfer +Reviewed-by: Pavan Chebbi +Signed-off-by: David S. Miller +Signed-off-by: Sasha Levin + +diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c +index bd3b56c7aab8..e18e58f8258e 100644 +--- a/drivers/net/ethernet/broadcom/tg3.c ++++ b/drivers/net/ethernet/broadcom/tg3.c +@@ -12223,7 +12223,7 @@ static int tg3_get_link_ksettings(struct net_device *dev, + ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising, + advertising); + +- if (netif_running(dev) && tp->link_up) { ++ if (netif_running(dev) && netif_carrier_ok(dev)) { + cmd->base.speed = tp->link_config.active_speed; + cmd->base.duplex = tp->link_config.active_duplex; + ethtool_convert_legacy_u32_to_link_mode( +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1740-netfilter-bridge-make-ebt-snat-arp-rewrite-writable.patch b/SOURCES/1740-netfilter-bridge-make-ebt-snat-arp-rewrite-writable.patch new file mode 100644 index 000000000..82c85a6c7 --- /dev/null +++ b/SOURCES/1740-netfilter-bridge-make-ebt-snat-arp-rewrite-writable.patch @@ -0,0 +1,53 @@ +From 76280b78cc9f23bdc6438e10ad6dff148ef8375b Mon Sep 17 00:00:00 2001 +From: Yiming Qian +Date: Sat, 23 May 2026 12:29:10 +0000 +Subject: [PATCH] netfilter: bridge: make ebt_snat ARP rewrite writable + +[ Upstream commit 67ba971ae02514d85818fe0c32549ab4bfa3bf49 ] + +The ebtables SNAT target keeps the Ethernet source address rewrite +behind skb_ensure_writable(skb, 0). This is intentional: at the bridge +ebtables hooks the Ethernet header is addressed through +skb_mac_header()/eth_hdr(), while skb->data points at the Ethernet +payload. Asking skb_ensure_writable() for ETH_HLEN bytes would check +the payload, not the Ethernet header, and would reintroduce the small +packet regression fixed by commit 63137bc5882a. + +However, the optional ARP sender hardware address rewrite is different. +It writes through skb_store_bits() at an offset relative to skb->data: + + skb_store_bits(skb, sizeof(struct arphdr), info->mac, ETH_ALEN) + +skb_header_pointer() only safely reads the ARP header; it does not make +the later sender hardware address range writable. If that range is +still held in a nonlinear skb fragment backed by a splice-imported file +page, skb_store_bits() maps the frag page and copies the new MAC address +directly into it. + +Ensure the ARP SHA range is writable before reading the ARP header and +before calling skb_store_bits(). + +Fixes: 63137bc5882a ("netfilter: ebtables: Fixes dropping of small packets in bridge nat") +Reported-by: Yiming Qian +Signed-off-by: Yiming Qian +Signed-off-by: Florian Westphal +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Sasha Levin + +diff --git a/net/bridge/netfilter/ebt_snat.c b/net/bridge/netfilter/ebt_snat.c +index 7dfbcdfc30e5..c9e229af0366 100644 +--- a/net/bridge/netfilter/ebt_snat.c ++++ b/net/bridge/netfilter/ebt_snat.c +@@ -31,6 +31,9 @@ ebt_snat_tg(struct sk_buff *skb, const struct xt_action_param *par) + const struct arphdr *ap; + struct arphdr _ah; + ++ if (skb_ensure_writable(skb, sizeof(_ah) + ETH_ALEN)) ++ return EBT_DROP; ++ + ap = skb_header_pointer(skb, 0, sizeof(_ah), &_ah); + if (ap == NULL) + return EBT_DROP; +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1741-epoll-annotate-racy-check.patch b/SOURCES/1741-epoll-annotate-racy-check.patch new file mode 100644 index 000000000..b698c2631 --- /dev/null +++ b/SOURCES/1741-epoll-annotate-racy-check.patch @@ -0,0 +1,66 @@ +From bde1be0c6e5eeb145576344159be1f51cb8d5d8b Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:53:22 +0800 +Subject: [PATCH] epoll: annotate racy check + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 6474353a5e3d0b2cf610153cea0c61f576a36d0a +Author: Christian Brauner +Date: Wed Sep 25 11:05:16 2024 +0200 + + epoll: annotate racy check + + Epoll relies on a racy fastpath check during __fput() in + eventpoll_release() to avoid the hit of pointlessly acquiring a + semaphore. Annotate that race by using WRITE_ONCE() and READ_ONCE(). + + Link: https://lore.kernel.org/r/66edfb3c.050a0220.3195df.001a.GAE@google.com + Link: https://lore.kernel.org/r/20240925-fungieren-anbauen-79b334b00542@brauner + Reviewed-by: Jan Kara + Reported-by: syzbot+3b6b32dc50537a49bb4a@syzkaller.appspotmail.com + Signed-off-by: Christian Brauner + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index a7efeb499e4b..616f97e9b75b 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -852,7 +852,8 @@ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) + to_free = NULL; + head = file->f_ep; + if (head->first == &epi->fllink && !epi->fllink.next) { +- file->f_ep = NULL; ++ /* See eventpoll_release() for details. */ ++ WRITE_ONCE(file->f_ep, NULL); + if (!is_file_epoll(file)) { + struct epitems_head *v; + v = container_of(head, struct epitems_head, epitems); +@@ -1630,7 +1631,8 @@ static int attach_epitem(struct file *file, struct epitem *epi) + spin_unlock(&file->f_lock); + goto allocate; + } +- file->f_ep = head; ++ /* See eventpoll_release() for details. */ ++ WRITE_ONCE(file->f_ep, head); + to_free = NULL; + } + hlist_add_head_rcu(&epi->fllink, file->f_ep); +diff --git a/include/linux/eventpoll.h b/include/linux/eventpoll.h +index 3337745d81bd..0c0d00fcd131 100644 +--- a/include/linux/eventpoll.h ++++ b/include/linux/eventpoll.h +@@ -42,7 +42,7 @@ static inline void eventpoll_release(struct file *file) + * because the file in on the way to be removed and nobody ( but + * eventpoll ) has still a reference to this file. + */ +- if (likely(!file->f_ep)) ++ if (likely(!READ_ONCE(file->f_ep))) + return; + + /* +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1742-eventpoll-defer-struct-eventpoll-free-to-rcu-grace-period.patch b/SOURCES/1742-eventpoll-defer-struct-eventpoll-free-to-rcu-grace-period.patch new file mode 100644 index 000000000..f2bdc8dc7 --- /dev/null +++ b/SOURCES/1742-eventpoll-defer-struct-eventpoll-free-to-rcu-grace-period.patch @@ -0,0 +1,53 @@ +From 2b62eb17ec16b6a7d341497e48e331f84280cd58 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:54:24 +0800 +Subject: [PATCH] eventpoll: defer struct eventpoll free to RCU grace period + +JIRA: https://redhat.atlassian.net/browse/RHEL-173837 +Upstream status: Linus + +CVE: CVE-2026-43074 + +commit 07712db80857d5d09ae08f3df85a708ecfc3b61f +Author: Nicholas Carlini +Date: Tue Mar 31 15:25:32 2026 +0200 + + eventpoll: defer struct eventpoll free to RCU grace period + + In certain situations, ep_free() in eventpoll.c will kfree the epi->ep + eventpoll struct while it still being used by another concurrent thread. + Defer the kfree() to an RCU callback to prevent UAF. + + Fixes: f2e467a48287 ("eventpoll: Fix semi-unbounded recursion") + Signed-off-by: Nicholas Carlini + Signed-off-by: Christian Brauner + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 616f97e9b75b..0368ab69138a 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -225,6 +225,9 @@ struct eventpoll { + */ + refcount_t refcount; + ++ /* used to defer freeing past ep_get_upwards_depth_proc() RCU walk */ ++ struct rcu_head rcu; ++ + #ifdef CONFIG_NET_RX_BUSY_POLL + /* used to track busy poll napi_id */ + unsigned int napi_id; +@@ -818,7 +821,8 @@ static void ep_free(struct eventpoll *ep) + mutex_destroy(&ep->mtx); + free_uid(ep->user); + wakeup_source_unregister(ep->ws); +- kfree(ep); ++ /* ep_get_upwards_depth_proc() may still hold epi->ep under RCU */ ++ kfree_rcu(ep, rcu); + } + + /* +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1743-eventpoll-use-hlist-is-singular-node-in-ep-remove.patch b/SOURCES/1743-eventpoll-use-hlist-is-singular-node-in-ep-remove.patch new file mode 100644 index 000000000..126527bc2 --- /dev/null +++ b/SOURCES/1743-eventpoll-use-hlist-is-singular-node-in-ep-remove.patch @@ -0,0 +1,39 @@ +From 1fe6204c6ea38fc1d370d9bbb9fd6a800104e5c8 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:54:59 +0800 +Subject: [PATCH] eventpoll: use hlist_is_singular_node() in __ep_remove() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 3d9fd0abc94d8cd430cc7cd7d37ce5e5aae2cd2b +Author: Christian Brauner +Date: Thu Apr 23 11:56:04 2026 +0200 + + eventpoll: use hlist_is_singular_node() in __ep_remove() + + 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) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 0368ab69138a..2dd87800bfbf 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -855,7 +855,7 @@ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) + + 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)) { +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1744-eventpoll-split-ep-remove.patch b/SOURCES/1744-eventpoll-split-ep-remove.patch new file mode 100644 index 000000000..a872525c2 --- /dev/null +++ b/SOURCES/1744-eventpoll-split-ep-remove.patch @@ -0,0 +1,84 @@ +From 83db11e5ba3c57104945247e11c1891355a12fb6 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:55:51 +0800 +Subject: [PATCH] eventpoll: split __ep_remove() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 0f7bdfd413000985de09fc39eb9efa1e091a3ce0 +Author: Christian Brauner +Date: Thu Apr 23 11:56:05 2026 +0200 + + eventpoll: split __ep_remove() + + Split __ep_remove() to delineate file removal from epoll item removal. + + Suggested-by: Linus Torvalds + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-2-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 2dd87800bfbf..001c87b6ddab 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -825,6 +825,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. +@@ -836,8 +839,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(); + +@@ -853,8 +854,21 @@ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) + 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); +@@ -868,6 +882,11 @@ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) + 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); + +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1745-eventpoll-kill-ep-remove.patch b/SOURCES/1745-eventpoll-kill-ep-remove.patch new file mode 100644 index 000000000..3d7e6632d --- /dev/null +++ b/SOURCES/1745-eventpoll-kill-ep-remove.patch @@ -0,0 +1,134 @@ +From 9a9a41f8aa46741f965703f520542e9e8fdbe59f Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:56:21 +0800 +Subject: [PATCH] eventpoll: kill __ep_remove() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit e9e5cd40d7c403e19f21d0f7b8b8ba3a76b58330 +Author: Christian Brauner +Date: Thu Apr 23 11:56:06 2026 +0200 + + eventpoll: kill __ep_remove() + + 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) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 001c87b6ddab..cb75868ba1de 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -825,49 +825,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. */ +@@ -914,7 +883,25 @@ static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi) + */ + 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)); + } + +@@ -1146,7 +1133,7 @@ void eventpoll_release_file(struct file *file) + 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); + + /* +@@ -1155,7 +1142,13 @@ void eventpoll_release_file(struct file *file) + */ + 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)) +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1746-eventpoll-rename-ep-remove-safe-back-to-ep-remove.patch b/SOURCES/1746-eventpoll-rename-ep-remove-safe-back-to-ep-remove.patch new file mode 100644 index 000000000..23c3da7fc --- /dev/null +++ b/SOURCES/1746-eventpoll-rename-ep-remove-safe-back-to-ep-remove.patch @@ -0,0 +1,98 @@ +From 4e16f36e52dfe03056ec8c4daaef79b1715bb341 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:58:37 +0800 +Subject: [PATCH] eventpoll: rename ep_remove_safe() back to ep_remove() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 0bade234723e40e4937be912e105785d6a51464e +Author: Christian Brauner +Date: Thu Apr 23 11:56:07 2026 +0200 + + eventpoll: rename ep_remove_safe() back to ep_remove() + + 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) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index cb75868ba1de..efc9e3e1e3f5 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -881,7 +881,7 @@ static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi) + /* + * 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; + +@@ -928,7 +928,7 @@ static void ep_clear_and_put(struct eventpoll *ep) + + /* + * 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 +@@ -937,7 +937,7 @@ static void ep_clear_and_put(struct eventpoll *ep) + 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(); + } + +@@ -1714,21 +1714,21 @@ static int ep_insert(struct eventpoll *ep, const struct epoll_event *event, + 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; + } + } +@@ -1752,7 +1752,7 @@ static int ep_insert(struct eventpoll *ep, const struct epoll_event *event, + * high memory pressure. + */ + if (unlikely(!epq.epi)) { +- ep_remove_safe(ep, epi); ++ ep_remove(ep, epi); + return -ENOMEM; + } + +@@ -2415,7 +2415,7 @@ int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds, + * 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; +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1747-eventpoll-move-epi-fget-up.patch b/SOURCES/1747-eventpoll-move-epi-fget-up.patch new file mode 100644 index 000000000..199fb0414 --- /dev/null +++ b/SOURCES/1747-eventpoll-move-epi-fget-up.patch @@ -0,0 +1,95 @@ +From 8c1707336e5674c16c2a24c9cdf2b5018b166a09 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:00:26 +0800 +Subject: [PATCH] eventpoll: move epi_fget() up + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 86e87059e6d1fd5115a31949726450ed03c1073b +Author: Christian Brauner +Date: Thu Apr 23 11:56:08 2026 +0200 + + eventpoll: move epi_fget() up + + 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) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index f7591c255..c9e9d3da9 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -715,6 +715,34 @@ static void ep_free(struct eventpoll *ep) + kfree_rcu(ep, rcu); + } + ++/* ++ * 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 +@@ -886,34 +914,6 @@ static __poll_t __ep_eventpoll_poll(struct file *file, poll_table *wait, int dep + return res; + } + +-/* +- * 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() diff --git a/SOURCES/1748-eventpoll-drop-vestigial-prefix-from-ep-remove-file-epi.patch b/SOURCES/1748-eventpoll-drop-vestigial-prefix-from-ep-remove-file-epi.patch new file mode 100644 index 000000000..985802699 --- /dev/null +++ b/SOURCES/1748-eventpoll-drop-vestigial-prefix-from-ep-remove-file-epi.patch @@ -0,0 +1,78 @@ +From 6175a9a692e559e7f0635d3a32d20fe94bae8280 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:57:24 +0800 +Subject: [PATCH] eventpoll: drop vestigial __ prefix from + ep_remove_{file,epi}() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +Conflict: I don't know how this commit is present when commit + d3608d9e2f5a ("eventpoll: fix ep_remove struct eventpoll / + struct file UAF") is applied since its time stamp is later than + the afore mentioned commit. I've applied it here purely to avoid + the subsequent conflict. + +commit 0feaf644f7180c4a91b6b405a881afbfd958f1cf +Author: Christian Brauner +Date: Fri Apr 24 00:23:18 2026 +0200 + + eventpoll: drop vestigial __ prefix from ep_remove_{file,epi}() + + 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) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index ded53c6fbd6f..1246c0517aa6 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -857,7 +857,7 @@ static struct file *epi_fget(const struct epitem *epi) + * 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; +@@ -881,7 +881,7 @@ static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, + 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); + +@@ -927,9 +927,9 @@ static void ep_remove(struct eventpoll *ep, struct epitem *epi) + 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)); + } + +@@ -1146,8 +1146,8 @@ void eventpoll_release_file(struct file *file) + 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); + +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1749-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch b/SOURCES/1749-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch new file mode 100644 index 000000000..2f4872c14 --- /dev/null +++ b/SOURCES/1749-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch @@ -0,0 +1,104 @@ +From 5c53e2d149e55d3ee64fb03387d2f81d59477774 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:00:59 +0800 +Subject: [PATCH] eventpoll: fix ep_remove struct eventpoll / struct file UAF + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +CVE: CVE-2026-46242 + +commit a6dc643c69311677c574a0f17a3f4d66a5f3744b +Author: Christian Brauner +Date: Thu Apr 23 11:56:09 2026 +0200 + + eventpoll: fix ep_remove struct eventpoll / struct file UAF + + 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 + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-6-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 1246c0517aa6..f46906640a9b 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -911,22 +911,26 @@ static bool ep_remove_epi(struct eventpoll *ep, struct epitem *epi) + */ + 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)) +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1750-eventpoll-move-f-lock-acquisition-into-ep-remove-file.patch b/SOURCES/1750-eventpoll-move-f-lock-acquisition-into-ep-remove-file.patch new file mode 100644 index 000000000..9c82bdf39 --- /dev/null +++ b/SOURCES/1750-eventpoll-move-f-lock-acquisition-into-ep-remove-file.patch @@ -0,0 +1,70 @@ +From e0bf480728b83ec6ceb66975bfbac514195873c8 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:01:51 +0800 +Subject: [PATCH] eventpoll: move f_lock acquisition into ep_remove_file() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit d30deeb8b0cf6259785c1fb79b87905d281b0a5a +Author: Christian Brauner +Date: Thu Apr 23 11:56:10 2026 +0200 + + eventpoll: move f_lock acquisition into ep_remove_file() + + Let the helper own its critical section end-to-end: take &file->f_lock + at the top, read file->f_ep inside the lock, release on exit. Callers + (ep_remove() and eventpoll_release_file()) no longer need to wrap the + call, and the function-comment lock-handoff contract is gone. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-7-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index f46906640a9b..9798ab5f7663 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -854,18 +854,18 @@ static struct file *epi_fget(const struct epitem *epi) + } + + /* +- * Called with &file->f_lock held, +- * returns with it released ++ * Takes &file->f_lock; 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; ++ struct hlist_head *head; + + lockdep_assert_held(&ep->mtx); +- lockdep_assert_held(&file->f_lock); + ++ spin_lock(&file->f_lock); ++ head = file->f_ep; + if (hlist_is_singular_node(&epi->fllink, head)) { + /* See eventpoll_release() for details. */ + WRITE_ONCE(file->f_ep, NULL); +@@ -930,7 +930,6 @@ static void ep_remove(struct eventpoll *ep, struct epitem *epi) + if (!file) + return; + +- spin_lock(&file->f_lock); + ep_remove_file(ep, epi, file); + + if (ep_remove_epi(ep, epi)) +@@ -1149,7 +1148,6 @@ void eventpoll_release_file(struct file *file) + + ep_unregister_pollwait(ep, epi); + +- spin_lock(&file->f_lock); + ep_remove_file(ep, epi, file); + dispose = ep_remove_epi(ep, epi); + +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1751-eventpoll-refresh-eventpoll-release-fast-path-comment.patch b/SOURCES/1751-eventpoll-refresh-eventpoll-release-fast-path-comment.patch new file mode 100644 index 000000000..deabbe282 --- /dev/null +++ b/SOURCES/1751-eventpoll-refresh-eventpoll-release-fast-path-comment.patch @@ -0,0 +1,66 @@ +From eab8e8be1be2430b6712fc1a6c8925d240a40031 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:02:27 +0800 +Subject: [PATCH] eventpoll: refresh eventpoll_release() fast-path comment + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 33e92e9ecf48c08cb4807e9a36f9eb01619c1a1e +Author: Christian Brauner +Date: Thu Apr 23 11:56:11 2026 +0200 + + eventpoll: refresh eventpoll_release() fast-path comment + + The old comment justified the lockless READ_ONCE(file->f_ep) check + with "False positives simply cannot happen because the file is on + the way to be removed and nobody ( but eventpoll ) has still a + reference to this file." That reasoning was the root of the UAF + fixed in "eventpoll: fix ep_remove struct eventpoll / struct file + UAF": __ep_remove() could clear f_ep while another close raced + past the fast path and freed the watched eventpoll / recycled the + struct file slot. + + With ep_remove() now pinning @file via epi_fget() across the f_ep + clear and hlist_del_rcu(), the invariant is re-established for the + right reason: anyone who might clear f_ep holds @file alive for + the duration, so a NULL observation really does mean no + concurrent eventpoll path has work left on this file. Refresh the + comment accordingly so the next reader doesn't inherit the broken + model. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-8-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/include/linux/eventpoll.h b/include/linux/eventpoll.h +index 0c0d00fcd131..be762281355a 100644 +--- a/include/linux/eventpoll.h ++++ b/include/linux/eventpoll.h +@@ -35,12 +35,16 @@ static inline void eventpoll_release(struct file *file) + { + + /* +- * Fast check to avoid the get/release of the semaphore. Since +- * we're doing this outside the semaphore lock, it might return +- * false negatives, but we don't care. It'll help in 99.99% of cases +- * to avoid the semaphore lock. 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. ++ * Fast check to skip the slow path in the common case where the ++ * file was never attached to an epoll. Safe without file->f_lock ++ * because every f_ep writer excludes a concurrent __fput() on ++ * @file: ++ * - ep_insert() requires the file alive (refcount > 0); ++ * - ep_remove() holds @file pinned via epi_fget() across the ++ * write; ++ * - eventpoll_release_file() runs from __fput() itself. ++ * We are in __fput() here, so none of those can race us: a NULL ++ * observation truly means no epoll path has work left on @file. + */ + if (likely(!READ_ONCE(file->f_ep))) + return; +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1752-eventpoll-drop-dead-bool-return-from-ep-remove-epi.patch b/SOURCES/1752-eventpoll-drop-dead-bool-return-from-ep-remove-epi.patch new file mode 100644 index 000000000..43a7ae060 --- /dev/null +++ b/SOURCES/1752-eventpoll-drop-dead-bool-return-from-ep-remove-epi.patch @@ -0,0 +1,92 @@ +From 38a78d49b434ce9f29afdfaa870142ecb121800f Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:03:04 +0800 +Subject: [PATCH] eventpoll: drop dead bool return from ep_remove_epi() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 3a4551ea9c042502019b1d8a986e962cb9015366 +Author: Christian Brauner +Date: Thu Apr 23 11:56:12 2026 +0200 + + eventpoll: drop dead bool return from ep_remove_epi() + + ep_remove_epi() always returns true -- the "can be disposed" + answer was meaningful back when the dying-check lived inside the + pre-split __ep_remove(), but after that check moved to ep_remove() + the return value is just noise. Both callers gate on it + unconditionally: + + if (ep_remove_epi(ep, epi)) + WARN_ON_ONCE(ep_refcount_dec_and_test(ep)); + + dispose = ep_remove_epi(ep, epi); + ... + if (dispose && ep_refcount_dec_and_test(ep)) + ep_free(ep); + + Make ep_remove_epi() return void, drop the dispose local in + eventpoll_release_file(), and the useless conditionals at both + callers. No functional change. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-9-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index d8163df95..2933f5ea0 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -771,7 +771,7 @@ static void ep_remove_file(struct eventpoll *ep, struct epitem *epi, + free_ephead(to_free); + } + +-static bool ep_remove_epi(struct eventpoll *ep, struct epitem *epi) ++static void ep_remove_epi(struct eventpoll *ep, struct epitem *epi) + { + lockdep_assert_held(&ep->mtx); + +@@ -793,7 +793,6 @@ static bool ep_remove_epi(struct eventpoll *ep, struct epitem *epi) + call_rcu(&epi->rcu, epi_rcu_free); + + percpu_counter_dec(&ep->user->epoll_watches); +- return true; + } + + /* +@@ -821,9 +820,8 @@ static void ep_remove(struct eventpoll *ep, struct epitem *epi) + return; + + ep_remove_file(ep, epi, file); +- +- if (ep_remove_epi(ep, epi)) +- WARN_ON_ONCE(ep_refcount_dec_and_test(ep)); ++ ep_remove_epi(ep, epi); ++ WARN_ON_ONCE(ep_refcount_dec_and_test(ep)); + } + + static void ep_clear_and_put(struct eventpoll *ep) +@@ -992,7 +990,6 @@ void eventpoll_release_file(struct file *file) + { + struct eventpoll *ep; + struct epitem *epi; +- bool dispose; + + /* + * Use the 'dying' flag to prevent a concurrent ep_clear_and_put() from +@@ -1016,11 +1013,11 @@ void eventpoll_release_file(struct file *file) + ep_unregister_pollwait(ep, epi); + + ep_remove_file(ep, epi, file); +- dispose = ep_remove_epi(ep, epi); ++ ep_remove_epi(ep, epi); + + mutex_unlock(&ep->mtx); + +- if (dispose && ep_refcount_dec_and_test(ep)) ++ if (ep_refcount_dec_and_test(ep)) + ep_free(ep); + goto again; + } diff --git a/SOURCES/1753-eventpoll-drop-vestigial-epi-dying-flag.patch b/SOURCES/1753-eventpoll-drop-vestigial-epi-dying-flag.patch new file mode 100644 index 000000000..499dae72e --- /dev/null +++ b/SOURCES/1753-eventpoll-drop-vestigial-epi-dying-flag.patch @@ -0,0 +1,110 @@ +From 3724dc6414c5da5c393e58e0e5863fdb95166922 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:03:26 +0800 +Subject: [PATCH] eventpoll: drop vestigial epi->dying flag + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 07422c948f4bdf15567a129a0983f7c12e57ba8e +Author: Christian Brauner +Date: Thu Apr 23 11:56:13 2026 +0200 + + eventpoll: drop vestigial epi->dying flag + + With ep_remove() now pinning @file via epi_fget() across the + f_ep clear and hlist_del_rcu(), the dying flag no longer + orchestrates anything: it was set in eventpoll_release_file() + (which only runs from __fput(), i.e. after @file's refcount has + reached zero) and read in __ep_remove() / ep_remove() as a cheap + bail before attempting the same synchronization epi_fget() now + provides unconditionally. + + The implication is simple: epi->dying == true always coincides + with file_ref_get(&file->f_ref) == false, because __fput() is + reachable only once the refcount hits zero and the refcount is + monotone in that state. The READ_ONCE(epi->dying) in ep_remove() + therefore selects exactly the same callers that epi_fget() would + reject, just one atomic cheaper. That's not worth a struct + field, a second coordination mechanism, and the comments on + both. + + Refresh the eventpoll_release_file() comment to describe what + actually makes the path race-free now (the pin in ep_remove()). + No functional change: the correctness argument is unchanged, + only the mechanism is now a single one instead of two. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-10-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 88b501a5e709..5228ff9c8742 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -148,13 +148,6 @@ struct epitem { + /* The file descriptor information this item refers to */ + struct epoll_filefd ffd; + +- /* +- * Protected by file->f_lock, true for to-be-released epitem already +- * removed from the "struct file" items list; together with +- * eventpoll->refcount orchestrates "struct eventpoll" disposal +- */ +- bool dying; +- + /* List containing poll wait queues */ + struct eppoll_entry *pwqlist; + +@@ -220,10 +213,7 @@ struct eventpoll { + struct hlist_head refs; + u8 loop_check_depth; + +- /* +- * usage count, used together with epitem->dying to +- * orchestrate the disposal of this struct +- */ ++ /* usage count, orchestrates "struct eventpoll" disposal */ + refcount_t refcount; + + /* used to defer freeing past ep_get_upwards_depth_proc() RCU walk */ +@@ -918,13 +908,10 @@ static void ep_remove(struct eventpoll *ep, struct epitem *epi) + + ep_unregister_pollwait(ep, epi); + +- /* cheap sync with eventpoll_release_file() */ +- if (unlikely(READ_ONCE(epi->dying))) +- return; +- + /* + * If we manage to grab a reference it means we're not in +- * eventpoll_release_file() and aren't going to be. ++ * eventpoll_release_file() and aren't going to be: once @file's ++ * refcount has reached zero, file_ref_get() cannot bring it back. + */ + file = epi_fget(epi); + if (!file) +@@ -1126,15 +1113,15 @@ void eventpoll_release_file(struct file *file) + struct epitem *epi; + + /* +- * Use the 'dying' flag to prevent a concurrent ep_clear_and_put() from +- * touching the epitems list before eventpoll_release_file() can access +- * the ep->mtx. ++ * A concurrent ep_remove() cannot outrace us: it pins @file via ++ * epi_fget(), which fails once __fput() has dropped the refcount ++ * to zero -- the path we're on. So any racing ep_remove() bails ++ * and leaves the epi for us to clean up here. + */ + again: + spin_lock(&file->f_lock); + if (file->f_ep && file->f_ep->first) { + epi = hlist_entry(file->f_ep->first, struct epitem, fllink); +- WRITE_ONCE(epi->dying, true); + spin_unlock(&file->f_lock); + + /* +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1754-eventpoll-fix-integer-overflow-in-ep-loop-check-proc.patch b/SOURCES/1754-eventpoll-fix-integer-overflow-in-ep-loop-check-proc.patch new file mode 100644 index 000000000..85940ba18 --- /dev/null +++ b/SOURCES/1754-eventpoll-fix-integer-overflow-in-ep-loop-check-proc.patch @@ -0,0 +1,57 @@ +From 2eb0a1e08ebad5be00433093449abf025450ca6d Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Wed, 17 Jun 2026 12:08:43 +0800 +Subject: [PATCH] eventpoll: Fix integer overflow in ep_loop_check_proc() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit fdcfce93073d990ed4b71752e31ad1c1d6e9d58b +Author: Jann Horn +Date: Mon Feb 23 20:59:33 2026 +0100 + + eventpoll: Fix integer overflow in ep_loop_check_proc() + + If a recursive call to ep_loop_check_proc() hits the `result = INT_MAX`, + an integer overflow will occur in the calling ep_loop_check_proc() at + `result = max(result, ep_loop_check_proc(ep_tovisit, depth + 1) + 1)`, + breaking the recursion depth check. + + Fix it by using a different placeholder value that can't lead to an + overflow. + + Reported-by: Guenter Roeck + Fixes: f2e467a48287 ("eventpoll: Fix semi-unbounded recursion") + Cc: stable@vger.kernel.org + Signed-off-by: Jann Horn + Link: https://patch.msgid.link/20260223-epoll-int-overflow-v1-1-452f35132224@google.com + Signed-off-by: Christian Brauner + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 5228ff9c8742..cb21dfe9ee0a 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -2130,7 +2130,8 @@ static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, + * @ep: the &struct eventpoll to be currently checked. + * @depth: Current depth of the path being checked. + * +- * Return: depth of the subtree, or INT_MAX if we found a loop or went too deep. ++ * Return: depth of the subtree, or a value bigger than EP_MAX_NESTS if we found ++ * a loop or went too deep. + */ + static int ep_loop_check_proc(struct eventpoll *ep, int depth) + { +@@ -2149,7 +2150,7 @@ static int ep_loop_check_proc(struct eventpoll *ep, int depth) + struct eventpoll *ep_tovisit; + ep_tovisit = epi->ffd.file->private_data; + if (ep_tovisit == inserting_into || depth > EP_MAX_NESTS) +- result = INT_MAX; ++ result = EP_MAX_NESTS+1; + else + result = max(result, ep_loop_check_proc(ep_tovisit, depth + 1) + 1); + if (result > EP_MAX_NESTS) +-- +2.50.1 (Apple Git-155) + diff --git a/SOURCES/1755-eventpoll-refresh-epi-fget-ep-remove-file-comments.patch b/SOURCES/1755-eventpoll-refresh-epi-fget-ep-remove-file-comments.patch new file mode 100644 index 000000000..bc6f290ea --- /dev/null +++ b/SOURCES/1755-eventpoll-refresh-epi-fget-ep-remove-file-comments.patch @@ -0,0 +1,101 @@ +From 39c9ab147e5bc9cf20d146d78c59afc7a0ab488d Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Thu, 18 Jun 2026 11:30:35 +0800 +Subject: [PATCH] eventpoll: refresh epi_fget() / ep_remove_file() comments + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linux + +commit a573cb40f9819c3fe81a43eb10170f8fc8eddc5e +Author: Christian Brauner +Date: Fri Apr 24 15:46:35 2026 +0200 + + eventpoll: refresh epi_fget() / ep_remove_file() comments + + Two comments drifted from the code they sit on. + + epi_fget()'s block comment still referenced atomic_long_inc_not_zero, + which has been file_ref_get() for a while, and described only one of + the function's two roles: safe dereference of epi->ffd.file under + ep->mtx. Since commit a6dc643c6931 ("eventpoll: fix ep_remove struct + eventpoll / struct file UAF") the refcount bump also serves as a pin + that blocks __fput() from starting, which is what lets ep_remove() + touch file->f_lock and file->f_ep without racing + eventpoll_release_file(). Update the block to name both roles and the + commit that introduced the pin role. + + ep_remove_file()'s one-line "See eventpoll_release() for details" + pointed at an inline in include/linux/eventpoll.h but said nothing + about what those details were. Replace it with a short explanation: + we publish NULL so the eventpoll_release() fastpath can skip the slow + path, and this is safe because every f_ep writer either holds a pin + via epi_fget() or is __fput() itself. + + Comment-only; no functional change. + + Signed-off-by: Christian Brauner (Amutable) + Link: https://patch.msgid.link/20260424-work-epoll-rework-v1-4-249ed00a20f3@kernel.org + Signed-off-by: Christian Brauner + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index cb21dfe9ee0a..9bde520ac74b 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -817,22 +817,23 @@ 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. ++ * Pin @epi->ffd.file for operations that require both safe dereference ++ * and exclusion from __fput(). + * +- * 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). ++ * struct file uses SLAB_TYPESAFE_BY_RCU, so a freed slot can be ++ * reassigned at any time. The bare load of epi->ffd.file is safe here ++ * because the caller holds ep->mtx and eventpoll_release_file() blocks ++ * on that mutex while tearing down the epi, so the backing file ++ * allocation cannot be freed and reused under us. An rcu_read_lock() ++ * is therefore unnecessary for the load. + * +- * 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). ++ * A successful file_ref_get() additionally blocks __fput() from ++ * starting on this file: once the refcount has reached zero it cannot ++ * come back. ep_remove() relies on that to touch file->f_lock and ++ * file->f_ep without racing eventpoll_release_file() (see commit ++ * a6dc643c6931). A NULL return means __fput() is already in flight; ++ * the caller must bail without touching the file, and ++ * eventpoll_release_file() will clean the epi up from its side. + */ + static struct file *epi_fget(const struct epitem *epi) + { +@@ -858,7 +859,13 @@ static void ep_remove_file(struct eventpoll *ep, struct epitem *epi, + spin_lock(&file->f_lock); + head = file->f_ep; + if (hlist_is_singular_node(&epi->fllink, head)) { +- /* See eventpoll_release() for details. */ ++ /* ++ * Last watcher: publish NULL so the eventpoll_release() ++ * fastpath in include/linux/eventpoll.h can skip the slow ++ * path on a future __fput(). Safe because every f_ep writer ++ * either holds a pin on @file via epi_fget() or is __fput() ++ * itself -- see the comment in eventpoll_release(). ++ */ + WRITE_ONCE(file->f_ep, NULL); + if (!is_file_epoll(file)) { + struct epitems_head *v; +-- +2.50.1 (Apple Git-155) + diff --git a/SPECS/kernel.spec b/SPECS/kernel.spec index 08dc64710..4c5c00661 100644 --- a/SPECS/kernel.spec +++ b/SPECS/kernel.spec @@ -176,13 +176,13 @@ Summary: The Linux kernel # define buildid .local %define specversion 5.14.0 %define patchversion 5.14 -%define pkgrelease 687.22.2 +%define pkgrelease 687.23.1 %define kversion 5 %define tarfile_release 5.14.0-687.5.1.el9_8 # This is needed to do merge window version magic %define patchlevel 14 # This allows pkg_release to have configurable %%{?dist} tag -%define specrelease 687.22.2%{?buildid}%{?dist} +%define specrelease 687.23.1%{?buildid}%{?dist} # This defines the kabi tarball version %define kabiversion 5.14.0-687.5.1.el9_8 @@ -1589,7 +1589,6 @@ Patch1708: 1708-crypto-krb5enc-fix-async-decrypt-skipping-hash-verification.patc Patch1709: 1709-crypto-krb5-filter-out-async-aead-implementations-at-alloc.patch Patch1710: 1710-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-gfn.patch Patch1711: 1711-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-role.patch -Patch1712: 1712-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch Patch1713: 1713-nouveau-gsp-drop-warn-on-in-acpi-probes.patch Patch1714: 1714-sctp-revalidate-list-cursor-after-sctp-sendmsg-to-asoc-in-sc.patch Patch1715: 1715-drm-gem-fix-inconsistent-plane-dimension-calculation-in-drm.patch @@ -1612,8 +1611,23 @@ Patch1732: 1732-net-gro-don-t-merge-zcopy-skbs.patch Patch1733: 1733-xfrm-defensively-unhash-xfrm-state-lists-in-xfrm-state-delet.patch Patch1734: 1734-smb-client-fix-off-by-8-bounds-check-in-check-wsl-eas.patch Patch1735: 1735-smb-client-fix-out-of-bounds-read-in-smb2-compound-op.patch -Patch1736: 1736-rtmutex-use-waiter-task-in-remove-waiter.patch -Patch1738: 1738-rtmutex-skip-remove-waiter-when-not-enqueued.patch +Patch1739: 1739-tg3-fix-race-for-querying-speed-duplex.patch +Patch1740: 1740-netfilter-bridge-make-ebt-snat-arp-rewrite-writable.patch +Patch1741: 1741-epoll-annotate-racy-check.patch +Patch1742: 1742-eventpoll-defer-struct-eventpoll-free-to-rcu-grace-period.patch +Patch1743: 1743-eventpoll-use-hlist-is-singular-node-in-ep-remove.patch +Patch1744: 1744-eventpoll-split-ep-remove.patch +Patch1745: 1745-eventpoll-kill-ep-remove.patch +Patch1746: 1746-eventpoll-rename-ep-remove-safe-back-to-ep-remove.patch +Patch1747: 1747-eventpoll-move-epi-fget-up.patch +Patch1748: 1748-eventpoll-drop-vestigial-prefix-from-ep-remove-file-epi.patch +Patch1749: 1749-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch +Patch1750: 1750-eventpoll-move-f-lock-acquisition-into-ep-remove-file.patch +Patch1751: 1751-eventpoll-refresh-eventpoll-release-fast-path-comment.patch +Patch1752: 1752-eventpoll-drop-dead-bool-return-from-ep-remove-epi.patch +Patch1753: 1753-eventpoll-drop-vestigial-epi-dying-flag.patch +Patch1754: 1754-eventpoll-fix-integer-overflow-in-ep-loop-check-proc.patch +Patch1755: 1755-eventpoll-refresh-epi-fget-ep-remove-file-comments.patch # END OF PATCH DEFINITIONS %description @@ -2970,7 +2984,6 @@ ApplyPatch 1708-crypto-krb5enc-fix-async-decrypt-skipping-hash-verification.patc ApplyPatch 1709-crypto-krb5-filter-out-async-aead-implementations-at-alloc.patch ApplyPatch 1710-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-gfn.patch ApplyPatch 1711-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-role.patch -ApplyPatch 1712-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch ApplyPatch 1713-nouveau-gsp-drop-warn-on-in-acpi-probes.patch ApplyPatch 1714-sctp-revalidate-list-cursor-after-sctp-sendmsg-to-asoc-in-sc.patch ApplyPatch 1715-drm-gem-fix-inconsistent-plane-dimension-calculation-in-drm.patch @@ -2993,8 +3006,23 @@ ApplyPatch 1732-net-gro-don-t-merge-zcopy-skbs.patch ApplyPatch 1733-xfrm-defensively-unhash-xfrm-state-lists-in-xfrm-state-delet.patch ApplyPatch 1734-smb-client-fix-off-by-8-bounds-check-in-check-wsl-eas.patch ApplyPatch 1735-smb-client-fix-out-of-bounds-read-in-smb2-compound-op.patch -ApplyPatch 1736-rtmutex-use-waiter-task-in-remove-waiter.patch -ApplyPatch 1738-rtmutex-skip-remove-waiter-when-not-enqueued.patch +ApplyPatch 1739-tg3-fix-race-for-querying-speed-duplex.patch +ApplyPatch 1740-netfilter-bridge-make-ebt-snat-arp-rewrite-writable.patch +ApplyPatch 1741-epoll-annotate-racy-check.patch +ApplyPatch 1742-eventpoll-defer-struct-eventpoll-free-to-rcu-grace-period.patch +ApplyPatch 1743-eventpoll-use-hlist-is-singular-node-in-ep-remove.patch +ApplyPatch 1744-eventpoll-split-ep-remove.patch +ApplyPatch 1745-eventpoll-kill-ep-remove.patch +ApplyPatch 1746-eventpoll-rename-ep-remove-safe-back-to-ep-remove.patch +ApplyPatch 1747-eventpoll-move-epi-fget-up.patch +ApplyPatch 1748-eventpoll-drop-vestigial-prefix-from-ep-remove-file-epi.patch +ApplyPatch 1749-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch +ApplyPatch 1750-eventpoll-move-f-lock-acquisition-into-ep-remove-file.patch +ApplyPatch 1751-eventpoll-refresh-eventpoll-release-fast-path-comment.patch +ApplyPatch 1752-eventpoll-drop-dead-bool-return-from-ep-remove-epi.patch +ApplyPatch 1753-eventpoll-drop-vestigial-epi-dying-flag.patch +ApplyPatch 1754-eventpoll-fix-integer-overflow-in-ep-loop-check-proc.patch +ApplyPatch 1755-eventpoll-refresh-epi-fget-ep-remove-file-comments.patch # END OF PATCH APPLICATIONS # Any further pre-build tree manipulations happen here. @@ -5069,6 +5097,32 @@ fi # # %changelog +* Wed Jul 08 2026 Andrew Lukoshko - 5.14.0-687.23.1 +- Recreate RHEL 5.14.0-687.23.1 from CentOS Stream and upstream stable backports (1739-1755) +- The AlmaLinux ahead-of-RHEL eventpoll CVE-2026-46242 fix (1712) is superseded by + RHEL's eventpoll series carrying the same fix and is dropped +- Temporarily drop the rtmutex remove_waiter() fixes (1736, 1738) +- RHEL changelog for 687.23.1 follows: + +* Mon Jul 06 2026 CKI KWF Bot [5.14.0-687.23.1.el9_8] +- eventpoll: refresh epi_fget() / ep_remove_file() comments (Ian Kent) [RHEL-180773] +- eventpoll: Fix integer overflow in ep_loop_check_proc() (Ian Kent) [RHEL-180773] +- eventpoll: drop vestigial epi->dying flag (Ian Kent) [RHEL-180773] +- eventpoll: drop dead bool return from ep_remove_epi() (Ian Kent) [RHEL-180773] +- eventpoll: refresh eventpoll_release() fast-path comment (Ian Kent) [RHEL-180773] +- eventpoll: move f_lock acquisition into ep_remove_file() (Ian Kent) [RHEL-180773] +- eventpoll: fix ep_remove struct eventpoll / struct file UAF (Ian Kent) [RHEL-180773] {CVE-2026-46242} +- eventpoll: drop vestigial __ prefix from ep_remove_{file,epi}() (Ian Kent) [RHEL-180773] +- eventpoll: move epi_fget() up (Ian Kent) [RHEL-180773] +- eventpoll: rename ep_remove_safe() back to ep_remove() (Ian Kent) [RHEL-180773] +- eventpoll: kill __ep_remove() (Ian Kent) [RHEL-180773] +- eventpoll: split __ep_remove() (Ian Kent) [RHEL-180773] +- eventpoll: use hlist_is_singular_node() in __ep_remove() (Ian Kent) [RHEL-180773] +- eventpoll: defer struct eventpoll free to RCU grace period (Ian Kent) [RHEL-173830] {CVE-2026-43074} +- epoll: annotate racy check (Ian Kent) [RHEL-180773] +- netfilter: bridge: make ebt_snat ARP rewrite writable (CKI Backport Bot) [RHEL-182344] +- tg3: Fix race for querying speed/duplex (CKI Backport Bot) [RHEL-182768] + * Tue Jul 08 2026 Andrew Lukoshko - 5.14.0-687.22.2 - Fix rtmutex self-deadlock NULL pointer dereference in remove_waiter() ahead of RHEL, upstream 3bfdc63936dd + 40a25d59e85b (1736, 1738); the futex_requeue guard