- Drop AlmaLinux ahead-of-RHEL eventpoll CVE-2026-46242 fix (1417), superseded by the RHEL eventpoll series in 211.31.1 - Temporarily drop the rtmutex remove_waiter() fixes (1418, 1420) - Add RHEL 211.31.1 backports recreated from CS10/upstream (1421-1444)
177 lines
6.0 KiB
Diff
177 lines
6.0 KiB
Diff
From 6ee3e28a7271944ba136a37d07ca35f0f7e3903b 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: Fix semi-unbounded recursion
|
|
|
|
JIRA: https://redhat.atlassian.net/browse/RHEL-180777
|
|
Upstream status: Linus
|
|
|
|
CVE: CVE-2025-38614
|
|
|
|
commit f2e467a48287c868818085aa35389a224d226732
|
|
Author: Jann Horn <jannh@google.com>
|
|
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 <jannh@google.com>
|
|
Link: https://lore.kernel.org/20250711-epoll-recursion-fix-v1-1-fb2457c33292@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 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)
|
|
|