diff --git a/1417-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch b/1417-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch deleted file mode 100644 index 223e93645..000000000 --- a/1417-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch +++ /dev/null @@ -1,99 +0,0 @@ -From: Christian Brauner -Date: Thu, 23 Apr 2026 11:56:09 +0200 -Subject: [PATCH] eventpoll: fix ep_remove struct eventpoll / struct file UAF - -CVE: CVE-2026-46242 - -Upstream commit a6dc643c69311677c574a0f17a3f4d66a5f3744b, adapted to the -pre-e9e5cd40d7c4 ("eventpoll: kill __ep_remove()") code shape still used -by this kernel, where the removal path is the monolithic __ep_remove(ep, -epi, force) rather than upstream's ep_remove()/ep_remove_epi()/ -ep_remove_file() split. - -ep_remove() used epi->ffd.file across the file->f_lock critical section -without holding a reference. A concurrent __fput() taking the -eventpoll_release() fastpath could free the file (and, for the -epoll-watches-epoll case, the watched struct eventpoll) mid-section, -leading to a use-after-free / attacker-controllable kmem_cache_free(). - -Pin the file with epi_fget() before entering the critical section and -release it afterwards. A failed pin means __fput() is already in flight -and will remove the epi via eventpoll_release_file() (which blocks on -ep->mtx), so it is safe to bail. The pin is taken only on the !force -path: the force path is reached from eventpoll_release_file() itself, -i.e. from within __fput() where f_count has already dropped to zero and -epi_fget() would necessarily fail; there @file is still valid and no -concurrent __fput() can race, so it keeps using epi->ffd.file directly. -A successful pin also makes the in-lock epi->dying recheck redundant; the -cheap lockless READ_ONCE(epi->dying) fast-path bailout is kept. - -Fixes: 58c9b016e128 ("epoll: use refcount to reduce ep_mutex contention") -Reported-by: Jaeyoung Chung -Signed-off-by: Christian Brauner ---- -diff --git a/fs/eventpoll.c b/fs/eventpoll.c -index f4efb5b..68e58d0 100644 ---- a/fs/eventpoll.c -+++ b/fs/eventpoll.c -@@ -821,6 +821,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. -@@ -832,6 +834,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; - -@@ -842,13 +845,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) { -@@ -882,6 +899,8 @@ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) - kfree_rcu(epi, rcu); - - percpu_counter_dec(&ep->user->epoll_watches); -+ if (to_put) -+ fput(to_put); - return true; - } - --- -2.43.0 - diff --git a/1418-rtmutex-use-waiter-task-in-remove-waiter.patch b/1418-rtmutex-use-waiter-task-in-remove-waiter.patch deleted file mode 100644 index fa94e316c..000000000 --- a/1418-rtmutex-use-waiter-task-in-remove-waiter.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 3bfdc63936dd4773109b7b8c280c0f3b5ae7d349 Mon Sep 17 00:00:00 2001 -From: Keenan Dong -Date: Wed, 8 Apr 2026 16:46:00 +0800 -Subject: [PATCH] rtmutex: Use waiter::task instead of current in - remove_waiter() - -remove_waiter() is used by the slowlock paths, but it is also used for -proxy-lock rollback in rt_mutex_start_proxy_lock() when invoked from -futex_requeue(). - -In the latter case waiter::task is not current, but remove_waiter() -operates on current for the dequeue operation. That results in several -problems: - - 1) the rbtree dequeue happens without waiter::task::pi_lock being held - - 2) the waiter task's pi_blocked_on state is not cleared, which leaves a - dangling pointer primed for UAF around. - - 3) rt_mutex_adjust_prio_chain() operates on the wrong top priority waiter - task - -Use waiter::task instead of current in all related operations in -remove_waiter() to cure those problems. - -[ tglx: Fixup rt_mutex_adjust_prio_chain(), add a comment and amend the - changelog ] - -Fixes: 8161239a8bcc ("rtmutex: Simplify PI algorithm and make highest prio task get lock") -Reported-by: Yuan Tan -Reported-by: Yifan Wu -Reported-by: Juefei Pu -Reported-by: Xin Liu -Signed-off-by: Keenan Dong -Signed-off-by: Thomas Gleixner -Cc: stable@vger.kernel.org - -(cherry picked from commit 3bfdc63936dd4773109b7b8c280c0f3b5ae7d349) -[AlmaLinux: adapted to a10 (6.12); context/line offsets only. scoped_guard(raw_spinlock, ...) is available in this tree (include/linux/cleanup.h + DEFINE_LOCK_GUARD_1 raw_spinlock in include/linux/spinlock.h), so the upstream form is used verbatim.] ---- - 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 4a8df18..e6f305d 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.50.1 diff --git a/1420-rtmutex-skip-remove-waiter-when-not-enqueued.patch b/1420-rtmutex-skip-remove-waiter-when-not-enqueued.patch deleted file mode 100644 index b692b6c4b..000000000 --- a/1420-rtmutex-skip-remove-waiter-when-not-enqueued.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 40a25d59e85b3c8709ac2424d44f65610467871e Mon Sep 17 00:00:00 2001 -From: Davidlohr Bueso -Date: Thu, 7 May 2026 04:29:13 -0700 -Subject: [PATCH] locking/rtmutex: Skip remove_waiter() when waiter is not - enqueued - -syzbot triggered the following splat in remove_waiter() via -FUTEX_CMP_REQUEUE_PI: - - KASAN: null-ptr-deref in range [0x0000000000000a88-0x0000000000000a8f] - class_raw_spinlock_constructor - remove_waiter+0x159/0x1200 kernel/locking/rtmutex.c:1561 - rt_mutex_start_proxy_lock+0x103/0x120 - futex_requeue+0x10e4/0x20d0 - __x64_sys_futex+0x34f/0x4d0 - -task_blocks_on_rt_mutex() does not arm the waiter upon deadlock detection, -leaving waiter->task nil, where 3bfdc63936dd ("rtmutex: Use waiter::task instead -of current in remove_waiter()") made this fatal. - -Furthermore, rt_mutex_start_proxy_lock() should not be calling into remove_waiter() -upon a successfully grabbing the rtmutex. 1a1fb985f2e2 ("futex: Handle early deadlock -return correctly"), moved the remove_waiter() out of __rt_mutex_start_proxy_lock() -(where 'ret' was only ever 0 or < 0) into the wrapper. Tighten this check to -account for try_to_take_rt_mutex(). - -Fixes: 3bfdc63936dd ("rtmutex: Use waiter::task instead of current in remove_waiter()") -Reported-by: syzbot+78147abe6c524f183ee9@syzkaller.appspotmail.com -Signed-off-by: Davidlohr Bueso -Signed-off-by: Thomas Gleixner -Cc: stable@vger.kernel.org -Closes: https://lore.kernel.org/all/69f114ac.050a0220.ac8b.0003.GAE@google.com/ -Link: https://patch.msgid.link/20260507112913.1019537-1-dave@stgolabs.net - -(cherry picked from commit 40a25d59e85b3c8709ac2424d44f65610467871e) -[AlmaLinux: adapted to a10 (6.12); context/line offsets only. Both hunks apply: remove_waiter() in kernel/locking/rtmutex.c (depends on waiter_task local from 3bfdc63936dd) and rt_mutex_start_proxy_lock() in kernel/locking/rtmutex_api.c. In this tree __rt_mutex_start_proxy_lock() returns 1 on try_to_take_rt_mutex() success, so the ret<0 tightening is required to avoid calling remove_waiter() on lock acquisition.] ---- - 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 e6f305d..15111dc 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 6614ccd..341a540 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.50.1 diff --git a/1421-net-page-pool-avoid-false-positive-warning-if-napi-was-never.patch b/1421-net-page-pool-avoid-false-positive-warning-if-napi-was-never.patch new file mode 100644 index 000000000..8167c50b9 --- /dev/null +++ b/1421-net-page-pool-avoid-false-positive-warning-if-napi-was-never.patch @@ -0,0 +1,71 @@ +From 942ef6b142b4e78e63c115e806f67ab31b8106d6 Mon Sep 17 00:00:00 2001 +From: CKI Backport Bot +Date: Mon, 30 Mar 2026 08:07:00 +0000 +Subject: [PATCH] net: page_pool: avoid false positive warning if NAPI was + never added + +JIRA: https://redhat.atlassian.net/browse/RHEL-162141 + +commit c1e00bc4be06cacee6307cedb9b55bbaddb5044d +Author: Jakub Kicinski +Date: Thu Feb 6 14:56:37 2025 -0800 + + net: page_pool: avoid false positive warning if NAPI was never added + + We expect NAPI to be in disabled state when page pool is torn down. + But it is also legal if the NAPI is completely uninitialized. + + Reviewed-by: Mina Almasry + Link: https://patch.msgid.link/20250206225638.1387810-4-kuba@kernel.org + Signed-off-by: Jakub Kicinski + +Signed-off-by: CKI Backport Bot + +diff --git a/net/core/dev.h b/net/core/dev.h +index ddf018a0d..f4fc420f7 100644 +--- a/net/core/dev.h ++++ b/net/core/dev.h +@@ -347,6 +347,18 @@ void xdp_do_check_flushed(struct napi_struct *napi); + static inline void xdp_do_check_flushed(struct napi_struct *napi) { } + #endif + ++/* Best effort check that NAPI is not idle (can't be scheduled to run) */ ++static inline void napi_assert_will_not_race(const struct napi_struct *napi) ++{ ++ /* uninitialized instance, can't race */ ++ if (!napi->poll_list.next) ++ return; ++ ++ /* SCHED bit is set on disabled instances */ ++ WARN_ON(!test_bit(NAPI_STATE_SCHED, &napi->state)); ++ WARN_ON(READ_ONCE(napi->list_owner) != -1); ++} ++ + void kick_defer_list_purge(struct softnet_data *sd, unsigned int cpu); + + #define XMIT_RECURSION_LIMIT 8 +diff --git a/net/core/page_pool.c b/net/core/page_pool.c +index 06531b7b7..15b08364d 100644 +--- a/net/core/page_pool.c ++++ b/net/core/page_pool.c +@@ -26,6 +26,7 @@ + + #include + ++#include RH_KABI_HIDE_INCLUDE("dev.h") + #include "mp_dmabuf_devmem.h" + #include "netmem_priv.h" + #include "page_pool_priv.h" +@@ -1196,11 +1197,7 @@ void page_pool_disable_direct_recycling(struct page_pool *pool) + if (!pool->p.napi) + return; + +- /* To avoid races with recycling and additional barriers make sure +- * pool and NAPI are unlinked when NAPI is disabled. +- */ +- WARN_ON(!test_bit(NAPI_STATE_SCHED, &pool->p.napi->state)); +- WARN_ON(READ_ONCE(pool->p.napi->list_owner) != -1); ++ napi_assert_will_not_race(pool->p.napi); + + mutex_lock(&page_pools_lock); + WRITE_ONCE(pool->p.napi, NULL); diff --git a/1422-net-mana-fix-double-destroy-workqueue-on-service-rescan-pci.patch b/1422-net-mana-fix-double-destroy-workqueue-on-service-rescan-pci.patch new file mode 100644 index 000000000..47e9a3bd6 --- /dev/null +++ b/1422-net-mana-fix-double-destroy-workqueue-on-service-rescan-pci.patch @@ -0,0 +1,82 @@ +From f975a0955276579e2176a134366ed586071c7c6a Mon Sep 17 00:00:00 2001 +From: Dipayaan Roy +Date: Tue, 24 Feb 2026 04:38:36 -0800 +Subject: [PATCH] net: mana: Fix double destroy_workqueue on service rescan PCI + path + +While testing corner cases in the driver, a use-after-free crash +was found on the service rescan PCI path. + +When mana_serv_reset() calls mana_gd_suspend(), mana_gd_cleanup() +destroys gc->service_wq. If the subsequent mana_gd_resume() fails +with -ETIMEDOUT or -EPROTO, the code falls through to +mana_serv_rescan() which triggers pci_stop_and_remove_bus_device(). +This invokes the PCI .remove callback (mana_gd_remove), which calls +mana_gd_cleanup() a second time, attempting to destroy the already- +freed workqueue. Fix this by NULL-checking gc->service_wq in +mana_gd_cleanup() and setting it to NULL after destruction. + +Call stack of issue for reference: +[Sat Feb 21 18:53:48 2026] Call Trace: +[Sat Feb 21 18:53:48 2026] +[Sat Feb 21 18:53:48 2026] mana_gd_cleanup+0x33/0x70 [mana] +[Sat Feb 21 18:53:48 2026] mana_gd_remove+0x3a/0xc0 [mana] +[Sat Feb 21 18:53:48 2026] pci_device_remove+0x41/0xb0 +[Sat Feb 21 18:53:48 2026] device_remove+0x46/0x70 +[Sat Feb 21 18:53:48 2026] device_release_driver_internal+0x1e3/0x250 +[Sat Feb 21 18:53:48 2026] device_release_driver+0x12/0x20 +[Sat Feb 21 18:53:48 2026] pci_stop_bus_device+0x6a/0x90 +[Sat Feb 21 18:53:48 2026] pci_stop_and_remove_bus_device+0x13/0x30 +[Sat Feb 21 18:53:48 2026] mana_do_service+0x180/0x290 [mana] +[Sat Feb 21 18:53:48 2026] mana_serv_func+0x24/0x50 [mana] +[Sat Feb 21 18:53:48 2026] process_one_work+0x190/0x3d0 +[Sat Feb 21 18:53:48 2026] worker_thread+0x16e/0x2e0 +[Sat Feb 21 18:53:48 2026] kthread+0xf7/0x130 +[Sat Feb 21 18:53:48 2026] ? __pfx_worker_thread+0x10/0x10 +[Sat Feb 21 18:53:48 2026] ? __pfx_kthread+0x10/0x10 +[Sat Feb 21 18:53:48 2026] ret_from_fork+0x269/0x350 +[Sat Feb 21 18:53:48 2026] ? __pfx_kthread+0x10/0x10 +[Sat Feb 21 18:53:48 2026] ret_from_fork_asm+0x1a/0x30 +[Sat Feb 21 18:53:48 2026] + +Fixes: 505cc26bcae0 ("net: mana: Add support for auxiliary device servicing events") +Reviewed-by: Haiyang Zhang +Signed-off-by: Dipayaan Roy +Reviewed-by: Simon Horman +Link: https://patch.msgid.link/aZ2bzL64NagfyHpg@linuxonhyperv3.guj3yctzbm1etfxqx2vob5hsef.xx.internal.cloudapp.net +Signed-off-by: Jakub Kicinski + +diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c +index 0055c231acf6..3926d18f1840 100644 +--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c ++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c +@@ -1946,7 +1946,10 @@ static void mana_gd_cleanup(struct pci_dev *pdev) + + mana_gd_remove_irqs(pdev); + +- destroy_workqueue(gc->service_wq); ++ if (gc->service_wq) { ++ destroy_workqueue(gc->service_wq); ++ gc->service_wq = NULL; ++ } + dev_dbg(&pdev->dev, "mana gdma cleanup successful\n"); + } + +diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c +index 9b5a72ada5c4..f69e42651359 100644 +--- a/drivers/net/ethernet/microsoft/mana/mana_en.c ++++ b/drivers/net/ethernet/microsoft/mana/mana_en.c +@@ -3762,7 +3762,9 @@ void mana_rdma_remove(struct gdma_dev *gd) + } + + WRITE_ONCE(gd->rdma_teardown, true); +- flush_workqueue(gc->service_wq); ++ ++ if (gc->service_wq) ++ flush_workqueue(gc->service_wq); + + if (gd->adev) + remove_adev(gd); +-- +2.50.1 (Apple Git-155) + diff --git a/1423-net-mana-null-service-wq-on-setup-error-to-prevent-double-de.patch b/1423-net-mana-null-service-wq-on-setup-error-to-prevent-double-de.patch new file mode 100644 index 000000000..59712dd91 --- /dev/null +++ b/1423-net-mana-null-service-wq-on-setup-error-to-prevent-double-de.patch @@ -0,0 +1,33 @@ +From 87c2302813abc55c46485711a678e3c312b00666 Mon Sep 17 00:00:00 2001 +From: Shiraz Saleem +Date: Mon, 9 Mar 2026 10:24:43 -0700 +Subject: [PATCH] net/mana: Null service_wq on setup error to prevent double + destroy + +In mana_gd_setup() error path, set gc->service_wq to NULL after +destroy_workqueue() to match the cleanup in mana_gd_cleanup(). +This prevents a use-after-free if the workqueue pointer is checked +after a failed setup. + +Fixes: f975a0955276 ("net: mana: Fix double destroy_workqueue on service rescan PCI path") +Signed-off-by: Shiraz Saleem +Signed-off-by: Konstantin Taranov +Reviewed-by: Simon Horman +Link: https://patch.msgid.link/20260309172443.688392-1-kotaranov@linux.microsoft.com +Signed-off-by: Jakub Kicinski + +diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c +index 37d2f108a839..786186c9a115 100644 +--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c ++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c +@@ -1934,6 +1934,7 @@ static int mana_gd_setup(struct pci_dev *pdev) + mana_gd_remove_irqs(pdev); + free_workqueue: + destroy_workqueue(gc->service_wq); ++ gc->service_wq = NULL; + dev_err(&pdev->dev, "%s failed (error %d)\n", __func__, err); + return err; + } +-- +2.50.1 (Apple Git-155) + diff --git a/1424-net-ipv6-ioam6-prevent-schema-length-wraparound-in-trace-fil.patch b/1424-net-ipv6-ioam6-prevent-schema-length-wraparound-in-trace-fil.patch new file mode 100644 index 000000000..0ef00550f --- /dev/null +++ b/1424-net-ipv6-ioam6-prevent-schema-length-wraparound-in-trace-fil.patch @@ -0,0 +1,47 @@ +From 77695a69baca9b99d95fad09fc78c2318736604f Mon Sep 17 00:00:00 2001 +From: Pengpeng Hou +Date: Wed, 25 Mar 2026 15:41:52 +0800 +Subject: [PATCH] net/ipv6: ioam6: prevent schema length wraparound in trace + fill + +[ Upstream commit 5e67ba9bb531e1ec6599a82a065dea9040b9ce50 ] + +ioam6_fill_trace_data() stores the schema contribution to the trace +length in a u8. With bit 22 enabled and the largest schema payload, +sclen becomes 1 + 1020 / 4, wraps from 256 to 0, and bypasses the +remaining-space check. __ioam6_fill_trace_data() then positions the +write cursor without reserving the schema area but still copies the +4-byte schema header and the full schema payload, overrunning the trace +buffer. + +Keep sclen in an unsigned int so the remaining-space check and the write +cursor calculation both see the full schema length. + +Fixes: 8c6f6fa67726 ("ipv6: ioam: IOAM Generic Netlink API") +Signed-off-by: Pengpeng Hou +Reviewed-by: Justin Iurman +Signed-off-by: David S. Miller +Signed-off-by: Sasha Levin + +diff --git a/net/ipv6/ioam6.c b/net/ipv6/ioam6.c +index a4b7b60..bdffb79 100644 +--- a/net/ipv6/ioam6.c ++++ b/net/ipv6/ioam6.c +@@ -698,7 +698,7 @@ static void __ioam6_fill_trace_data(struct sk_buff *skb, + struct ioam6_namespace *ns, + struct ioam6_trace_hdr *trace, + struct ioam6_schema *sc, +- u8 sclen, bool is_input) ++ unsigned int sclen, bool is_input) + { + struct net_device *dev = skb_dst_dev(skb); + struct timespec64 ts; +@@ -929,7 +929,7 @@ void ioam6_fill_trace_data(struct sk_buff *skb, + bool is_input) + { + struct ioam6_schema *sc; +- u8 sclen = 0; ++ unsigned int sclen = 0; + + /* Skip if Overflow flag is set + */ diff --git a/1425-procfs-fix-missing-rcu-protection-when-reading-real-parent-i.patch b/1425-procfs-fix-missing-rcu-protection-when-reading-real-parent-i.patch new file mode 100644 index 000000000..12ab2dba7 --- /dev/null +++ b/1425-procfs-fix-missing-rcu-protection-when-reading-real-parent-i.patch @@ -0,0 +1,63 @@ +From 1d724378d2b1c0a475bc1743dbba105ded1b7a9d Mon Sep 17 00:00:00 2001 +From: CKI Backport Bot +Date: Wed, 3 Jun 2026 19:00:19 +0000 +Subject: [PATCH] procfs: fix missing RCU protection when reading real_parent + in do_task_stat() + +JIRA: https://redhat.atlassian.net/browse/RHEL-181905 +CVE: CVE-2026-46259 +Backported from tree(s): linux + +commit 76149d53502cf17ef3ae454ff384551236fba867 +Author: Jinliang Zheng +Date: Wed Jan 28 16:30:07 2026 +0800 + + procfs: fix missing RCU protection when reading real_parent in do_task_stat() + + When reading /proc/[pid]/stat, do_task_stat() accesses task->real_parent + without proper RCU protection, which leads to: + + cpu 0 cpu 1 + ----- ----- + do_task_stat + var = task->real_parent + release_task + call_rcu(delayed_put_task_struct) + task_tgid_nr_ns(var) + rcu_read_lock <--- Too late to protect task->real_parent! + task_pid_ptr <--- UAF! + rcu_read_unlock + + This patch uses task_ppid_nr_ns() instead of task_tgid_nr_ns() to add + proper RCU protection for accessing task->real_parent. + + Link: https://lkml.kernel.org/r/20260128083007.3173016-1-alexjlzheng@tencent.com + Fixes: 06fffb1267c9 ("do_task_stat: don't take rcu_read_lock()") + Signed-off-by: Jinliang Zheng + Acked-by: Oleg Nesterov + Cc: David Hildenbrand + Cc: Ingo Molnar + Cc: Lorenzo Stoakes + Cc: Mateusz Guzik + Cc: ruippan + Cc: Usama Arif + Signed-off-by: Andrew Morton + +Signed-off-by: CKI Backport Bot + +diff --git a/fs/proc/array.c b/fs/proc/array.c +index d6a0369caa93..11704686c097 100644 +--- a/fs/proc/array.c ++++ b/fs/proc/array.c +@@ -531,7 +531,7 @@ static int do_task_stat(struct seq_file *m, struct pid_namespace *ns, + } + + sid = task_session_nr_ns(task, ns); +- ppid = task_tgid_nr_ns(task->real_parent, ns); ++ ppid = task_ppid_nr_ns(task, ns); + pgid = task_pgrp_nr_ns(task, ns); + + unlock_task_sighand(task, &flags); +-- +2.50.1 (Apple Git-155) + diff --git a/1426-tg3-fix-race-for-querying-speed-duplex.patch b/1426-tg3-fix-race-for-querying-speed-duplex.patch new file mode 100644 index 000000000..0384a3f8a --- /dev/null +++ b/1426-tg3-fix-race-for-querying-speed-duplex.patch @@ -0,0 +1,42 @@ +From 61ea0d078a3ccf72afe38e00b891dbe48cf25c87 Mon Sep 17 00:00:00 2001 +From: Thomas Walsh +Date: Fri, 5 Jun 2026 17:21:58 -0400 +Subject: [PATCH] tg3: Fix race for querying speed/duplex + +JIRA: https://redhat.atlassian.net/browse/RHEL-182561 + +commit bb417456c7814d1493d98b7dd9c040bf3ce3b4ed +Author: Thomas Bogendoerfer +Date: Wed Mar 25 12:20:53 2026 +0100 + + tg3: Fix race for querying speed/duplex + + When driver signals carrier up via netif_carrier_on() its internal + link_up state isn't updated immediately. This leads to inconsistent + speed/duplex in /proc/net/bonding/bondX where the speed and duplex + is shown as unknown while ethtool shows correct values. Fix this by + using netif_carrier_ok() for link checking in get_ksettings function. + + Fixes: 84421b99cedc ("tg3: Update link_up flag for phylib devices") + Signed-off-by: Thomas Bogendoerfer + Reviewed-by: Pavan Chebbi + Signed-off-by: David S. Miller + +Signed-off-by: Thomas Walsh + +diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c +index 98abd0491abe..39754ee9d393 100644 +--- a/drivers/net/ethernet/broadcom/tg3.c ++++ b/drivers/net/ethernet/broadcom/tg3.c +@@ -12289,7 +12289,7 @@ static int tg3_get_link_ksettings(struct net_device *dev, + ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising, + advertising); + +- if (netif_running(dev) && tp->link_up) { ++ if (netif_running(dev) && netif_carrier_ok(dev)) { + cmd->base.speed = tp->link_config.active_speed; + cmd->base.duplex = tp->link_config.active_duplex; + ethtool_convert_legacy_u32_to_link_mode( +-- +2.50.1 (Apple Git-155) + diff --git a/1427-smb-client-fix-off-by-8-bounds-check-in-check-wsl-eas.patch b/1427-smb-client-fix-off-by-8-bounds-check-in-check-wsl-eas.patch new file mode 100644 index 000000000..6b8bf372b --- /dev/null +++ b/1427-smb-client-fix-off-by-8-bounds-check-in-check-wsl-eas.patch @@ -0,0 +1,62 @@ +From 14dc02b7c9eabd99199d9668ba1c0b4a29ed9a27 Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +Date: Wed, 1 Jul 2026 15:54:57 -0300 +Subject: [PATCH] smb: client: fix off-by-8 bounds check in check_wsl_eas() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180047 + +commit 3d8b9d06bd3ac4c6846f5498800b0f5f8062e53b +Author: Greg Kroah-Hartman +Date: Mon Apr 6 15:49:37 2026 +0200 + + smb: client: fix off-by-8 bounds check in check_wsl_eas() + + The bounds check uses (u8 *)ea + nlen + 1 + vlen as the end of the EA + name and value, but ea_data sits at offset sizeof(struct + smb2_file_full_ea_info) = 8 from ea, not at offset 0. The strncmp() + later reads ea->ea_data[0..nlen-1] and the value bytes follow at + ea_data[nlen+1..nlen+vlen], so the actual end is ea->ea_data + nlen + 1 + + vlen. Isn't pointer math fun? + + The earlier check (u8 *)ea > end - sizeof(*ea) only guarantees the + 8-byte header is in bounds, but since the last EA is placed within 8 + bytes of the end of the response, the name and value bytes are read past + the end of iov. + + Fix this mess all up by using ea->ea_data as the base for the bounds + check. + + An "untrusted" server can use this to leak up to 8 bytes of kernel heap + into the EA name comparison and influence which WSL xattr the data is + interpreted as. + + Cc: Ronnie Sahlberg + Cc: Shyam Prasad N + Cc: Tom Talpey + Cc: Bharath SM + Cc: linux-cifs@vger.kernel.org + Cc: samba-technical@lists.samba.org + Cc: stable + Assisted-by: gregkh_clanker_t1000 + Reviewed-by: Paulo Alcantara (Red Hat) + Signed-off-by: Greg Kroah-Hartman + Signed-off-by: Steve French + +Signed-off-by: Paulo Alcantara + +diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c +index ea05bb4b82a1..715ff58f1742 100644 +--- a/fs/smb/client/smb2inode.c ++++ b/fs/smb/client/smb2inode.c +@@ -128,7 +128,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) + nlen = ea->ea_name_length; + vlen = le16_to_cpu(ea->ea_value_length); + if (nlen != SMB2_WSL_XATTR_NAME_LEN || +- (u8 *)ea + nlen + 1 + vlen > end) ++ (u8 *)ea->ea_data + nlen + 1 + vlen > end) + return -EINVAL; + + switch (vlen) { +-- +2.50.1 (Apple Git-155) + diff --git a/1428-smb-client-fix-out-of-bounds-read-in-smb2-compound-op.patch b/1428-smb-client-fix-out-of-bounds-read-in-smb2-compound-op.patch new file mode 100644 index 000000000..c232a6405 --- /dev/null +++ b/1428-smb-client-fix-out-of-bounds-read-in-smb2-compound-op.patch @@ -0,0 +1,73 @@ +From b4c153a8f7b3cf32752c8c0a81193535507baba7 Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +Date: Wed, 1 Jul 2026 15:55:06 -0300 +Subject: [PATCH] smb/client: fix out-of-bounds read in smb2_compound_op() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180047 +CVE: CVE-2026-46155 + +commit 8d09328dfda089675e4c049f3f256064a1d1996b +Author: Zisen Ye +Date: Wed May 6 11:49:08 2026 +0800 + + smb/client: fix out-of-bounds read in smb2_compound_op() + + If a server sends a truncated response but a large OutputBufferLength, and + terminates the EA list early, check_wsl_eas() returns success without + validating that the entire OutputBufferLength fits within iov_len. + + Then smb2_compound_op() does: + memcpy(idata->wsl.eas, data[0], size[0]); + + Where size[0] is OutputBufferLength. If iov_len is smaller than size[0], + memcpy can read beyond the end of the rsp_iov allocation and leak adjacent + kernel heap memory. + + Link: https://lore.kernel.org/linux-cifs/d998240c-aca9-420d-9dbd-f5ba24af19e0@chenxiaosong.com/ + Fixes: ea41367b2a60 ("smb: client: introduce SMB2_OP_QUERY_WSL_EA") + Cc: stable@vger.kernel.org + Signed-off-by: Zisen Ye + Reviewed-by: ChenXiaoSong + Signed-off-by: Steve French + +Signed-off-by: Paulo Alcantara + +diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c +index 715ff58f1742..2cdd46228e31 100644 +--- a/fs/smb/client/smb2inode.c ++++ b/fs/smb/client/smb2inode.c +@@ -111,7 +111,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) + u32 outlen, next; + u16 vlen; + u8 nlen; +- u8 *end; ++ u8 *ea_end, *iov_end; + + outlen = le32_to_cpu(rsp->OutputBufferLength); + if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || +@@ -120,15 +120,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) + + ea = (void *)((u8 *)rsp_iov->iov_base + + le16_to_cpu(rsp->OutputBufferOffset)); +- end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; ++ ea_end = (u8 *)ea + outlen; ++ iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; ++ if (ea_end > iov_end) ++ return -EINVAL; ++ + for (;;) { +- if ((u8 *)ea > end - sizeof(*ea)) ++ if ((u8 *)ea > ea_end - sizeof(*ea)) + return -EINVAL; + + nlen = ea->ea_name_length; + vlen = le16_to_cpu(ea->ea_value_length); + if (nlen != SMB2_WSL_XATTR_NAME_LEN || +- (u8 *)ea->ea_data + nlen + 1 + vlen > end) ++ (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) + return -EINVAL; + + switch (vlen) { +-- +2.50.1 (Apple Git-155) + diff --git a/1429-epoll-annotate-racy-check.patch b/1429-epoll-annotate-racy-check.patch new file mode 100644 index 000000000..b698c2631 --- /dev/null +++ b/1429-epoll-annotate-racy-check.patch @@ -0,0 +1,66 @@ +From bde1be0c6e5eeb145576344159be1f51cb8d5d8b Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:53:22 +0800 +Subject: [PATCH] epoll: annotate racy check + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 6474353a5e3d0b2cf610153cea0c61f576a36d0a +Author: Christian Brauner +Date: Wed Sep 25 11:05:16 2024 +0200 + + epoll: annotate racy check + + Epoll relies on a racy fastpath check during __fput() in + eventpoll_release() to avoid the hit of pointlessly acquiring a + semaphore. Annotate that race by using WRITE_ONCE() and READ_ONCE(). + + Link: https://lore.kernel.org/r/66edfb3c.050a0220.3195df.001a.GAE@google.com + Link: https://lore.kernel.org/r/20240925-fungieren-anbauen-79b334b00542@brauner + Reviewed-by: Jan Kara + Reported-by: syzbot+3b6b32dc50537a49bb4a@syzkaller.appspotmail.com + Signed-off-by: Christian Brauner + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index a7efeb499e4b..616f97e9b75b 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -852,7 +852,8 @@ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) + to_free = NULL; + head = file->f_ep; + if (head->first == &epi->fllink && !epi->fllink.next) { +- file->f_ep = NULL; ++ /* See eventpoll_release() for details. */ ++ WRITE_ONCE(file->f_ep, NULL); + if (!is_file_epoll(file)) { + struct epitems_head *v; + v = container_of(head, struct epitems_head, epitems); +@@ -1630,7 +1631,8 @@ static int attach_epitem(struct file *file, struct epitem *epi) + spin_unlock(&file->f_lock); + goto allocate; + } +- file->f_ep = head; ++ /* See eventpoll_release() for details. */ ++ WRITE_ONCE(file->f_ep, head); + to_free = NULL; + } + hlist_add_head_rcu(&epi->fllink, file->f_ep); +diff --git a/include/linux/eventpoll.h b/include/linux/eventpoll.h +index 3337745d81bd..0c0d00fcd131 100644 +--- a/include/linux/eventpoll.h ++++ b/include/linux/eventpoll.h +@@ -42,7 +42,7 @@ static inline void eventpoll_release(struct file *file) + * because the file in on the way to be removed and nobody ( but + * eventpoll ) has still a reference to this file. + */ +- if (likely(!file->f_ep)) ++ if (likely(!READ_ONCE(file->f_ep))) + return; + + /* +-- +2.50.1 (Apple Git-155) + diff --git a/1430-eventpoll-defer-struct-eventpoll-free-to-rcu-grace-period.patch b/1430-eventpoll-defer-struct-eventpoll-free-to-rcu-grace-period.patch new file mode 100644 index 000000000..f2bdc8dc7 --- /dev/null +++ b/1430-eventpoll-defer-struct-eventpoll-free-to-rcu-grace-period.patch @@ -0,0 +1,53 @@ +From 2b62eb17ec16b6a7d341497e48e331f84280cd58 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:54:24 +0800 +Subject: [PATCH] eventpoll: defer struct eventpoll free to RCU grace period + +JIRA: https://redhat.atlassian.net/browse/RHEL-173837 +Upstream status: Linus + +CVE: CVE-2026-43074 + +commit 07712db80857d5d09ae08f3df85a708ecfc3b61f +Author: Nicholas Carlini +Date: Tue Mar 31 15:25:32 2026 +0200 + + eventpoll: defer struct eventpoll free to RCU grace period + + In certain situations, ep_free() in eventpoll.c will kfree the epi->ep + eventpoll struct while it still being used by another concurrent thread. + Defer the kfree() to an RCU callback to prevent UAF. + + Fixes: f2e467a48287 ("eventpoll: Fix semi-unbounded recursion") + Signed-off-by: Nicholas Carlini + Signed-off-by: Christian Brauner + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 616f97e9b75b..0368ab69138a 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -225,6 +225,9 @@ struct eventpoll { + */ + refcount_t refcount; + ++ /* used to defer freeing past ep_get_upwards_depth_proc() RCU walk */ ++ struct rcu_head rcu; ++ + #ifdef CONFIG_NET_RX_BUSY_POLL + /* used to track busy poll napi_id */ + unsigned int napi_id; +@@ -818,7 +821,8 @@ static void ep_free(struct eventpoll *ep) + mutex_destroy(&ep->mtx); + free_uid(ep->user); + wakeup_source_unregister(ep->ws); +- kfree(ep); ++ /* ep_get_upwards_depth_proc() may still hold epi->ep under RCU */ ++ kfree_rcu(ep, rcu); + } + + /* +-- +2.50.1 (Apple Git-155) + diff --git a/1431-eventpoll-use-hlist-is-singular-node-in-ep-remove.patch b/1431-eventpoll-use-hlist-is-singular-node-in-ep-remove.patch new file mode 100644 index 000000000..126527bc2 --- /dev/null +++ b/1431-eventpoll-use-hlist-is-singular-node-in-ep-remove.patch @@ -0,0 +1,39 @@ +From 1fe6204c6ea38fc1d370d9bbb9fd6a800104e5c8 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:54:59 +0800 +Subject: [PATCH] eventpoll: use hlist_is_singular_node() in __ep_remove() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 3d9fd0abc94d8cd430cc7cd7d37ce5e5aae2cd2b +Author: Christian Brauner +Date: Thu Apr 23 11:56:04 2026 +0200 + + eventpoll: use hlist_is_singular_node() in __ep_remove() + + Replace the open-coded "epi is the only entry in file->f_ep" check + with hlist_is_singular_node(). Same semantics, and the helper avoids + the head-cacheline access in the common false case. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-1-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 0368ab69138a..2dd87800bfbf 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -855,7 +855,7 @@ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) + + to_free = NULL; + head = file->f_ep; +- if (head->first == &epi->fllink && !epi->fllink.next) { ++ if (hlist_is_singular_node(&epi->fllink, head)) { + /* See eventpoll_release() for details. */ + WRITE_ONCE(file->f_ep, NULL); + if (!is_file_epoll(file)) { +-- +2.50.1 (Apple Git-155) + diff --git a/1432-eventpoll-split-ep-remove.patch b/1432-eventpoll-split-ep-remove.patch new file mode 100644 index 000000000..a872525c2 --- /dev/null +++ b/1432-eventpoll-split-ep-remove.patch @@ -0,0 +1,84 @@ +From 83db11e5ba3c57104945247e11c1891355a12fb6 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:55:51 +0800 +Subject: [PATCH] eventpoll: split __ep_remove() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 0f7bdfd413000985de09fc39eb9efa1e091a3ce0 +Author: Christian Brauner +Date: Thu Apr 23 11:56:05 2026 +0200 + + eventpoll: split __ep_remove() + + Split __ep_remove() to delineate file removal from epoll item removal. + + Suggested-by: Linus Torvalds + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-2-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 2dd87800bfbf..001c87b6ddab 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -825,6 +825,9 @@ static void ep_free(struct eventpoll *ep) + kfree_rcu(ep, rcu); + } + ++static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, struct file *file); ++static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi); ++ + /* + * Removes a "struct epitem" from the eventpoll RB tree and deallocates + * all the associated resources. Must be called with "mtx" held. +@@ -836,8 +839,6 @@ static void ep_free(struct eventpoll *ep) + static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) + { + struct file *file = epi->ffd.file; +- struct epitems_head *to_free; +- struct hlist_head *head; + + lockdep_assert_irqs_enabled(); + +@@ -853,8 +854,21 @@ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) + return false; + } + +- to_free = NULL; +- head = file->f_ep; ++ __ep_remove_file(ep, epi, file); ++ return __ep_remove_epi(ep, epi); ++} ++ ++/* ++ * Called with &file->f_lock held, ++ * returns with it released ++ */ ++static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, struct file *file) ++{ ++ struct epitems_head *to_free = NULL; ++ struct hlist_head *head = file->f_ep; ++ ++ lockdep_assert_held(&ep->mtx); ++ + if (hlist_is_singular_node(&epi->fllink, head)) { + /* See eventpoll_release() for details. */ + WRITE_ONCE(file->f_ep, NULL); +@@ -868,6 +882,11 @@ static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) + hlist_del_rcu(&epi->fllink); + spin_unlock(&file->f_lock); + free_ephead(to_free); ++} ++ ++static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi) ++{ ++ lockdep_assert_held(&ep->mtx); + + rb_erase_cached(&epi->rbn, &ep->rbr); + +-- +2.50.1 (Apple Git-155) + diff --git a/1433-eventpoll-kill-ep-remove.patch b/1433-eventpoll-kill-ep-remove.patch new file mode 100644 index 000000000..3d7e6632d --- /dev/null +++ b/1433-eventpoll-kill-ep-remove.patch @@ -0,0 +1,134 @@ +From 9a9a41f8aa46741f965703f520542e9e8fdbe59f Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:56:21 +0800 +Subject: [PATCH] eventpoll: kill __ep_remove() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit e9e5cd40d7c403e19f21d0f7b8b8ba3a76b58330 +Author: Christian Brauner +Date: Thu Apr 23 11:56:06 2026 +0200 + + eventpoll: kill __ep_remove() + + Remove the boolean conditional in __ep_remove() and restructure the code + so the check for racing with eventpoll_release_file() are only done in + the ep_remove_safe() path where they belong. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-3-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 001c87b6ddab..cb75868ba1de 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -825,49 +825,18 @@ static void ep_free(struct eventpoll *ep) + kfree_rcu(ep, rcu); + } + +-static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, struct file *file); +-static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi); +- +-/* +- * Removes a "struct epitem" from the eventpoll RB tree and deallocates +- * all the associated resources. Must be called with "mtx" held. +- * If the dying flag is set, do the removal only if force is true. +- * This prevents ep_clear_and_put() from dropping all the ep references +- * while running concurrently with eventpoll_release_file(). +- * Returns true if the eventpoll can be disposed. +- */ +-static bool __ep_remove(struct eventpoll *ep, struct epitem *epi, bool force) +-{ +- struct file *file = epi->ffd.file; +- +- lockdep_assert_irqs_enabled(); +- +- /* +- * Removes poll wait queue hooks. +- */ +- ep_unregister_pollwait(ep, epi); +- +- /* Remove the current item from the list of epoll hooks */ +- spin_lock(&file->f_lock); +- if (epi->dying && !force) { +- spin_unlock(&file->f_lock); +- return false; +- } +- +- __ep_remove_file(ep, epi, file); +- return __ep_remove_epi(ep, epi); +-} +- + /* + * Called with &file->f_lock held, + * returns with it released + */ +-static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, struct file *file) ++static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, ++ struct file *file) + { + struct epitems_head *to_free = NULL; + struct hlist_head *head = file->f_ep; + + lockdep_assert_held(&ep->mtx); ++ lockdep_assert_held(&file->f_lock); + + if (hlist_is_singular_node(&epi->fllink, head)) { + /* See eventpoll_release() for details. */ +@@ -914,7 +883,25 @@ static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi) + */ + static void ep_remove_safe(struct eventpoll *ep, struct epitem *epi) + { +- if (__ep_remove(ep, epi, false)) ++ struct file *file = epi->ffd.file; ++ ++ lockdep_assert_irqs_enabled(); ++ lockdep_assert_held(&ep->mtx); ++ ++ ep_unregister_pollwait(ep, epi); ++ ++ /* sync with eventpoll_release_file() */ ++ if (unlikely(READ_ONCE(epi->dying))) ++ return; ++ ++ spin_lock(&file->f_lock); ++ if (epi->dying) { ++ spin_unlock(&file->f_lock); ++ return; ++ } ++ __ep_remove_file(ep, epi, file); ++ ++ if (__ep_remove_epi(ep, epi)) + WARN_ON_ONCE(ep_refcount_dec_and_test(ep)); + } + +@@ -1146,7 +1133,7 @@ void eventpoll_release_file(struct file *file) + spin_lock(&file->f_lock); + if (file->f_ep && file->f_ep->first) { + epi = hlist_entry(file->f_ep->first, struct epitem, fllink); +- epi->dying = true; ++ WRITE_ONCE(epi->dying, true); + spin_unlock(&file->f_lock); + + /* +@@ -1155,7 +1142,13 @@ void eventpoll_release_file(struct file *file) + */ + ep = epi->ep; + mutex_lock(&ep->mtx); +- dispose = __ep_remove(ep, epi, true); ++ ++ ep_unregister_pollwait(ep, epi); ++ ++ spin_lock(&file->f_lock); ++ __ep_remove_file(ep, epi, file); ++ dispose = __ep_remove_epi(ep, epi); ++ + mutex_unlock(&ep->mtx); + + if (dispose && ep_refcount_dec_and_test(ep)) +-- +2.50.1 (Apple Git-155) + diff --git a/1434-eventpoll-rename-ep-remove-safe-back-to-ep-remove.patch b/1434-eventpoll-rename-ep-remove-safe-back-to-ep-remove.patch new file mode 100644 index 000000000..23c3da7fc --- /dev/null +++ b/1434-eventpoll-rename-ep-remove-safe-back-to-ep-remove.patch @@ -0,0 +1,98 @@ +From 4e16f36e52dfe03056ec8c4daaef79b1715bb341 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:58:37 +0800 +Subject: [PATCH] eventpoll: rename ep_remove_safe() back to ep_remove() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 0bade234723e40e4937be912e105785d6a51464e +Author: Christian Brauner +Date: Thu Apr 23 11:56:07 2026 +0200 + + eventpoll: rename ep_remove_safe() back to ep_remove() + + The current name is just confusing and doesn't clarify anything. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-4-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index cb75868ba1de..efc9e3e1e3f5 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -881,7 +881,7 @@ static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi) + /* + * ep_remove variant for callers owing an additional reference to the ep + */ +-static void ep_remove_safe(struct eventpoll *ep, struct epitem *epi) ++static void ep_remove(struct eventpoll *ep, struct epitem *epi) + { + struct file *file = epi->ffd.file; + +@@ -928,7 +928,7 @@ static void ep_clear_and_put(struct eventpoll *ep) + + /* + * Walks through the whole tree and try to free each "struct epitem". +- * Note that ep_remove_safe() will not remove the epitem in case of a ++ * Note that ep_remove() will not remove the epitem in case of a + * racing eventpoll_release_file(); the latter will do the removal. + * At this point we are sure no poll callbacks will be lingering around. + * Since we still own a reference to the eventpoll struct, the loop can't +@@ -937,7 +937,7 @@ static void ep_clear_and_put(struct eventpoll *ep) + for (rbp = rb_first_cached(&ep->rbr); rbp; rbp = next) { + next = rb_next(rbp); + epi = rb_entry(rbp, struct epitem, rbn); +- ep_remove_safe(ep, epi); ++ ep_remove(ep, epi); + cond_resched(); + } + +@@ -1714,21 +1714,21 @@ static int ep_insert(struct eventpoll *ep, const struct epoll_event *event, + mutex_unlock(&tep->mtx); + + /* +- * ep_remove_safe() calls in the later error paths can't lead to ++ * ep_remove() calls in the later error paths can't lead to + * ep_free() as the ep file itself still holds an ep reference. + */ + ep_get(ep); + + /* now check if we've created too many backpaths */ + if (unlikely(full_check && reverse_path_check())) { +- ep_remove_safe(ep, epi); ++ ep_remove(ep, epi); + return -EINVAL; + } + + if (epi->event.events & EPOLLWAKEUP) { + error = ep_create_wakeup_source(epi); + if (error) { +- ep_remove_safe(ep, epi); ++ ep_remove(ep, epi); + return error; + } + } +@@ -1752,7 +1752,7 @@ static int ep_insert(struct eventpoll *ep, const struct epoll_event *event, + * high memory pressure. + */ + if (unlikely(!epq.epi)) { +- ep_remove_safe(ep, epi); ++ ep_remove(ep, epi); + return -ENOMEM; + } + +@@ -2415,7 +2415,7 @@ int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds, + * The eventpoll itself is still alive: the refcount + * can't go to zero here. + */ +- ep_remove_safe(ep, epi); ++ ep_remove(ep, epi); + error = 0; + } else { + error = -ENOENT; +-- +2.50.1 (Apple Git-155) + diff --git a/1435-eventpoll-move-epi-fget-up.patch b/1435-eventpoll-move-epi-fget-up.patch new file mode 100644 index 000000000..f2838812b --- /dev/null +++ b/1435-eventpoll-move-epi-fget-up.patch @@ -0,0 +1,98 @@ +From 8c1707336e5674c16c2a24c9cdf2b5018b166a09 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:00:26 +0800 +Subject: [PATCH] eventpoll: move epi_fget() up + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 86e87059e6d1fd5115a31949726450ed03c1073b +Author: Christian Brauner +Date: Thu Apr 23 11:56:08 2026 +0200 + + eventpoll: move epi_fget() up + + We'll need it when removing files so move it up. No functional change. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-5-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index efc9e3e1e3f5..ded53c6fbd6f 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -825,6 +825,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 (!file_ref_get(&file->f_ref)) ++ file = NULL; ++ return file; ++} ++ + /* + * Called with &file->f_lock held, + * returns with it released +@@ -1017,34 +1045,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 (!file_ref_get(&file->f_ref)) +- 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() +-- +2.50.1 (Apple Git-155) + diff --git a/1436-eventpoll-drop-vestigial-prefix-from-ep-remove-file-epi.patch b/1436-eventpoll-drop-vestigial-prefix-from-ep-remove-file-epi.patch new file mode 100644 index 000000000..985802699 --- /dev/null +++ b/1436-eventpoll-drop-vestigial-prefix-from-ep-remove-file-epi.patch @@ -0,0 +1,78 @@ +From 6175a9a692e559e7f0635d3a32d20fe94bae8280 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 15:57:24 +0800 +Subject: [PATCH] eventpoll: drop vestigial __ prefix from + ep_remove_{file,epi}() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +Conflict: I don't know how this commit is present when commit + d3608d9e2f5a ("eventpoll: fix ep_remove struct eventpoll / + struct file UAF") is applied since its time stamp is later than + the afore mentioned commit. I've applied it here purely to avoid + the subsequent conflict. + +commit 0feaf644f7180c4a91b6b405a881afbfd958f1cf +Author: Christian Brauner +Date: Fri Apr 24 00:23:18 2026 +0200 + + eventpoll: drop vestigial __ prefix from ep_remove_{file,epi}() + + With __ep_remove() gone, the double-underscore on __ep_remove_file() + and __ep_remove_epi() no longer contrasts with a __-less parent and + just reads as noise. Rename both to ep_remove_file() and + ep_remove_epi(). No functional change. + + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index ded53c6fbd6f..1246c0517aa6 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -857,7 +857,7 @@ static struct file *epi_fget(const struct epitem *epi) + * Called with &file->f_lock held, + * returns with it released + */ +-static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, ++static void ep_remove_file(struct eventpoll *ep, struct epitem *epi, + struct file *file) + { + struct epitems_head *to_free = NULL; +@@ -881,7 +881,7 @@ static void __ep_remove_file(struct eventpoll *ep, struct epitem *epi, + free_ephead(to_free); + } + +-static bool __ep_remove_epi(struct eventpoll *ep, struct epitem *epi) ++static bool ep_remove_epi(struct eventpoll *ep, struct epitem *epi) + { + lockdep_assert_held(&ep->mtx); + +@@ -927,9 +927,9 @@ static void ep_remove(struct eventpoll *ep, struct epitem *epi) + spin_unlock(&file->f_lock); + return; + } +- __ep_remove_file(ep, epi, file); ++ ep_remove_file(ep, epi, file); + +- if (__ep_remove_epi(ep, epi)) ++ if (ep_remove_epi(ep, epi)) + WARN_ON_ONCE(ep_refcount_dec_and_test(ep)); + } + +@@ -1146,8 +1146,8 @@ void eventpoll_release_file(struct file *file) + ep_unregister_pollwait(ep, epi); + + spin_lock(&file->f_lock); +- __ep_remove_file(ep, epi, file); +- dispose = __ep_remove_epi(ep, epi); ++ ep_remove_file(ep, epi, file); ++ dispose = ep_remove_epi(ep, epi); + + mutex_unlock(&ep->mtx); + +-- +2.50.1 (Apple Git-155) + diff --git a/1437-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch b/1437-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch new file mode 100644 index 000000000..2f4872c14 --- /dev/null +++ b/1437-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch @@ -0,0 +1,104 @@ +From 5c53e2d149e55d3ee64fb03387d2f81d59477774 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:00:59 +0800 +Subject: [PATCH] eventpoll: fix ep_remove struct eventpoll / struct file UAF + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +CVE: CVE-2026-46242 + +commit a6dc643c69311677c574a0f17a3f4d66a5f3744b +Author: Christian Brauner +Date: Thu Apr 23 11:56:09 2026 +0200 + + eventpoll: fix ep_remove struct eventpoll / struct file UAF + + ep_remove() (via ep_remove_file()) cleared file->f_ep under + file->f_lock but then kept using @file inside the critical section + (is_file_epoll(), hlist_del_rcu() through the head, spin_unlock). + A concurrent __fput() taking the eventpoll_release() fastpath in + that window observed the transient NULL, skipped + eventpoll_release_file() and ran to f_op->release / file_free(). + + For the epoll-watches-epoll case, f_op->release is + ep_eventpoll_release() -> ep_clear_and_put() -> ep_free(), which + kfree()s the watched struct eventpoll. Its embedded ->refs + hlist_head is exactly where epi->fllink.pprev points, so the + subsequent hlist_del_rcu()'s "*pprev = next" scribbles into freed + kmalloc-192 memory. + + In addition, struct file is SLAB_TYPESAFE_BY_RCU, so the slot + backing @file could be recycled by alloc_empty_file() -- + reinitializing f_lock and f_ep -- while ep_remove() is still + nominally inside that lock. The upshot is an attacker-controllable + kmem_cache_free() against the wrong slab cache. + + Pin @file via epi_fget() at the top of ep_remove() and gate the + critical section on the pin succeeding. With the pin held @file + cannot reach refcount zero, which holds __fput() off and + transitively keeps the watched struct eventpoll alive across the + hlist_del_rcu() and the f_lock use, closing both UAFs. + + If the pin fails @file has already reached refcount zero and its + __fput() is in flight. Because we bailed before clearing f_ep, + that path takes the eventpoll_release() slow path into + eventpoll_release_file() and blocks on ep->mtx until the waiter + side's ep_clear_and_put() drops it. The bailed epi's share of + ep->refcount stays intact, so the trailing ep_refcount_dec_and_test() + in ep_clear_and_put() cannot free the eventpoll out from under + eventpoll_release_file(); the orphaned epi is then cleaned up + there. + + A successful pin also proves we are not racing + eventpoll_release_file() on this epi, so drop the now-redundant + re-check of epi->dying under f_lock. The cheap lockless + READ_ONCE(epi->dying) fast-path bailout stays. + + Fixes: 58c9b016e128 ("epoll: use refcount to reduce ep_mutex contention") + Reported-by: Jaeyoung Chung + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-6-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 1246c0517aa6..f46906640a9b 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -911,22 +911,26 @@ static bool ep_remove_epi(struct eventpoll *ep, struct epitem *epi) + */ + static void ep_remove(struct eventpoll *ep, struct epitem *epi) + { +- struct file *file = epi->ffd.file; ++ struct file *file __free(fput) = NULL; + + lockdep_assert_irqs_enabled(); + lockdep_assert_held(&ep->mtx); + + ep_unregister_pollwait(ep, epi); + +- /* sync with eventpoll_release_file() */ ++ /* cheap sync with eventpoll_release_file() */ + if (unlikely(READ_ONCE(epi->dying))) + return; + +- spin_lock(&file->f_lock); +- if (epi->dying) { +- spin_unlock(&file->f_lock); ++ /* ++ * If we manage to grab a reference it means we're not in ++ * eventpoll_release_file() and aren't going to be. ++ */ ++ file = epi_fget(epi); ++ if (!file) + return; +- } ++ ++ spin_lock(&file->f_lock); + ep_remove_file(ep, epi, file); + + if (ep_remove_epi(ep, epi)) +-- +2.50.1 (Apple Git-155) + diff --git a/1438-eventpoll-move-f-lock-acquisition-into-ep-remove-file.patch b/1438-eventpoll-move-f-lock-acquisition-into-ep-remove-file.patch new file mode 100644 index 000000000..9c82bdf39 --- /dev/null +++ b/1438-eventpoll-move-f-lock-acquisition-into-ep-remove-file.patch @@ -0,0 +1,70 @@ +From e0bf480728b83ec6ceb66975bfbac514195873c8 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:01:51 +0800 +Subject: [PATCH] eventpoll: move f_lock acquisition into ep_remove_file() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit d30deeb8b0cf6259785c1fb79b87905d281b0a5a +Author: Christian Brauner +Date: Thu Apr 23 11:56:10 2026 +0200 + + eventpoll: move f_lock acquisition into ep_remove_file() + + Let the helper own its critical section end-to-end: take &file->f_lock + at the top, read file->f_ep inside the lock, release on exit. Callers + (ep_remove() and eventpoll_release_file()) no longer need to wrap the + call, and the function-comment lock-handoff contract is gone. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-7-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index f46906640a9b..9798ab5f7663 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -854,18 +854,18 @@ static struct file *epi_fget(const struct epitem *epi) + } + + /* +- * Called with &file->f_lock held, +- * returns with it released ++ * Takes &file->f_lock; returns with it released. + */ + static void ep_remove_file(struct eventpoll *ep, struct epitem *epi, + struct file *file) + { + struct epitems_head *to_free = NULL; +- struct hlist_head *head = file->f_ep; ++ struct hlist_head *head; + + lockdep_assert_held(&ep->mtx); +- lockdep_assert_held(&file->f_lock); + ++ spin_lock(&file->f_lock); ++ head = file->f_ep; + if (hlist_is_singular_node(&epi->fllink, head)) { + /* See eventpoll_release() for details. */ + WRITE_ONCE(file->f_ep, NULL); +@@ -930,7 +930,6 @@ static void ep_remove(struct eventpoll *ep, struct epitem *epi) + if (!file) + return; + +- spin_lock(&file->f_lock); + ep_remove_file(ep, epi, file); + + if (ep_remove_epi(ep, epi)) +@@ -1149,7 +1148,6 @@ void eventpoll_release_file(struct file *file) + + ep_unregister_pollwait(ep, epi); + +- spin_lock(&file->f_lock); + ep_remove_file(ep, epi, file); + dispose = ep_remove_epi(ep, epi); + +-- +2.50.1 (Apple Git-155) + diff --git a/1439-eventpoll-refresh-eventpoll-release-fast-path-comment.patch b/1439-eventpoll-refresh-eventpoll-release-fast-path-comment.patch new file mode 100644 index 000000000..deabbe282 --- /dev/null +++ b/1439-eventpoll-refresh-eventpoll-release-fast-path-comment.patch @@ -0,0 +1,66 @@ +From eab8e8be1be2430b6712fc1a6c8925d240a40031 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:02:27 +0800 +Subject: [PATCH] eventpoll: refresh eventpoll_release() fast-path comment + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 33e92e9ecf48c08cb4807e9a36f9eb01619c1a1e +Author: Christian Brauner +Date: Thu Apr 23 11:56:11 2026 +0200 + + eventpoll: refresh eventpoll_release() fast-path comment + + The old comment justified the lockless READ_ONCE(file->f_ep) check + with "False positives simply cannot happen because the file is on + the way to be removed and nobody ( but eventpoll ) has still a + reference to this file." That reasoning was the root of the UAF + fixed in "eventpoll: fix ep_remove struct eventpoll / struct file + UAF": __ep_remove() could clear f_ep while another close raced + past the fast path and freed the watched eventpoll / recycled the + struct file slot. + + With ep_remove() now pinning @file via epi_fget() across the f_ep + clear and hlist_del_rcu(), the invariant is re-established for the + right reason: anyone who might clear f_ep holds @file alive for + the duration, so a NULL observation really does mean no + concurrent eventpoll path has work left on this file. Refresh the + comment accordingly so the next reader doesn't inherit the broken + model. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-8-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/include/linux/eventpoll.h b/include/linux/eventpoll.h +index 0c0d00fcd131..be762281355a 100644 +--- a/include/linux/eventpoll.h ++++ b/include/linux/eventpoll.h +@@ -35,12 +35,16 @@ static inline void eventpoll_release(struct file *file) + { + + /* +- * Fast check to avoid the get/release of the semaphore. Since +- * we're doing this outside the semaphore lock, it might return +- * false negatives, but we don't care. It'll help in 99.99% of cases +- * to avoid the semaphore lock. False positives simply cannot happen +- * because the file in on the way to be removed and nobody ( but +- * eventpoll ) has still a reference to this file. ++ * Fast check to skip the slow path in the common case where the ++ * file was never attached to an epoll. Safe without file->f_lock ++ * because every f_ep writer excludes a concurrent __fput() on ++ * @file: ++ * - ep_insert() requires the file alive (refcount > 0); ++ * - ep_remove() holds @file pinned via epi_fget() across the ++ * write; ++ * - eventpoll_release_file() runs from __fput() itself. ++ * We are in __fput() here, so none of those can race us: a NULL ++ * observation truly means no epoll path has work left on @file. + */ + if (likely(!READ_ONCE(file->f_ep))) + return; +-- +2.50.1 (Apple Git-155) + diff --git a/1440-eventpoll-drop-dead-bool-return-from-ep-remove-epi.patch b/1440-eventpoll-drop-dead-bool-return-from-ep-remove-epi.patch new file mode 100644 index 000000000..336fd9baa --- /dev/null +++ b/1440-eventpoll-drop-dead-bool-return-from-ep-remove-epi.patch @@ -0,0 +1,95 @@ +From 38a78d49b434ce9f29afdfaa870142ecb121800f Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:03:04 +0800 +Subject: [PATCH] eventpoll: drop dead bool return from ep_remove_epi() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 3a4551ea9c042502019b1d8a986e962cb9015366 +Author: Christian Brauner +Date: Thu Apr 23 11:56:12 2026 +0200 + + eventpoll: drop dead bool return from ep_remove_epi() + + ep_remove_epi() always returns true -- the "can be disposed" + answer was meaningful back when the dying-check lived inside the + pre-split __ep_remove(), but after that check moved to ep_remove() + the return value is just noise. Both callers gate on it + unconditionally: + + if (ep_remove_epi(ep, epi)) + WARN_ON_ONCE(ep_refcount_dec_and_test(ep)); + + dispose = ep_remove_epi(ep, epi); + ... + if (dispose && ep_refcount_dec_and_test(ep)) + ep_free(ep); + + Make ep_remove_epi() return void, drop the dispose local in + eventpoll_release_file(), and the useless conditionals at both + callers. No functional change. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-9-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 9798ab5f7663..761bd85f213e 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -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 void ep_remove_epi(struct eventpoll *ep, struct epitem *epi) + { + lockdep_assert_held(&ep->mtx); + +@@ -903,7 +903,6 @@ static bool ep_remove_epi(struct eventpoll *ep, struct epitem *epi) + kfree_rcu(epi, rcu); + + percpu_counter_dec(&ep->user->epoll_watches); +- return true; + } + + /* +@@ -931,9 +930,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) +@@ -1125,7 +1123,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 +@@ -1149,11 +1146,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; + } +-- +2.50.1 (Apple Git-155) + diff --git a/1441-eventpoll-fix-semi-unbounded-recursion.patch b/1441-eventpoll-fix-semi-unbounded-recursion.patch new file mode 100644 index 000000000..428583af9 --- /dev/null +++ b/1441-eventpoll-fix-semi-unbounded-recursion.patch @@ -0,0 +1,176 @@ +From 6ee3e28a7271944ba136a37d07ca35f0f7e3903b Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:03:26 +0800 +Subject: [PATCH] eventpoll: Fix semi-unbounded recursion + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +CVE: CVE-2025-38614 + +commit f2e467a48287c868818085aa35389a224d226732 +Author: Jann Horn +Date: Fri Jul 11 18:33:36 2025 +0200 + + eventpoll: Fix semi-unbounded recursion + + Ensure that epoll instances can never form a graph deeper than + EP_MAX_NESTS+1 links. + + Currently, ep_loop_check_proc() ensures that the graph is loop-free and + does some recursion depth checks, but those recursion depth checks don't + limit the depth of the resulting tree for two reasons: + + - They don't look upwards in the tree. + - If there are multiple downwards paths of different lengths, only one of + the paths is actually considered for the depth check since commit + 28d82dc1c4ed ("epoll: limit paths"). + + Essentially, the current recursion depth check in ep_loop_check_proc() just + serves to prevent it from recursing too deeply while checking for loops. + + A more thorough check is done in reverse_path_check() after the new graph + edge has already been created; this checks, among other things, that no + paths going upwards from any non-epoll file with a length of more than 5 + edges exist. However, this check does not apply to non-epoll files. + + As a result, it is possible to recurse to a depth of at least roughly 500, + tested on v6.15. (I am unsure if deeper recursion is possible; and this may + have changed with commit 8c44dac8add7 ("eventpoll: Fix priority inversion + problem").) + + To fix it: + + 1. In ep_loop_check_proc(), note the subtree depth of each visited node, + and use subtree depths for the total depth calculation even when a subtree + has already been visited. + 2. Add ep_get_upwards_depth_proc() for similarly determining the maximum + depth of an upwards walk. + 3. In ep_loop_check(), use these values to limit the total path length + between epoll nodes to EP_MAX_NESTS edges. + + Fixes: 22bacca48a17 ("epoll: prevent creating circular epoll structures") + Cc: stable@vger.kernel.org + Signed-off-by: Jann Horn + Link: https://lore.kernel.org/20250711-epoll-recursion-fix-v1-1-fb2457c33292@google.com + Signed-off-by: Christian Brauner + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 761bd85f213e..88b501a5e709 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -218,6 +218,7 @@ struct eventpoll { + /* used to optimize loop detection check */ + u64 gen; + struct hlist_head refs; ++ u8 loop_check_depth; + + /* + * usage count, used together with epitem->dying to +@@ -2135,23 +2136,24 @@ static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, + } + + /** +- * ep_loop_check_proc - verify that adding an epoll file inside another +- * epoll structure does not violate the constraints, in +- * terms of closed loops, or too deep chains (which can +- * result in excessive stack usage). ++ * ep_loop_check_proc - verify that adding an epoll file @ep inside another ++ * epoll file does not create closed loops, and ++ * determine the depth of the subtree starting at @ep + * + * @ep: the &struct eventpoll to be currently checked. + * @depth: Current depth of the path being checked. + * +- * Return: %zero if adding the epoll @file inside current epoll +- * structure @ep does not violate the constraints, or %-1 otherwise. ++ * Return: depth of the subtree, or INT_MAX if we found a loop or went too deep. + */ + static int ep_loop_check_proc(struct eventpoll *ep, int depth) + { +- int error = 0; ++ int result = 0; + struct rb_node *rbp; + struct epitem *epi; + ++ if (ep->gen == loop_check_gen) ++ return ep->loop_check_depth; ++ + mutex_lock_nested(&ep->mtx, depth + 1); + ep->gen = loop_check_gen; + for (rbp = rb_first_cached(&ep->rbr); rbp; rbp = rb_next(rbp)) { +@@ -2159,13 +2161,11 @@ static int ep_loop_check_proc(struct eventpoll *ep, int depth) + if (unlikely(is_file_epoll(epi->ffd.file))) { + struct eventpoll *ep_tovisit; + ep_tovisit = epi->ffd.file->private_data; +- if (ep_tovisit->gen == loop_check_gen) +- continue; + if (ep_tovisit == inserting_into || depth > EP_MAX_NESTS) +- error = -1; ++ result = INT_MAX; + else +- error = ep_loop_check_proc(ep_tovisit, depth + 1); +- if (error != 0) ++ result = max(result, ep_loop_check_proc(ep_tovisit, depth + 1) + 1); ++ if (result > EP_MAX_NESTS) + break; + } else { + /* +@@ -2179,9 +2179,27 @@ static int ep_loop_check_proc(struct eventpoll *ep, int depth) + list_file(epi->ffd.file); + } + } ++ ep->loop_check_depth = result; + mutex_unlock(&ep->mtx); + +- return error; ++ return result; ++} ++ ++/** ++ * ep_get_upwards_depth_proc - determine depth of @ep when traversed upwards ++ */ ++static int ep_get_upwards_depth_proc(struct eventpoll *ep, int depth) ++{ ++ int result = 0; ++ struct epitem *epi; ++ ++ if (ep->gen == loop_check_gen) ++ return ep->loop_check_depth; ++ hlist_for_each_entry_rcu(epi, &ep->refs, fllink) ++ result = max(result, ep_get_upwards_depth_proc(epi->ep, depth + 1) + 1); ++ ep->gen = loop_check_gen; ++ ep->loop_check_depth = result; ++ return result; + } + + /** +@@ -2197,8 +2215,22 @@ static int ep_loop_check_proc(struct eventpoll *ep, int depth) + */ + static int ep_loop_check(struct eventpoll *ep, struct eventpoll *to) + { ++ int depth, upwards_depth; ++ + inserting_into = ep; +- return ep_loop_check_proc(to, 0); ++ /* ++ * Check how deep down we can get from @to, and whether it is possible ++ * to loop up to @ep. ++ */ ++ depth = ep_loop_check_proc(to, 0); ++ if (depth > EP_MAX_NESTS) ++ return -1; ++ /* Check how far up we can go from @ep. */ ++ rcu_read_lock(); ++ upwards_depth = ep_get_upwards_depth_proc(ep, 0); ++ rcu_read_unlock(); ++ ++ return (depth+1+upwards_depth > EP_MAX_NESTS) ? -1 : 0; + } + + static void clear_tfile_check_list(void) +-- +2.50.1 (Apple Git-155) + diff --git a/1442-eventpoll-drop-vestigial-epi-dying-flag.patch b/1442-eventpoll-drop-vestigial-epi-dying-flag.patch new file mode 100644 index 000000000..499dae72e --- /dev/null +++ b/1442-eventpoll-drop-vestigial-epi-dying-flag.patch @@ -0,0 +1,110 @@ +From 3724dc6414c5da5c393e58e0e5863fdb95166922 Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Tue, 16 Jun 2026 16:03:26 +0800 +Subject: [PATCH] eventpoll: drop vestigial epi->dying flag + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit 07422c948f4bdf15567a129a0983f7c12e57ba8e +Author: Christian Brauner +Date: Thu Apr 23 11:56:13 2026 +0200 + + eventpoll: drop vestigial epi->dying flag + + With ep_remove() now pinning @file via epi_fget() across the + f_ep clear and hlist_del_rcu(), the dying flag no longer + orchestrates anything: it was set in eventpoll_release_file() + (which only runs from __fput(), i.e. after @file's refcount has + reached zero) and read in __ep_remove() / ep_remove() as a cheap + bail before attempting the same synchronization epi_fget() now + provides unconditionally. + + The implication is simple: epi->dying == true always coincides + with file_ref_get(&file->f_ref) == false, because __fput() is + reachable only once the refcount hits zero and the refcount is + monotone in that state. The READ_ONCE(epi->dying) in ep_remove() + therefore selects exactly the same callers that epi_fget() would + reject, just one atomic cheaper. That's not worth a struct + field, a second coordination mechanism, and the comments on + both. + + Refresh the eventpoll_release_file() comment to describe what + actually makes the path race-free now (the pin in ep_remove()). + No functional change: the correctness argument is unchanged, + only the mechanism is now a single one instead of two. + + Link: https://patch.msgid.link/20260423-work-epoll-uaf-v1-10-2470f9eec0f5@kernel.org + Signed-off-by: Christian Brauner (Amutable) + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 88b501a5e709..5228ff9c8742 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -148,13 +148,6 @@ struct epitem { + /* The file descriptor information this item refers to */ + struct epoll_filefd ffd; + +- /* +- * Protected by file->f_lock, true for to-be-released epitem already +- * removed from the "struct file" items list; together with +- * eventpoll->refcount orchestrates "struct eventpoll" disposal +- */ +- bool dying; +- + /* List containing poll wait queues */ + struct eppoll_entry *pwqlist; + +@@ -220,10 +213,7 @@ struct eventpoll { + struct hlist_head refs; + u8 loop_check_depth; + +- /* +- * usage count, used together with epitem->dying to +- * orchestrate the disposal of this struct +- */ ++ /* usage count, orchestrates "struct eventpoll" disposal */ + refcount_t refcount; + + /* used to defer freeing past ep_get_upwards_depth_proc() RCU walk */ +@@ -918,13 +908,10 @@ static void ep_remove(struct eventpoll *ep, struct epitem *epi) + + ep_unregister_pollwait(ep, epi); + +- /* cheap sync with eventpoll_release_file() */ +- if (unlikely(READ_ONCE(epi->dying))) +- return; +- + /* + * If we manage to grab a reference it means we're not in +- * eventpoll_release_file() and aren't going to be. ++ * eventpoll_release_file() and aren't going to be: once @file's ++ * refcount has reached zero, file_ref_get() cannot bring it back. + */ + file = epi_fget(epi); + if (!file) +@@ -1126,15 +1113,15 @@ void eventpoll_release_file(struct file *file) + struct epitem *epi; + + /* +- * Use the 'dying' flag to prevent a concurrent ep_clear_and_put() from +- * touching the epitems list before eventpoll_release_file() can access +- * the ep->mtx. ++ * A concurrent ep_remove() cannot outrace us: it pins @file via ++ * epi_fget(), which fails once __fput() has dropped the refcount ++ * to zero -- the path we're on. So any racing ep_remove() bails ++ * and leaves the epi for us to clean up here. + */ + again: + spin_lock(&file->f_lock); + if (file->f_ep && file->f_ep->first) { + epi = hlist_entry(file->f_ep->first, struct epitem, fllink); +- WRITE_ONCE(epi->dying, true); + spin_unlock(&file->f_lock); + + /* +-- +2.50.1 (Apple Git-155) + diff --git a/1443-eventpoll-fix-integer-overflow-in-ep-loop-check-proc.patch b/1443-eventpoll-fix-integer-overflow-in-ep-loop-check-proc.patch new file mode 100644 index 000000000..85940ba18 --- /dev/null +++ b/1443-eventpoll-fix-integer-overflow-in-ep-loop-check-proc.patch @@ -0,0 +1,57 @@ +From 2eb0a1e08ebad5be00433093449abf025450ca6d Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Wed, 17 Jun 2026 12:08:43 +0800 +Subject: [PATCH] eventpoll: Fix integer overflow in ep_loop_check_proc() + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linus + +commit fdcfce93073d990ed4b71752e31ad1c1d6e9d58b +Author: Jann Horn +Date: Mon Feb 23 20:59:33 2026 +0100 + + eventpoll: Fix integer overflow in ep_loop_check_proc() + + If a recursive call to ep_loop_check_proc() hits the `result = INT_MAX`, + an integer overflow will occur in the calling ep_loop_check_proc() at + `result = max(result, ep_loop_check_proc(ep_tovisit, depth + 1) + 1)`, + breaking the recursion depth check. + + Fix it by using a different placeholder value that can't lead to an + overflow. + + Reported-by: Guenter Roeck + Fixes: f2e467a48287 ("eventpoll: Fix semi-unbounded recursion") + Cc: stable@vger.kernel.org + Signed-off-by: Jann Horn + Link: https://patch.msgid.link/20260223-epoll-int-overflow-v1-1-452f35132224@google.com + Signed-off-by: Christian Brauner + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index 5228ff9c8742..cb21dfe9ee0a 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -2130,7 +2130,8 @@ static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, + * @ep: the &struct eventpoll to be currently checked. + * @depth: Current depth of the path being checked. + * +- * Return: depth of the subtree, or INT_MAX if we found a loop or went too deep. ++ * Return: depth of the subtree, or a value bigger than EP_MAX_NESTS if we found ++ * a loop or went too deep. + */ + static int ep_loop_check_proc(struct eventpoll *ep, int depth) + { +@@ -2149,7 +2150,7 @@ static int ep_loop_check_proc(struct eventpoll *ep, int depth) + struct eventpoll *ep_tovisit; + ep_tovisit = epi->ffd.file->private_data; + if (ep_tovisit == inserting_into || depth > EP_MAX_NESTS) +- result = INT_MAX; ++ result = EP_MAX_NESTS+1; + else + result = max(result, ep_loop_check_proc(ep_tovisit, depth + 1) + 1); + if (result > EP_MAX_NESTS) +-- +2.50.1 (Apple Git-155) + diff --git a/1444-eventpoll-refresh-epi-fget-ep-remove-file-comments.patch b/1444-eventpoll-refresh-epi-fget-ep-remove-file-comments.patch new file mode 100644 index 000000000..bc6f290ea --- /dev/null +++ b/1444-eventpoll-refresh-epi-fget-ep-remove-file-comments.patch @@ -0,0 +1,101 @@ +From 39c9ab147e5bc9cf20d146d78c59afc7a0ab488d Mon Sep 17 00:00:00 2001 +From: Ian Kent +Date: Thu, 18 Jun 2026 11:30:35 +0800 +Subject: [PATCH] eventpoll: refresh epi_fget() / ep_remove_file() comments + +JIRA: https://redhat.atlassian.net/browse/RHEL-180777 +Upstream status: Linux + +commit a573cb40f9819c3fe81a43eb10170f8fc8eddc5e +Author: Christian Brauner +Date: Fri Apr 24 15:46:35 2026 +0200 + + eventpoll: refresh epi_fget() / ep_remove_file() comments + + Two comments drifted from the code they sit on. + + epi_fget()'s block comment still referenced atomic_long_inc_not_zero, + which has been file_ref_get() for a while, and described only one of + the function's two roles: safe dereference of epi->ffd.file under + ep->mtx. Since commit a6dc643c6931 ("eventpoll: fix ep_remove struct + eventpoll / struct file UAF") the refcount bump also serves as a pin + that blocks __fput() from starting, which is what lets ep_remove() + touch file->f_lock and file->f_ep without racing + eventpoll_release_file(). Update the block to name both roles and the + commit that introduced the pin role. + + ep_remove_file()'s one-line "See eventpoll_release() for details" + pointed at an inline in include/linux/eventpoll.h but said nothing + about what those details were. Replace it with a short explanation: + we publish NULL so the eventpoll_release() fastpath can skip the slow + path, and this is safe because every f_ep writer either holds a pin + via epi_fget() or is __fput() itself. + + Comment-only; no functional change. + + Signed-off-by: Christian Brauner (Amutable) + Link: https://patch.msgid.link/20260424-work-epoll-rework-v1-4-249ed00a20f3@kernel.org + Signed-off-by: Christian Brauner + +Signed-off-by: Ian Kent + +diff --git a/fs/eventpoll.c b/fs/eventpoll.c +index cb21dfe9ee0a..9bde520ac74b 100644 +--- a/fs/eventpoll.c ++++ b/fs/eventpoll.c +@@ -817,22 +817,23 @@ static void ep_free(struct eventpoll *ep) + } + + /* +- * The ffd.file pointer may be in the process of being torn down due to +- * being closed, but we may not have finished eventpoll_release() yet. ++ * Pin @epi->ffd.file for operations that require both safe dereference ++ * and exclusion from __fput(). + * +- * Normally, even with the atomic_long_inc_not_zero, the file may have +- * been free'd and then gotten re-allocated to something else (since +- * files are not RCU-delayed, they are SLAB_TYPESAFE_BY_RCU). ++ * struct file uses SLAB_TYPESAFE_BY_RCU, so a freed slot can be ++ * reassigned at any time. The bare load of epi->ffd.file is safe here ++ * because the caller holds ep->mtx and eventpoll_release_file() blocks ++ * on that mutex while tearing down the epi, so the backing file ++ * allocation cannot be freed and reused under us. An rcu_read_lock() ++ * is therefore unnecessary for the load. + * +- * But for epoll, users hold the ep->mtx mutex, and as such any file in +- * the process of being free'd will block in eventpoll_release_file() +- * and thus the underlying file allocation will not be free'd, and the +- * file re-use cannot happen. +- * +- * For the same reason we can avoid a rcu_read_lock() around the +- * operation - 'ffd.file' cannot go away even if the refcount has +- * reached zero (but we must still not call out to ->poll() functions +- * etc). ++ * A successful file_ref_get() additionally blocks __fput() from ++ * starting on this file: once the refcount has reached zero it cannot ++ * come back. ep_remove() relies on that to touch file->f_lock and ++ * file->f_ep without racing eventpoll_release_file() (see commit ++ * a6dc643c6931). A NULL return means __fput() is already in flight; ++ * the caller must bail without touching the file, and ++ * eventpoll_release_file() will clean the epi up from its side. + */ + static struct file *epi_fget(const struct epitem *epi) + { +@@ -858,7 +859,13 @@ static void ep_remove_file(struct eventpoll *ep, struct epitem *epi, + spin_lock(&file->f_lock); + head = file->f_ep; + if (hlist_is_singular_node(&epi->fllink, head)) { +- /* See eventpoll_release() for details. */ ++ /* ++ * Last watcher: publish NULL so the eventpoll_release() ++ * fastpath in include/linux/eventpoll.h can skip the slow ++ * path on a future __fput(). Safe because every f_ep writer ++ * either holds a pin on @file via epi_fget() or is __fput() ++ * itself -- see the comment in eventpoll_release(). ++ */ + WRITE_ONCE(file->f_ep, NULL); + if (!is_file_epoll(file)) { + struct epitems_head *v; +-- +2.50.1 (Apple Git-155) + diff --git a/kernel.spec b/kernel.spec index a05e5ccd7..f98dc69fd 100644 --- a/kernel.spec +++ b/kernel.spec @@ -176,13 +176,13 @@ Summary: The Linux kernel %define specrpmversion 6.12.0 %define specversion 6.12.0 %define patchversion 6.12 -%define pkgrelease 211.30.4 +%define pkgrelease 211.31.1 %define kversion 6 %define tarfile_release 6.12.0-211.7.1.el10_2 # This is needed to do merge window version magic %define patchlevel 12 # This allows pkg_release to have configurable %%{?dist} tag -%define specrelease 211.30.4%{?buildid}%{?dist} +%define specrelease 211.31.1%{?buildid}%{?dist} # This defines the kabi tarball version %define kabiversion 6.12.0-211.7.1.el10_2 @@ -1451,9 +1451,30 @@ Patch1413: 1413-arm64-errata-mitigate-tlbi-errata-on-microsoft-azure-cobalt.patc Patch1414: 1414-fs-smb-client-fix-out-of-bounds-read-in-cifs-sanitize-prepa.patch Patch1415: 1415-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-gfn.patch Patch1416: 1416-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-role.patch -Patch1417: 1417-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch -Patch1418: 1418-rtmutex-use-waiter-task-in-remove-waiter.patch -Patch1420: 1420-rtmutex-skip-remove-waiter-when-not-enqueued.patch +Patch1421: 1421-net-page-pool-avoid-false-positive-warning-if-napi-was-never.patch +Patch1422: 1422-net-mana-fix-double-destroy-workqueue-on-service-rescan-pci.patch +Patch1423: 1423-net-mana-null-service-wq-on-setup-error-to-prevent-double-de.patch +Patch1424: 1424-net-ipv6-ioam6-prevent-schema-length-wraparound-in-trace-fil.patch +Patch1425: 1425-procfs-fix-missing-rcu-protection-when-reading-real-parent-i.patch +Patch1426: 1426-tg3-fix-race-for-querying-speed-duplex.patch +Patch1427: 1427-smb-client-fix-off-by-8-bounds-check-in-check-wsl-eas.patch +Patch1428: 1428-smb-client-fix-out-of-bounds-read-in-smb2-compound-op.patch +Patch1429: 1429-epoll-annotate-racy-check.patch +Patch1430: 1430-eventpoll-defer-struct-eventpoll-free-to-rcu-grace-period.patch +Patch1431: 1431-eventpoll-use-hlist-is-singular-node-in-ep-remove.patch +Patch1432: 1432-eventpoll-split-ep-remove.patch +Patch1433: 1433-eventpoll-kill-ep-remove.patch +Patch1434: 1434-eventpoll-rename-ep-remove-safe-back-to-ep-remove.patch +Patch1435: 1435-eventpoll-move-epi-fget-up.patch +Patch1436: 1436-eventpoll-drop-vestigial-prefix-from-ep-remove-file-epi.patch +Patch1437: 1437-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch +Patch1438: 1438-eventpoll-move-f-lock-acquisition-into-ep-remove-file.patch +Patch1439: 1439-eventpoll-refresh-eventpoll-release-fast-path-comment.patch +Patch1440: 1440-eventpoll-drop-dead-bool-return-from-ep-remove-epi.patch +Patch1441: 1441-eventpoll-fix-semi-unbounded-recursion.patch +Patch1442: 1442-eventpoll-drop-vestigial-epi-dying-flag.patch +Patch1443: 1443-eventpoll-fix-integer-overflow-in-ep-loop-check-proc.patch +Patch1444: 1444-eventpoll-refresh-epi-fget-ep-remove-file-comments.patch # END OF PATCH DEFINITIONS %description @@ -2627,9 +2648,30 @@ ApplyPatch 1413-arm64-errata-mitigate-tlbi-errata-on-microsoft-azure-cobalt.patc ApplyPatch 1414-fs-smb-client-fix-out-of-bounds-read-in-cifs-sanitize-prepa.patch ApplyPatch 1415-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-gfn.patch ApplyPatch 1416-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-role.patch -ApplyPatch 1417-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch -ApplyPatch 1418-rtmutex-use-waiter-task-in-remove-waiter.patch -ApplyPatch 1420-rtmutex-skip-remove-waiter-when-not-enqueued.patch +ApplyPatch 1421-net-page-pool-avoid-false-positive-warning-if-napi-was-never.patch +ApplyPatch 1422-net-mana-fix-double-destroy-workqueue-on-service-rescan-pci.patch +ApplyPatch 1423-net-mana-null-service-wq-on-setup-error-to-prevent-double-de.patch +ApplyPatch 1424-net-ipv6-ioam6-prevent-schema-length-wraparound-in-trace-fil.patch +ApplyPatch 1425-procfs-fix-missing-rcu-protection-when-reading-real-parent-i.patch +ApplyPatch 1426-tg3-fix-race-for-querying-speed-duplex.patch +ApplyPatch 1427-smb-client-fix-off-by-8-bounds-check-in-check-wsl-eas.patch +ApplyPatch 1428-smb-client-fix-out-of-bounds-read-in-smb2-compound-op.patch +ApplyPatch 1429-epoll-annotate-racy-check.patch +ApplyPatch 1430-eventpoll-defer-struct-eventpoll-free-to-rcu-grace-period.patch +ApplyPatch 1431-eventpoll-use-hlist-is-singular-node-in-ep-remove.patch +ApplyPatch 1432-eventpoll-split-ep-remove.patch +ApplyPatch 1433-eventpoll-kill-ep-remove.patch +ApplyPatch 1434-eventpoll-rename-ep-remove-safe-back-to-ep-remove.patch +ApplyPatch 1435-eventpoll-move-epi-fget-up.patch +ApplyPatch 1436-eventpoll-drop-vestigial-prefix-from-ep-remove-file-epi.patch +ApplyPatch 1437-eventpoll-fix-ep-remove-struct-eventpoll-struct-file-uaf.patch +ApplyPatch 1438-eventpoll-move-f-lock-acquisition-into-ep-remove-file.patch +ApplyPatch 1439-eventpoll-refresh-eventpoll-release-fast-path-comment.patch +ApplyPatch 1440-eventpoll-drop-dead-bool-return-from-ep-remove-epi.patch +ApplyPatch 1441-eventpoll-fix-semi-unbounded-recursion.patch +ApplyPatch 1442-eventpoll-drop-vestigial-epi-dying-flag.patch +ApplyPatch 1443-eventpoll-fix-integer-overflow-in-ep-loop-check-proc.patch +ApplyPatch 1444-eventpoll-refresh-epi-fget-ep-remove-file-comments.patch # END OF PATCH APPLICATIONS # Any further pre-build tree manipulations happen here. @@ -5134,6 +5176,39 @@ fi\ # # %changelog +* Wed Jul 08 2026 Andrew Lukoshko - 6.12.0-211.31.1 +- Recreate RHEL 6.12.0-211.31.1 from CentOS Stream 10 and upstream stable backports (1421-1444) +- The AlmaLinux ahead-of-RHEL eventpoll CVE-2026-46242 fix (1417) is superseded by + RHEL's eventpoll series carrying the same fix and is dropped +- Temporarily drop the rtmutex remove_waiter() fixes (1418, 1420) +- RHEL changelog for 211.31.1 follows: + +* Mon Jul 06 2026 CKI KWF Bot [6.12.0-211.31.1.el10_2] +- eventpoll: refresh epi_fget() / ep_remove_file() comments (Ian Kent) [RHEL-180776] +- eventpoll: Fix integer overflow in ep_loop_check_proc() (Ian Kent) [RHEL-180776] +- eventpoll: drop vestigial epi->dying flag (Ian Kent) [RHEL-180776] +- eventpoll: Fix semi-unbounded recursion (Ian Kent) [RHEL-180776] {CVE-2025-38614} +- eventpoll: drop dead bool return from ep_remove_epi() (Ian Kent) [RHEL-180776] +- eventpoll: refresh eventpoll_release() fast-path comment (Ian Kent) [RHEL-180776] +- eventpoll: move f_lock acquisition into ep_remove_file() (Ian Kent) [RHEL-180776] +- eventpoll: fix ep_remove struct eventpoll / struct file UAF (Ian Kent) [RHEL-180776] {CVE-2026-46242} +- eventpoll: drop vestigial __ prefix from ep_remove_{file,epi}() (Ian Kent) [RHEL-180776] +- eventpoll: move epi_fget() up (Ian Kent) [RHEL-180776] +- eventpoll: rename ep_remove_safe() back to ep_remove() (Ian Kent) [RHEL-180776] +- eventpoll: kill __ep_remove() (Ian Kent) [RHEL-180776] +- eventpoll: split __ep_remove() (Ian Kent) [RHEL-180776] +- eventpoll: use hlist_is_singular_node() in __ep_remove() (Ian Kent) [RHEL-180776] +- eventpoll: defer struct eventpoll free to RCU grace period (Ian Kent) [RHEL-173832] {CVE-2026-43074} +- epoll: annotate racy check (Ian Kent) [RHEL-180776] +- smb/client: fix out-of-bounds read in smb2_compound_op() (Paulo Alcantara) [RHEL-180048] {CVE-2026-46155} +- smb: client: fix off-by-8 bounds check in check_wsl_eas() (Paulo Alcantara) [RHEL-180048] +- tg3: Fix race for querying speed/duplex (CKI Backport Bot) [RHEL-182770] +- procfs: fix missing RCU protection when reading real_parent in do_task_stat() (CKI Backport Bot) [RHEL-181902] {CVE-2026-46259} +- net/ipv6: ioam6: prevent schema length wraparound in trace fill (Antoine Tenart) [RHEL-174786] {CVE-2026-43341} +- net/mana: Null service_wq on setup error to prevent double destroy (CKI Backport Bot) [RHEL-180277] {CVE-2026-43276} +- net: mana: Fix double destroy_workqueue on service rescan PCI path (CKI Backport Bot) [RHEL-180277] {CVE-2026-43276} +- net: page_pool: avoid false positive warning if NAPI was never added (Ivan Vecera) [RHEL-162140] + * Tue Jul 08 2026 Andrew Lukoshko - 6.12.0-211.30.4 - Fix rtmutex self-deadlock NULL pointer dereference in remove_waiter() ahead of RHEL, upstream 3bfdc63936dd + 40a25d59e85b (1418, 1420); the futex_requeue guard