Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

authored by

Jann Horn and committed by
Christian Brauner
f2e467a4 3bc4e441

+46 -14
+46 -14
fs/eventpoll.c
··· 218 218 /* used to optimize loop detection check */ 219 219 u64 gen; 220 220 struct hlist_head refs; 221 + u8 loop_check_depth; 221 222 222 223 /* 223 224 * usage count, used together with epitem->dying to ··· 2143 2142 } 2144 2143 2145 2144 /** 2146 - * ep_loop_check_proc - verify that adding an epoll file inside another 2147 - * epoll structure does not violate the constraints, in 2148 - * terms of closed loops, or too deep chains (which can 2149 - * result in excessive stack usage). 2145 + * ep_loop_check_proc - verify that adding an epoll file @ep inside another 2146 + * epoll file does not create closed loops, and 2147 + * determine the depth of the subtree starting at @ep 2150 2148 * 2151 2149 * @ep: the &struct eventpoll to be currently checked. 2152 2150 * @depth: Current depth of the path being checked. 2153 2151 * 2154 - * Return: %zero if adding the epoll @file inside current epoll 2155 - * structure @ep does not violate the constraints, or %-1 otherwise. 2152 + * Return: depth of the subtree, or INT_MAX if we found a loop or went too deep. 2156 2153 */ 2157 2154 static int ep_loop_check_proc(struct eventpoll *ep, int depth) 2158 2155 { 2159 - int error = 0; 2156 + int result = 0; 2160 2157 struct rb_node *rbp; 2161 2158 struct epitem *epi; 2159 + 2160 + if (ep->gen == loop_check_gen) 2161 + return ep->loop_check_depth; 2162 2162 2163 2163 mutex_lock_nested(&ep->mtx, depth + 1); 2164 2164 ep->gen = loop_check_gen; ··· 2168 2166 if (unlikely(is_file_epoll(epi->ffd.file))) { 2169 2167 struct eventpoll *ep_tovisit; 2170 2168 ep_tovisit = epi->ffd.file->private_data; 2171 - if (ep_tovisit->gen == loop_check_gen) 2172 - continue; 2173 2169 if (ep_tovisit == inserting_into || depth > EP_MAX_NESTS) 2174 - error = -1; 2170 + result = INT_MAX; 2175 2171 else 2176 - error = ep_loop_check_proc(ep_tovisit, depth + 1); 2177 - if (error != 0) 2172 + result = max(result, ep_loop_check_proc(ep_tovisit, depth + 1) + 1); 2173 + if (result > EP_MAX_NESTS) 2178 2174 break; 2179 2175 } else { 2180 2176 /* ··· 2186 2186 list_file(epi->ffd.file); 2187 2187 } 2188 2188 } 2189 + ep->loop_check_depth = result; 2189 2190 mutex_unlock(&ep->mtx); 2190 2191 2191 - return error; 2192 + return result; 2193 + } 2194 + 2195 + /** 2196 + * ep_get_upwards_depth_proc - determine depth of @ep when traversed upwards 2197 + */ 2198 + static int ep_get_upwards_depth_proc(struct eventpoll *ep, int depth) 2199 + { 2200 + int result = 0; 2201 + struct epitem *epi; 2202 + 2203 + if (ep->gen == loop_check_gen) 2204 + return ep->loop_check_depth; 2205 + hlist_for_each_entry_rcu(epi, &ep->refs, fllink) 2206 + result = max(result, ep_get_upwards_depth_proc(epi->ep, depth + 1) + 1); 2207 + ep->gen = loop_check_gen; 2208 + ep->loop_check_depth = result; 2209 + return result; 2192 2210 } 2193 2211 2194 2212 /** ··· 2222 2204 */ 2223 2205 static int ep_loop_check(struct eventpoll *ep, struct eventpoll *to) 2224 2206 { 2207 + int depth, upwards_depth; 2208 + 2225 2209 inserting_into = ep; 2226 - return ep_loop_check_proc(to, 0); 2210 + /* 2211 + * Check how deep down we can get from @to, and whether it is possible 2212 + * to loop up to @ep. 2213 + */ 2214 + depth = ep_loop_check_proc(to, 0); 2215 + if (depth > EP_MAX_NESTS) 2216 + return -1; 2217 + /* Check how far up we can go from @ep. */ 2218 + rcu_read_lock(); 2219 + upwards_depth = ep_get_upwards_depth_proc(ep, 0); 2220 + rcu_read_unlock(); 2221 + 2222 + return (depth+1+upwards_depth > EP_MAX_NESTS) ? -1 : 0; 2227 2223 } 2228 2224 2229 2225 static void clear_tfile_check_list(void)