mirror of
git://git.yoctoproject.org/linux-yocto.git
synced 2025-10-23 07:23:12 +02:00
eventpoll: Fix semi-unbounded recursion
commitf2e467a482
upstream. 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 commit28d82dc1c4
("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 commit8c44dac8ad
("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:22bacca48a
("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: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
237e416eb6
commit
2a0c0c974b
|
@ -217,6 +217,7 @@ struct eventpoll {
|
||||||
/* used to optimize loop detection check */
|
/* used to optimize loop detection check */
|
||||||
u64 gen;
|
u64 gen;
|
||||||
struct hlist_head refs;
|
struct hlist_head refs;
|
||||||
|
u8 loop_check_depth;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* usage count, used together with epitem->dying to
|
* usage count, used together with epitem->dying to
|
||||||
|
@ -1986,23 +1987,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
|
* ep_loop_check_proc - verify that adding an epoll file @ep inside another
|
||||||
* epoll structure does not violate the constraints, in
|
* epoll file does not create closed loops, and
|
||||||
* terms of closed loops, or too deep chains (which can
|
* determine the depth of the subtree starting at @ep
|
||||||
* result in excessive stack usage).
|
|
||||||
*
|
*
|
||||||
* @ep: the &struct eventpoll to be currently checked.
|
* @ep: the &struct eventpoll to be currently checked.
|
||||||
* @depth: Current depth of the path being checked.
|
* @depth: Current depth of the path being checked.
|
||||||
*
|
*
|
||||||
* Return: %zero if adding the epoll @file inside current epoll
|
* Return: depth of the subtree, or INT_MAX if we found a loop or went too deep.
|
||||||
* structure @ep does not violate the constraints, or %-1 otherwise.
|
|
||||||
*/
|
*/
|
||||||
static int ep_loop_check_proc(struct eventpoll *ep, int depth)
|
static int ep_loop_check_proc(struct eventpoll *ep, int depth)
|
||||||
{
|
{
|
||||||
int error = 0;
|
int result = 0;
|
||||||
struct rb_node *rbp;
|
struct rb_node *rbp;
|
||||||
struct epitem *epi;
|
struct epitem *epi;
|
||||||
|
|
||||||
|
if (ep->gen == loop_check_gen)
|
||||||
|
return ep->loop_check_depth;
|
||||||
|
|
||||||
mutex_lock_nested(&ep->mtx, depth + 1);
|
mutex_lock_nested(&ep->mtx, depth + 1);
|
||||||
ep->gen = loop_check_gen;
|
ep->gen = loop_check_gen;
|
||||||
for (rbp = rb_first_cached(&ep->rbr); rbp; rbp = rb_next(rbp)) {
|
for (rbp = rb_first_cached(&ep->rbr); rbp; rbp = rb_next(rbp)) {
|
||||||
|
@ -2010,13 +2012,11 @@ static int ep_loop_check_proc(struct eventpoll *ep, int depth)
|
||||||
if (unlikely(is_file_epoll(epi->ffd.file))) {
|
if (unlikely(is_file_epoll(epi->ffd.file))) {
|
||||||
struct eventpoll *ep_tovisit;
|
struct eventpoll *ep_tovisit;
|
||||||
ep_tovisit = epi->ffd.file->private_data;
|
ep_tovisit = epi->ffd.file->private_data;
|
||||||
if (ep_tovisit->gen == loop_check_gen)
|
|
||||||
continue;
|
|
||||||
if (ep_tovisit == inserting_into || depth > EP_MAX_NESTS)
|
if (ep_tovisit == inserting_into || depth > EP_MAX_NESTS)
|
||||||
error = -1;
|
result = INT_MAX;
|
||||||
else
|
else
|
||||||
error = ep_loop_check_proc(ep_tovisit, depth + 1);
|
result = max(result, ep_loop_check_proc(ep_tovisit, depth + 1) + 1);
|
||||||
if (error != 0)
|
if (result > EP_MAX_NESTS)
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
|
@ -2030,9 +2030,27 @@ static int ep_loop_check_proc(struct eventpoll *ep, int depth)
|
||||||
list_file(epi->ffd.file);
|
list_file(epi->ffd.file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ep->loop_check_depth = result;
|
||||||
mutex_unlock(&ep->mtx);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2048,8 +2066,22 @@ static int ep_loop_check_proc(struct eventpoll *ep, int depth)
|
||||||
*/
|
*/
|
||||||
static int ep_loop_check(struct eventpoll *ep, struct eventpoll *to)
|
static int ep_loop_check(struct eventpoll *ep, struct eventpoll *to)
|
||||||
{
|
{
|
||||||
|
int depth, upwards_depth;
|
||||||
|
|
||||||
inserting_into = ep;
|
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)
|
static void clear_tfile_check_list(void)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user