eventpoll: document loop-check / path-check globals

The globals that support EPOLL_CTL_ADD's cycle and path-length checks
are scattered: epnested_mutex, loop_check_gen, inserting_into, and
tfile_check_list sit at the top of the file; path_count[] and
path_limits[] are declared inline with the path-check code further
down. Their interaction -- the "ep->gen == loop_check_gen" trigger in
do_epoll_ctl(), the two loop_check_gen++ bumps that sandwich a check,
the EP_UNACTIVE_PTR sentinel on tfile_check_list, the -ELOOP back-edge
detection via inserting_into -- is not documented anywhere.

The area has had three recent fixes (CVE-2025-38349, the unbounded
recursion fix, and the overflow fix) whose logic depends on these
invariants. Collect the description in one block alongside the
declarations, cross-reference the path_count[] declaration that lives
with the path-check code, and name the fix commits so future readers
can find the context.

Also add a short comment on struct epitems_head describing its
dual use (wrapper for non-epoll file->f_ep versus pointing into
&ep->refs for the epoll-watches-epoll case), which the old comment
on tfile_check_list had accidentally attached to the struct.

Comment-only; no functional change.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260424-work-epoll-rework-v1-2-249ed00a20f3@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-04-24 15:46:33 +02:00
parent 7c25a0bd4b
commit 35e7fa083a
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2

View File

@ -372,12 +372,54 @@ struct ep_pqueue {
/* Maximum number of epoll watched descriptors, per user */
static long max_user_watches __read_mostly;
/* Used for cycles detection */
/*
* Cycle and path-length checks at EPOLL_CTL_ADD
* ---------------------------------------------
*
* When EPOLL_CTL_ADD creates a link that either targets an eventpoll
* file or extends an existing chain of eventpolls, two checks run:
*
* 1. no cycle is being formed -- ep_loop_check() walks downward
* from the candidate target, and ep_get_upwards_depth_proc()
* walks upward from the outer ep, both bounded by EP_MAX_NESTS.
* 2. no file accumulates more than path_limits[depth] wakeup paths
* of a given length -- reverse_path_check().
*
* Both need a global view of the epoll topology and must be atomic
* with the insertion, so the scratch state below is all serialized by
* one global mutex, epnested_mutex. Non-nested inserts skip this
* machinery entirely and take only ep->mtx.
*
* epnested_mutex Serializes the whole check; also protects every
* other variable in this block plus path_count[]
* (declared with the path-check code further
* down).
* loop_check_gen Monotonic stamp, bumped once at the start of a
* check and once at the end. ep->gen caches the
* value under which ep was last visited by
* ep_loop_check_proc() or
* ep_get_upwards_depth_proc(); the post-check
* bump ensures those cached stamps can no longer
* equal loop_check_gen, so the
* "ep->gen == loop_check_gen" trigger in
* do_epoll_ctl() only fires while another check
* is in flight.
* inserting_into Outer eventpoll pointer for the lifetime of one
* ep_loop_check(); ep_loop_check_proc() fails
* with -ELOOP if the downward walk reaches it.
* tfile_check_list Singly-linked list of epitems_head objects
* collected by ep_loop_check_proc() during the
* walk, consumed by reverse_path_check()
* afterwards. Sentinel EP_UNACTIVE_PTR means no
* check is in flight.
*
* Commits fdcfce93073d ("eventpoll: Fix integer overflow in
* ep_loop_check_proc()") and f2e467a48287 ("eventpoll: Fix
* semi-unbounded recursion") hardened the walk; any refactor must
* preserve both bail-outs.
*/
static DEFINE_MUTEX(epnested_mutex);
static u64 loop_check_gen = 0;
/* Used to check for epoll file descriptor inclusion loops */
static struct eventpoll *inserting_into;
/* Slab cache used to allocate "struct epitem" */
@ -387,8 +429,10 @@ static struct kmem_cache *epi_cache __ro_after_init;
static struct kmem_cache *pwq_cache __ro_after_init;
/*
* List of files with newly added links, where we may need to limit the number
* of emanating paths. Protected by the epnested_mutex.
* Wrapper anchor for file->f_ep when the watched file is not itself an
* eventpoll; for the epoll-watches-epoll case, file->f_ep points at
* &watched_ep->refs directly. The ->next field threads
* tfile_check_list during one EPOLL_CTL_ADD path check.
*/
struct epitems_head {
struct hlist_head epitems;