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:
parent
819eaa69a5
commit
2042c1b105
@ -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
|
||||
|
||||
@ -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(¤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
|
||||
|
||||
@ -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
|
||||
|
||||
35
SOURCES/1739-tg3-fix-race-for-querying-speed-duplex.patch
Normal file
35
SOURCES/1739-tg3-fix-race-for-querying-speed-duplex.patch
Normal 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)
|
||||
|
||||
@ -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)
|
||||
|
||||
66
SOURCES/1741-epoll-annotate-racy-check.patch
Normal file
66
SOURCES/1741-epoll-annotate-racy-check.patch
Normal 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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
84
SOURCES/1744-eventpoll-split-ep-remove.patch
Normal file
84
SOURCES/1744-eventpoll-split-ep-remove.patch
Normal 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)
|
||||
|
||||
134
SOURCES/1745-eventpoll-kill-ep-remove.patch
Normal file
134
SOURCES/1745-eventpoll-kill-ep-remove.patch
Normal 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)
|
||||
|
||||
@ -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)
|
||||
|
||||
95
SOURCES/1747-eventpoll-move-epi-fget-up.patch
Normal file
95
SOURCES/1747-eventpoll-move-epi-fget-up.patch
Normal 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()
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
110
SOURCES/1753-eventpoll-drop-vestigial-epi-dying-flag.patch
Normal file
110
SOURCES/1753-eventpoll-drop-vestigial-epi-dying-flag.patch
Normal 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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user