Re: [PATCH] eventpoll: pin files while checking reverse paths

From: Guidong Han

Date: Thu Jul 23 2026 - 23:26:29 EST


On Fri, Jul 24, 2026 at 2:06 AM David Laight
<david.laight.linux@xxxxxxxxx> wrote:
>
> On Sat, 18 Jul 2026 18:44:06 +0800
> Guidong Han <2045gemini@xxxxxxxxx> wrote:
>
> > Commit 319c15174757 ("epoll: take epitem list out of struct file")
> > intentionally removed temporary file references from the reverse path
> > check list. At the time, both epitems and their files were freed after
> > an RCU grace period, so unlist_file() could obtain file->f_lock through
> > an epitem while clear_tfile_check_list() held rcu_read_lock().
> >
> > Commit 0ede61d8589c ("file: convert to SLAB_TYPESAFE_BY_RCU") made
> > struct file SLAB_TYPESAFE_BY_RCU and removed its RCU-delayed freeing.
> > RCU still protects the epitem, but no longer keeps the referenced file
> > from being freed and reused. A concurrent close can therefore make
> > unlist_file() lock or unlock f_lock in a recycled file object.
> >
> > This violates the documented SLAB_TYPESAFE_BY_RCU rule requiring a
> > reference before acquiring an object's lock. The race was reproduced,
> > causing a wild unlock of f_lock in a recycled file and breaking its
> > mutual exclusion.
> >
> > Add ->file to epitems_head to remember the pinned file independently of
> > ->epitems. A concurrent EPOLL_CTL_DEL can empty ->epitems before the head
> > is unlisted, leaving no epi->ffd.file from which to drop the reference.
> >
> > In list_file(), acquire the reference before adding the head to the
> > check list. The caller either owns a reference or holds the ep->mtx for
> > the epitem leading to the file. In the latter case, file_ref_get() can
> > fail after the last reference is dropped, but eventpoll_release_file()
> > must acquire the same mutex before the file can be freed. The dying leaf
> > can be skipped because removing links cannot increase the reverse path
> > count.
> >
> > In unlist_file(), epnested_mutex excludes another list_file() or
> > unlist_file(), while head->next prevents a concurrent EPOLL_CTL_DEL from
> > freeing the head. Save head->file locally, clear it with head->next
> > under f_lock, and drop the reference after the RCU-protected operation.
>
> That seems to hold a reference to 'file' that persists after EPOLL_CTL_ADD
> returns.

Thanks for checking.

The reference does not persist after EPOLL_CTL_ADD returns. I went back
and carefully audited both list_file() call sites and every subsequent
exit path. Whenever list_file() adds a head to tfile_check_list,
clear_tfile_check_list() drops the temporary reference before
do_epoll_ctl_file() returns, including on error paths.