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)
This commit is contained in:
Andrew Lukoshko 2026-07-08 15:20:58 +00:00
parent 819eaa69a5
commit 2042c1b105
21 changed files with 1397 additions and 274 deletions

View File

@ -1,99 +0,0 @@
From: Christian Brauner <brauner@kernel.org>
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 <jjy600901@snu.ac.kr>
Signed-off-by: Christian Brauner <brauner@kernel.org>
---
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

View File

@ -1,90 +0,0 @@
From 3bfdc63936dd4773109b7b8c280c0f3b5ae7d349 Mon Sep 17 00:00:00 2001
From: Keenan Dong <keenanat2000@gmail.com>
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 <yuantan098@gmail.com>
Reported-by: Yifan Wu <yifanwucs@gmail.com>
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Reported-by: Xin Liu <bird@lzu.edu.cn>
Signed-off-by: Keenan Dong <keenanat2000@gmail.com>
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
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(&current->pi_lock);
- rt_mutex_dequeue(lock, waiter);
- current->pi_blocked_on = NULL;
- raw_spin_unlock(&current->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

View File

@ -1,77 +0,0 @@
From 40a25d59e85b3c8709ac2424d44f65610467871e Mon Sep 17 00:00:00 2001
From: Davidlohr Bueso <dave@stgolabs.net>
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 <dave@stgolabs.net>
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
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

View File

@ -0,0 +1,35 @@
From 38b778cd8963336ed70b618be269b65ffeb0ae42 Mon Sep 17 00:00:00 2001
From: Thomas Bogendoerfer <tbogendoerfer@suse.de>
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 <tbogendoerfer@suse.de>
Reviewed-by: Pavan Chebbi <pavan.chebbi@broadcom.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Sasha Levin <sashal@kernel.org>
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)

View File

@ -0,0 +1,53 @@
From 76280b78cc9f23bdc6438e10ad6dff148ef8375b Mon Sep 17 00:00:00 2001
From: Yiming Qian <yimingqian591@gmail.com>
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 <yimingqian591@gmail.com>
Signed-off-by: Yiming Qian <yimingqian591@gmail.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
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)

View File

@ -0,0 +1,66 @@
From bde1be0c6e5eeb145576344159be1f51cb8d5d8b Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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 <jack@suse.cz>
Reported-by: syzbot+3b6b32dc50537a49bb4a@syzkaller.appspotmail.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,53 @@
From 2b62eb17ec16b6a7d341497e48e331f84280cd58 Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <nicholas@carlini.com>
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 <nicholas@carlini.com>
Signed-off-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,39 @@
From 1fe6204c6ea38fc1d370d9bbb9fd6a800104e5c8 Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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) <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,84 @@
From 83db11e5ba3c57104945247e11c1891355a12fb6 Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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 <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>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,134 @@
From 9a9a41f8aa46741f965703f520542e9e8fdbe59f Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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) <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,98 @@
From 4e16f36e52dfe03056ec8c4daaef79b1715bb341 Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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) <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,95 @@
From 8c1707336e5674c16c2a24c9cdf2b5018b166a09 Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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) <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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()

View File

@ -0,0 +1,78 @@
From 6175a9a692e559e7f0635d3a32d20fe94bae8280 Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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) <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,104 @@
From 5c53e2d149e55d3ee64fb03387d2f81d59477774 Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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 <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>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,70 @@
From e0bf480728b83ec6ceb66975bfbac514195873c8 Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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) <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,66 @@
From eab8e8be1be2430b6712fc1a6c8925d240a40031 Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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) <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,92 @@
From 38a78d49b434ce9f29afdfaa870142ecb121800f Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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) <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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;
}

View File

@ -0,0 +1,110 @@
From 3724dc6414c5da5c393e58e0e5863fdb95166922 Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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) <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,57 @@
From 2eb0a1e08ebad5be00433093449abf025450ca6d Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <jannh@google.com>
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 <linux@roeck-us.net>
Fixes: f2e467a48287 ("eventpoll: Fix semi-unbounded recursion")
Cc: stable@vger.kernel.org
Signed-off-by: Jann Horn <jannh@google.com>
Link: https://patch.msgid.link/20260223-epoll-int-overflow-v1-1-452f35132224@google.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -0,0 +1,101 @@
From 39c9ab147e5bc9cf20d146d78c59afc7a0ab488d Mon Sep 17 00:00:00 2001
From: Ian Kent <ikent@redhat.com>
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 <brauner@kernel.org>
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) <brauner@kernel.org>
Link: https://patch.msgid.link/20260424-work-epoll-rework-v1-4-249ed00a20f3@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Ian Kent <ikent@redhat.com>
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)

View File

@ -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 <alukoshko@almalinux.org> - 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 <cki-ci-bot+kwf-gitlab-com@redhat.com> [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 <alukoshko@almalinux.org> - 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