* [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file
@ 2026-10-29 21:18 Edvard Conrad
2026-10-30 9:26 ` Amir Goldstein
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Edvard Conrad @ 2026-10-29 21:18 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Alexander Viro, Christian Brauner
Cc: Jan Kara, linux-fsdevel, linux-unionfs, linux-kernel,
Edvard Conrad, stable
When fanotify reports a permission event, copy_event_to_user() creates
the event fd with dentry_open_nonotify(), so that the listener's use of
that fd does not generate further events. If the event's path is on a
stacked filesystem, the stacked filesystem's ->open() opens the real
file with backing_file_open(). Since commit ebe559609d78 ("fs: get rid
of __FMODE_NONOTIFY kludge"), FMODE_NONOTIFY lives in f_mode only and
the backing file does not inherit it: it is allocated with the default
fsnotify mode, and fsnotify_open_perm_and_set_mode() raises FS_OPEN_PERM
on the real path if the backing filesystem has permission event
watchers.
Before that commit __FMODE_NONOTIFY travelled in f_flags, and overlayfs
copies the user file's f_flags into the backing open
(ovl_open_realfile()), so the backing file was exempt implicitly. Commit
bc2473c90fca ("ovl: enable fsnotify events on underlying real files")
relied on that when it dropped FMODE_NONOTIFY from OVL_OPEN_FLAGS.
The nested event is queued to the same group, from inside the
listener's read(2), and can only be answered by another reader thread
of that group. A listener holding filesystem marks on both the stacked
and the backing filesystem therefore deadlocks as soon as all of its
reader threads are inside such a read(2) at the same time, and every
other task that touches a marked filesystem queues up behind it.
Observed with a commercial anti-malware agent (16 reader threads) on an
ostree/composefs host: / is an overlayfs whose data layer is the ostree
object store on the same btrfs that holds /etc and /home, and a
systemd-sysext overlay sits on /usr. Every on-demand scan of /usr wedged
the whole machine until the group was killed. Stack of a blocked reader
thread on 7.2.7:
fanotify_get_response
fanotify_handle_event
send_to_group
fsnotify
__fsnotify_parent
do_dentry_open
vfs_open
backing_file_open
ovl_open_realfile [overlay]
ovl_open [overlay]
do_dentry_open
vfs_open
backing_file_open
ovl_open_realfile [overlay]
ovl_open [overlay]
do_dentry_open
vfs_open
dentry_open_nonotify
copy_event_to_user
fanotify_read
vfs_read
ksys_read
Userspace cannot avoid this: the backing open happens while the kernel
builds the fd, before the event is reported, and fid reporting is not
available for permission event classes.
Make a backing file inherit FMODE_NONOTIFY when the user file it is
opened for must not generate events. This restores the pre-6.14
behaviour for the fds that fanotify creates (and for any other
FMODE_NONOTIFY file), while ordinary opens through a stacked filesystem
keep generating events on the real path, as intended by bc2473c90fca.
Verified with a self-contained reproducer (private tmpfs lower layers, an
overlayfs over them, FAN_OPEN_PERM filesystem marks on the overlay and on
one lower layer, a child opening a file through the overlay) in a qemu
VM on this tree built with defconfig plus OVERLAY_FS, FANOTIFY and
FANOTIFY_ACCESS_PERMISSIONS: without this patch the listener's read(2)
never returns and the child's open is released only by the listener's
death; with it read(2) returns, the event is answered and the open
completes.
backing_tmpfile_open() is left alone: its only caller is
ovl_create_tmpfile(), whose user file is a regular overlayfs file.
Fixes: ebe559609d78 ("fs: get rid of __FMODE_NONOTIFY kludge")
Cc: stable@vger.kernel.org
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Edvard Conrad <edvard@tuta.io>
---
v3:
- Back to the v1 change in backing_file_open(). The init_file()
variant posted as v2 was larger without being more general; Amir
withdrew that suggestion and reviewed v1 instead.
- Add Amir's Reviewed-by. Drop the Assisted-by and Suggested-by
trailers and the blank line in the trailer block, as asked.
- Keep the subject line from v2, which is the prefix used for
fs/backing-file.c.
- The reproducer posted inline with v1 is now an LTP test, under
review as syscalls/fanotify26:
https://lore.kernel.org/ltp/P50G-yE--F-9@tuta.io/
v2: https://lore.kernel.org/all/P4wV1J9--F-9@tuta.io/
v1: https://lore.kernel.org/all/P4sl5iz--F-9@tuta.io/
Tool disclosure (Documentation/process/generated-content.rst): the
stack analysis, the git-history search for the regressing commit, the
patch text, the commit message and the reproducer were produced with
an AI coding assistant (Anthropic Claude Code) under my direction over
interactive sessions; I reviewed, built and tested the result. No
static analysis tools beyond checkpatch were used.
fs/backing-file.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/fs/backing-file.c b/fs/backing-file.c
index 5614cb7801e..24357bf9328 100644
--- a/fs/backing-file.c
+++ b/fs/backing-file.c
@@ -43,6 +43,17 @@ struct file *backing_file_open(const struct file *user_file, int flags,
if (IS_ERR(f))
return f;
+ /*
+ * A user file that must not generate fsnotify events (e.g. the fd
+ * that fanotify creates for a permission event with
+ * dentry_open_nonotify()) must not generate them through its backing
+ * file either. Otherwise a listener watching the backing filesystem
+ * gets a nested permission event from its own read(2), which only
+ * another reader of the same group can answer.
+ */
+ if (FMODE_FSNOTIFY_NONE(user_file->f_mode))
+ file_set_fsnotify_mode(f, FMODE_NONOTIFY);
+
path_get(user_path);
backing_file_set_user_path(f, user_path);
error = vfs_open(real_path, f);
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file
2026-10-29 21:18 [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file Edvard Conrad
@ 2026-10-30 9:26 ` Amir Goldstein
2026-10-30 14:57 ` Christian Brauner
2026-10-30 14:58 ` Christian Brauner
2 siblings, 0 replies; 7+ messages in thread
From: Amir Goldstein @ 2026-10-30 9:26 UTC (permalink / raw)
To: Edvard Conrad
Cc: Miklos Szeredi, Alexander Viro, Christian Brauner, Jan Kara,
linux-fsdevel, linux-unionfs, linux-kernel, stable
On Thu, Oct 29, 2026 at 10:18 PM Edvard Conrad <edvard@tuta.io> wrote:
>
> When fanotify reports a permission event, copy_event_to_user() creates
> the event fd with dentry_open_nonotify(), so that the listener's use of
> that fd does not generate further events. If the event's path is on a
> stacked filesystem, the stacked filesystem's ->open() opens the real
> file with backing_file_open(). Since commit ebe559609d78 ("fs: get rid
> of __FMODE_NONOTIFY kludge"), FMODE_NONOTIFY lives in f_mode only and
> the backing file does not inherit it: it is allocated with the default
> fsnotify mode, and fsnotify_open_perm_and_set_mode() raises FS_OPEN_PERM
> on the real path if the backing filesystem has permission event
> watchers.
>
> Before that commit __FMODE_NONOTIFY travelled in f_flags, and overlayfs
> copies the user file's f_flags into the backing open
> (ovl_open_realfile()), so the backing file was exempt implicitly. Commit
> bc2473c90fca ("ovl: enable fsnotify events on underlying real files")
> relied on that when it dropped FMODE_NONOTIFY from OVL_OPEN_FLAGS.
>
> The nested event is queued to the same group, from inside the
> listener's read(2), and can only be answered by another reader thread
> of that group. A listener holding filesystem marks on both the stacked
> and the backing filesystem therefore deadlocks as soon as all of its
> reader threads are inside such a read(2) at the same time, and every
> other task that touches a marked filesystem queues up behind it.
>
> Observed with a commercial anti-malware agent (16 reader threads) on an
> ostree/composefs host: / is an overlayfs whose data layer is the ostree
> object store on the same btrfs that holds /etc and /home, and a
> systemd-sysext overlay sits on /usr. Every on-demand scan of /usr wedged
> the whole machine until the group was killed. Stack of a blocked reader
> thread on 7.2.7:
>
> fanotify_get_response
> fanotify_handle_event
> send_to_group
> fsnotify
> __fsnotify_parent
> do_dentry_open
> vfs_open
> backing_file_open
> ovl_open_realfile [overlay]
> ovl_open [overlay]
> do_dentry_open
> vfs_open
> backing_file_open
> ovl_open_realfile [overlay]
> ovl_open [overlay]
> do_dentry_open
> vfs_open
> dentry_open_nonotify
> copy_event_to_user
> fanotify_read
> vfs_read
> ksys_read
>
> Userspace cannot avoid this: the backing open happens while the kernel
> builds the fd, before the event is reported, and fid reporting is not
> available for permission event classes.
>
> Make a backing file inherit FMODE_NONOTIFY when the user file it is
> opened for must not generate events. This restores the pre-6.14
> behaviour for the fds that fanotify creates (and for any other
> FMODE_NONOTIFY file), while ordinary opens through a stacked filesystem
> keep generating events on the real path, as intended by bc2473c90fca.
>
> Verified with a self-contained reproducer (private tmpfs lower layers, an
> overlayfs over them, FAN_OPEN_PERM filesystem marks on the overlay and on
> one lower layer, a child opening a file through the overlay) in a qemu
> VM on this tree built with defconfig plus OVERLAY_FS, FANOTIFY and
> FANOTIFY_ACCESS_PERMISSIONS: without this patch the listener's read(2)
> never returns and the child's open is released only by the listener's
> death; with it read(2) returns, the event is answered and the open
> completes.
>
> backing_tmpfile_open() is left alone: its only caller is
> ovl_create_tmpfile(), whose user file is a regular overlayfs file.
>
> Fixes: ebe559609d78 ("fs: get rid of __FMODE_NONOTIFY kludge")
> Cc: stable@vger.kernel.org
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: Edvard Conrad <edvard@tuta.io>
> ---
> v3:
> - Back to the v1 change in backing_file_open(). The init_file()
> variant posted as v2 was larger without being more general; Amir
> withdrew that suggestion and reviewed v1 instead.
Apologies for the detour
> - Add Amir's Reviewed-by. Drop the Assisted-by and Suggested-by
> trailers and the blank line in the trailer block, as asked.
> - Keep the subject line from v2, which is the prefix used for
> fs/backing-file.c.
> - The reproducer posted inline with v1 is now an LTP test, under
> review as syscalls/fanotify26:
> https://lore.kernel.org/ltp/P50G-yE--F-9@tuta.io/
Thanks for following up on that!
Amir.
>
> v2: https://lore.kernel.org/all/P4wV1J9--F-9@tuta.io/
> v1: https://lore.kernel.org/all/P4sl5iz--F-9@tuta.io/
>
> Tool disclosure (Documentation/process/generated-content.rst): the
> stack analysis, the git-history search for the regressing commit, the
> patch text, the commit message and the reproducer were produced with
> an AI coding assistant (Anthropic Claude Code) under my direction over
> interactive sessions; I reviewed, built and tested the result. No
> static analysis tools beyond checkpatch were used.
>
> fs/backing-file.c | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/fs/backing-file.c b/fs/backing-file.c
> index 5614cb7801e..24357bf9328 100644
> --- a/fs/backing-file.c
> +++ b/fs/backing-file.c
> @@ -43,6 +43,17 @@ struct file *backing_file_open(const struct file *user_file, int flags,
> if (IS_ERR(f))
> return f;
>
> + /*
> + * A user file that must not generate fsnotify events (e.g. the fd
> + * that fanotify creates for a permission event with
> + * dentry_open_nonotify()) must not generate them through its backing
> + * file either. Otherwise a listener watching the backing filesystem
> + * gets a nested permission event from its own read(2), which only
> + * another reader of the same group can answer.
> + */
> + if (FMODE_FSNOTIFY_NONE(user_file->f_mode))
> + file_set_fsnotify_mode(f, FMODE_NONOTIFY);
> +
> path_get(user_path);
> backing_file_set_user_path(f, user_path);
> error = vfs_open(real_path, f);
> --
> 2.56.0
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file
2026-10-29 21:18 [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file Edvard Conrad
2026-10-30 9:26 ` Amir Goldstein
@ 2026-10-30 14:57 ` Christian Brauner
2026-10-30 17:13 ` Amir Goldstein
2026-10-30 14:58 ` Christian Brauner
2 siblings, 1 reply; 7+ messages in thread
From: Christian Brauner @ 2026-10-30 14:57 UTC (permalink / raw)
To: Edvard Conrad, Amir Goldstein
Cc: Miklos Szeredi, Alexander Viro, Jan Kara, linux-fsdevel,
linux-unionfs, linux-kernel, stable
On Thu, Oct 29, 2026 at 10:18:40PM +0100, Edvard Conrad wrote:
> Make a backing file inherit FMODE_NONOTIFY when the user file it is
> opened for must not generate events. This restores the pre-6.14
> behaviour for the fds that fanotify creates (and for any other
> FMODE_NONOTIFY file), while ordinary opens through a stacked filesystem
> keep generating events on the real path, as intended by bc2473c90fca.
Looks good to me. I'll take this via vfs.fixes and fix up the subject to
use the "backing-file:" prefix while applying.
Amir, note that the same deadlock is still there for directories.
ovl_dir_open_realfile() goes through ovl_path_open() -> dentry_open()
and not through backing_file_open(). So a FAN_OPEN_PERM|FAN_ONDIR
listener that opens an overlay directory through the fd fanotify handed
it wedges the same way. That needs to be fixed on the overlayfs side.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file
2026-10-29 21:18 [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file Edvard Conrad
2026-10-30 9:26 ` Amir Goldstein
2026-10-30 14:57 ` Christian Brauner
@ 2026-10-30 14:58 ` Christian Brauner
2 siblings, 0 replies; 7+ messages in thread
From: Christian Brauner @ 2026-10-30 14:58 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Alexander Viro, Edvard Conrad
Cc: Jan Kara, linux-fsdevel, linux-unionfs, linux-kernel, stable
On Thu, 29 Oct 2026 22:18:40 +0100, Edvard Conrad wrote:
> When fanotify reports a permission event, copy_event_to_user() creates
> the event fd with dentry_open_nonotify(), so that the listener's use of
> that fd does not generate further events. If the event's path is on a
> stacked filesystem, the stacked filesystem's ->open() opens the real
> file with backing_file_open(). Since commit ebe559609d78 ("fs: get rid
> of __FMODE_NONOTIFY kludge"), FMODE_NONOTIFY lives in f_mode only and
> the backing file does not inherit it: it is allocated with the default
> fsnotify mode, and fsnotify_open_perm_and_set_mode() raises FS_OPEN_PERM
> on the real path if the backing filesystem has permission event
> watchers.
>
> [...]
Applied to the vfs.fixes branch of the vfs/vfs.git tree.
Patches in the vfs.fixes branch should appear in linux-next soon.
Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.
It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.
Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.
tree: https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: vfs.fixes
[1/1] fs: let backing files inherit FMODE_NONOTIFY from the user file
https://git.kernel.org/vfs/vfs/c/c3d355f427ce
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file
2026-10-30 14:57 ` Christian Brauner
@ 2026-10-30 17:13 ` Amir Goldstein
2026-11-02 10:57 ` Edvard Conrad
0 siblings, 1 reply; 7+ messages in thread
From: Amir Goldstein @ 2026-10-30 17:13 UTC (permalink / raw)
To: Christian Brauner, Edvard Conrad
Cc: Miklos Szeredi, Alexander Viro, Jan Kara, linux-fsdevel,
linux-unionfs, linux-kernel, stable
[-- Attachment #1: Type: text/plain, Size: 1318 bytes --]
On Fri, Oct 30, 2026 at 3:57 PM Christian Brauner <brauner@kernel.org> wrote:
>
> On Thu, Oct 29, 2026 at 10:18:40PM +0100, Edvard Conrad wrote:
> > Make a backing file inherit FMODE_NONOTIFY when the user file it is
> > opened for must not generate events. This restores the pre-6.14
> > behaviour for the fds that fanotify creates (and for any other
> > FMODE_NONOTIFY file), while ordinary opens through a stacked filesystem
> > keep generating events on the real path, as intended by bc2473c90fca.
>
> Looks good to me. I'll take this via vfs.fixes and fix up the subject to
> use the "backing-file:" prefix while applying.
>
> Amir, note that the same deadlock is still there for directories.
> ovl_dir_open_realfile() goes through ovl_path_open() -> dentry_open()
> and not through backing_file_open(). So a FAN_OPEN_PERM|FAN_ONDIR
> listener that opens an overlay directory through the fd fanotify handed
> it wedges the same way. That needs to be fixed on the overlayfs side.
Urgh! for f sake, can we just bring back the damn Kludge?!%$
Edvard,
Would you mind adding test cases (or variants) to fanotify26 to cover this find
and test the attached (sanity tested only) patch.
No need to hold off on the merge, you can post another patch to
add the test cases.
Thanks,
Amir.
[-- Attachment #2: 0001-ovl-inherit-FMODE_NONOTIFY-when-opening-directory-re.patch --]
[-- Type: text/x-patch, Size: 4085 bytes --]
From ae0e3584fe9c32040889271b3ecec4580123a5cf Mon Sep 17 00:00:00 2001
From: Amir Goldstein <amir73il@gmail.com>
Date: Fri, 30 Oct 2026 18:07:02 +0100
Subject: [PATCH] ovl: inherit FMODE_NONOTIFY when opening directory realfiles
When fanotify hands a listener an fd for a permission event on an
overlay directory, ovl_dir_open_realfile() opens the real directory via
dentry_open(), not backing_file_open(), so the lower open could still
raise FS_OPEN_PERM and deadlock a listener that also marks the backing
filesystem.
Export dentry_open_nonotify() and call it from ovl_path_open_realfile()
when the overlay user file must not generate events. Ordinary
ovl_path_open() callers are unchanged.
Reported-by: Christian Brauner (Amutable) <brauner@kernel.org>
Closes: https://lore.kernel.org/all/20261030-welpen-anbinden-nullen-b2432cee1d10@brauner/
Fixes: ebe559609d78 ("fs: get rid of __FMODE_NONOTIFY kludge")
Cc: stable@vger.kernel.org
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
fs/open.c | 1 +
fs/overlayfs/overlayfs.h | 9 ++++++++-
fs/overlayfs/readdir.c | 3 ++-
fs/overlayfs/util.c | 11 ++++++++++-
4 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/fs/open.c b/fs/open.c
index 6b1c14e684a93..d7d9fcc666ab5 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1147,6 +1147,7 @@ struct file *dentry_open_nonotify(const struct path *path, int flags,
}
return f;
}
+EXPORT_SYMBOL_GPL(dentry_open_nonotify);
/**
* kernel_file_open - open a file for kernel internal use
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index 4faebb4c6825b..b32089a104291 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -529,7 +529,14 @@ void ovl_dir_modified(struct dentry *dentry, bool impurity);
u64 ovl_inode_version_get(struct inode *inode);
bool ovl_is_whiteout(struct dentry *dentry);
bool ovl_path_is_whiteout(struct ovl_fs *ofs, const struct path *path);
-struct file *ovl_path_open(const struct path *path, int flags);
+struct file *ovl_path_open_realfile(const struct file *user_file,
+ const struct path *path, int flags);
+
+static inline struct file *ovl_path_open(const struct path *path, int flags)
+{
+ return ovl_path_open_realfile(NULL, path, flags);
+}
+
int ovl_copy_up_start(struct dentry *dentry, int flags);
void ovl_copy_up_end(struct dentry *dentry);
bool ovl_already_copied_up(struct dentry *dentry, int flags);
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index b862309eba4d1..5d21a328fad46 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -994,7 +994,8 @@ static struct file *ovl_dir_open_realfile(const struct file *file,
const struct path *realpath)
{
with_ovl_creds(file_inode(file)->i_sb)
- return ovl_path_open(realpath, O_RDONLY | (file->f_flags & O_LARGEFILE));
+ return ovl_path_open_realfile(file, realpath,
+ O_RDONLY | (file->f_flags & O_LARGEFILE));
}
/*
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 47734eeb97b75..04103a6741ba0 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -654,7 +654,8 @@ bool ovl_path_is_whiteout(struct ovl_fs *ofs, const struct path *path)
ovl_path_check_xwhiteout_xattr(ofs, path);
}
-struct file *ovl_path_open(const struct path *path, int flags)
+struct file *ovl_path_open_realfile(const struct file *user_file,
+ const struct path *path, int flags)
{
struct inode *inode = d_inode(path->dentry);
struct mnt_idmap *real_idmap = mnt_idmap(path->mnt);
@@ -682,6 +683,14 @@ struct file *ovl_path_open(const struct path *path, int flags)
if (inode_owner_or_capable(real_idmap, inode))
flags |= O_NOATIME;
+ /*
+ * Directory opens use dentry_open() here, not backing_file_open().
+ * Inherit FMODE_NONOTIFY from a fanotify permission fd so a listener
+ * that also watches the real filesystem does not deadlock.
+ */
+ if (user_file && FMODE_FSNOTIFY_NONE(user_file->f_mode))
+ return dentry_open_nonotify(path, flags, current_cred());
+
return dentry_open(path, flags, current_cred());
}
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file
2026-10-30 17:13 ` Amir Goldstein
@ 2026-11-02 10:57 ` Edvard Conrad
2026-11-02 12:34 ` Amir Goldstein
0 siblings, 1 reply; 7+ messages in thread
From: Edvard Conrad @ 2026-11-02 10:57 UTC (permalink / raw)
To: Amir Goldstein, Christian Brauner
Cc: Miklos Szeredi, Alexander Viro, Jan Kara, linux-fsdevel,
linux-unionfs, linux-kernel, stable, Edvard Conrad
On Fri, Oct 30, 2026 at 6:13 PM Amir Goldstein <amir73il@gmail.com> wrote:
> Would you mind adding test cases (or variants) to fanotify26 to cover
> this find and test the attached (sanity tested only) patch.
Two things on the directory side, one settled and one not.
Agreed on 130fdbc3d1f99 ("ovl: pass correct flags for opening real
directory"). Before it ovl_dir_open() passed the user file's f_flags to
ovl_path_open(), and __FMODE_NONOTIFY travelled with them; the commit
reasons through every O_* flag but that one is not an O_* flag. That
fits v6.13 failing the directory case and passing the file case.
The one that is not settled: with your patch, listing a merged
directory through the event fd still deadlocks. ovl_iterate() sends a
directory that has OVL_WHITEOUTS to ovl_iterate_merged(), and
ovl_dir_read() opens each layer with plain ovl_path_open(), which no
user file reaches. A listener that calls getdents64() on the fd before
answering blocks here:
fanotify_handle_event
fsnotify
fsnotify_open_perm_and_set_mode
do_dentry_open
vfs_open
dentry_open
ovl_dir_read_merged
ovl_iterate
iterate_dir
__x64_sys_getdents64
Two controls on the same kernel: the same merged directory without the
listing passes, and a lower-only directory with the listing passes,
since od->realfile is nonotify with your patch and iterate_dir() uses
it directly. That was on 6b4b543fa18 with your patch applied (with some
fuzz); the readdir.c paths involved are the same in today's master. I
have it as an extra case in a local copy of fanotify26 and can post it
once there is a fix to run it against.
Incidentally, I was thinking about your ask for something more
generic: a task-level "creating an event fd" state in fanotify would
cover the file and directory opens in one place, and the copy-up opens
in copy_up.c too, which also go through plain ovl_path_open() and
which an O_RDWR group would trigger for a lower-only file (from
reading, not tested). It would not reach the listing case above, which
runs at getdents() time, long after the fd exists.
Thanks,
Edvard
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file
2026-11-02 10:57 ` Edvard Conrad
@ 2026-11-02 12:34 ` Amir Goldstein
0 siblings, 0 replies; 7+ messages in thread
From: Amir Goldstein @ 2026-11-02 12:34 UTC (permalink / raw)
To: Edvard Conrad
Cc: Christian Brauner, Miklos Szeredi, Alexander Viro, Jan Kara,
linux-fsdevel, linux-unionfs, linux-kernel, stable
[-- Attachment #1: Type: text/plain, Size: 2743 bytes --]
On Mon, Nov 2, 2026 at 11:58 AM Edvard Conrad <edvard@tuta.io> wrote:
>
> On Fri, Oct 30, 2026 at 6:13 PM Amir Goldstein <amir73il@gmail.com> wrote:
> > Would you mind adding test cases (or variants) to fanotify26 to cover
> > this find and test the attached (sanity tested only) patch.
>
> Two things on the directory side, one settled and one not.
>
> Agreed on 130fdbc3d1f99 ("ovl: pass correct flags for opening real
> directory"). Before it ovl_dir_open() passed the user file's f_flags to
> ovl_path_open(), and __FMODE_NONOTIFY travelled with them; the commit
> reasons through every O_* flag but that one is not an O_* flag. That
> fits v6.13 failing the directory case and passing the file case.
>
> The one that is not settled: with your patch, listing a merged
> directory through the event fd still deadlocks. ovl_iterate() sends a
> directory that has OVL_WHITEOUTS to ovl_iterate_merged(), and
> ovl_dir_read() opens each layer with plain ovl_path_open(), which no
> user file reaches. A listener that calls getdents64() on the fd before
> answering blocks here:
>
> fanotify_handle_event
> fsnotify
> fsnotify_open_perm_and_set_mode
> do_dentry_open
> vfs_open
> dentry_open
> ovl_dir_read_merged
> ovl_iterate
> iterate_dir
> __x64_sys_getdents64
>
> Two controls on the same kernel: the same merged directory without the
> listing passes, and a lower-only directory with the listing passes,
> since od->realfile is nonotify with your patch and iterate_dir() uses
> it directly. That was on 6b4b543fa18 with your patch applied (with some
> fuzz); the readdir.c paths involved are the same in today's master. I
> have it as an extra case in a local copy of fanotify26 and can post it
> once there is a fix to run it against.
Good catch!
Please test the attached v2 patch and post the new test cases.
>
> Incidentally, I was thinking about your ask for something more
> generic: a task-level "creating an event fd" state in fanotify would
> cover the file and directory opens in one place, and the copy-up opens
> in copy_up.c too, which also go through plain ovl_path_open() and
> which an O_RDWR group would trigger for a lower-only file (from
> reading, not tested). It would not reach the listing case above, which
> runs at getdents() time, long after the fd exists.
Writing into fanotify provided fds already has issues with potential deadlocks,
so I am not excited about making more changes to overlayfs to avert this.
Also it is the fd which is supposed to be marked NONOTIFY and not the listener
process because the listener process. We have thought about other models as well
but for now we need to keep it this way.
Thanks,
Amir.
[-- Attachment #2: v2-0001-ovl-inherit-FMODE_NONOTIFY-when-opening-directory.patch --]
[-- Type: text/x-patch, Size: 6705 bytes --]
From afc8e63b3054d65f7b0f2c1adf893ad625514397 Mon Sep 17 00:00:00 2001
From: Amir Goldstein <amir73il@gmail.com>
Date: Fri, 30 Oct 2026 18:07:02 +0100
Subject: [PATCH v2] ovl: inherit FMODE_NONOTIFY when opening directory
realfiles
When fanotify hands a listener an fd for a permission event on an
overlay directory, ovl_dir_open_realfile() opens the real directory via
dentry_open(), not backing_file_open(), so the lower open could still
raise FS_OPEN_PERM and deadlock a listener that also marks the backing
filesystem.
Export dentry_open_nonotify() and call it from ovl_path_open_realfile()
when the overlay user file must not generate events. Ordinary
ovl_path_open() callers are unchanged.
Reported-by: Christian Brauner (Amutable) <brauner@kernel.org>
Closes: https://lore.kernel.org/all/20261030-welpen-anbinden-nullen-b2432cee1d10@brauner/
Fixes: 130fdbc3d1f99 ("ovl: pass correct flags for opening real directory")
Tested-by: Edvard Conrad <edvard@tuta.io>
Link: https://lore.kernel.org/all/P5Iny41--F-9@tuta.io/
Cc: stable@vger.kernel.org
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
fs/open.c | 1 +
fs/overlayfs/overlayfs.h | 9 ++++++++-
fs/overlayfs/readdir.c | 20 ++++++++++++--------
fs/overlayfs/util.c | 11 ++++++++++-
4 files changed, 31 insertions(+), 10 deletions(-)
diff --git a/fs/open.c b/fs/open.c
index e43f02ff64acb..bb484b07390ac 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1172,6 +1172,7 @@ struct file *dentry_open_nonotify(const struct path *path, int flags,
}
return f;
}
+EXPORT_SYMBOL_GPL(dentry_open_nonotify);
/**
* kernel_file_open - open a file for kernel internal use
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index e9389a10452e8..b5a672e8e4764 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -531,7 +531,14 @@ void ovl_dir_modified(struct dentry *dentry, bool impurity);
u64 ovl_inode_version_get(struct inode *inode);
bool ovl_is_whiteout(struct dentry *dentry);
bool ovl_path_is_whiteout(struct ovl_fs *ofs, const struct path *path);
-struct file *ovl_path_open(const struct path *path, int flags);
+struct file *ovl_path_open_realfile(const struct file *user_file,
+ const struct path *path, int flags);
+
+static inline struct file *ovl_path_open(const struct path *path, int flags)
+{
+ return ovl_path_open_realfile(NULL, path, flags);
+}
+
int ovl_copy_up_start(struct dentry *dentry, int flags);
void ovl_copy_up_end(struct dentry *dentry);
bool ovl_already_copied_up(struct dentry *dentry, int flags);
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index b862309eba4d1..d51b24e2bbd9a 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -43,6 +43,7 @@ struct ovl_dir_cache {
struct ovl_readdir_data {
struct dir_context ctx;
+ const struct file *user_file;
struct dentry *dentry;
bool is_lowest;
struct rb_root *root;
@@ -377,7 +378,8 @@ static inline int ovl_dir_read(const struct path *realpath,
struct file *realfile;
int err;
- realfile = ovl_path_open(realpath, O_RDONLY | O_LARGEFILE);
+ realfile = ovl_path_open_realfile(rdd->user_file, realpath,
+ O_RDONLY | O_LARGEFILE);
if (IS_ERR(realfile))
return PTR_ERR(realfile);
@@ -421,13 +423,14 @@ static void ovl_dir_reset(struct file *file)
}
static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list,
- struct rb_root *root)
+ struct rb_root *root, const struct file *user_file)
{
int err;
struct path realpath;
struct ovl_readdir_data rdd = {
.ctx.actor = ovl_fill_merge,
.ctx.count = INT_MAX,
+ .user_file = user_file,
.dentry = dentry,
.list = list,
.root = root,
@@ -498,9 +501,10 @@ static struct ovl_dir_cache *ovl_dir_cache_check(struct inode *inode,
return cache;
}
-static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry)
+static struct ovl_dir_cache *ovl_dir_cache_get(struct file *file)
{
int res;
+ struct dentry *dentry = file->f_path.dentry;
struct ovl_dir_cache *cache;
struct inode *inode = d_inode(dentry);
@@ -521,7 +525,7 @@ static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry)
INIT_LIST_HEAD(&cache->entries);
cache->root = RB_ROOT;
- res = ovl_dir_read_merged(dentry, &cache->entries, &cache->root);
+ res = ovl_dir_read_merged(dentry, &cache->entries, &cache->root, file);
if (res) {
ovl_cache_free(&cache->entries);
kfree(cache);
@@ -866,14 +870,13 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)
static int ovl_iterate_merged(struct file *file, struct dir_context *ctx)
{
struct ovl_dir_file *od = file->private_data;
- struct dentry *dentry = file->f_path.dentry;
struct ovl_cache_entry *p;
int err;
if (!od->cache) {
struct ovl_dir_cache *cache;
- cache = ovl_cache_get(dentry);
+ cache = ovl_dir_cache_get(file);
if (IS_ERR(cache))
return PTR_ERR(cache);
@@ -994,7 +997,8 @@ static struct file *ovl_dir_open_realfile(const struct file *file,
const struct path *realpath)
{
with_ovl_creds(file_inode(file)->i_sb)
- return ovl_path_open(realpath, O_RDONLY | (file->f_flags & O_LARGEFILE));
+ return ovl_path_open_realfile(file, realpath,
+ O_RDONLY | (file->f_flags & O_LARGEFILE));
}
/*
@@ -1119,7 +1123,7 @@ int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list)
struct rb_root root = RB_ROOT;
with_ovl_creds(dentry->d_sb)
- err = ovl_dir_read_merged(dentry, list, &root);
+ err = ovl_dir_read_merged(dentry, list, &root, NULL);
if (err)
return err;
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 6c3fda047f94f..788c6870d67df 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -654,7 +654,8 @@ bool ovl_path_is_whiteout(struct ovl_fs *ofs, const struct path *path)
ovl_path_check_xwhiteout_xattr(ofs, path);
}
-struct file *ovl_path_open(const struct path *path, int flags)
+struct file *ovl_path_open_realfile(const struct file *user_file,
+ const struct path *path, int flags)
{
struct inode *inode = d_inode(path->dentry);
const struct mnt_idmap *real_idmap = mnt_idmap(path->mnt);
@@ -682,6 +683,14 @@ struct file *ovl_path_open(const struct path *path, int flags)
if (inode_owner_or_capable(real_idmap, inode))
flags |= O_NOATIME;
+ /*
+ * Directory opens use dentry_open() here, not backing_file_open().
+ * Inherit FMODE_NONOTIFY from a fanotify permission fd so a listener
+ * that also watches the real filesystem does not deadlock.
+ */
+ if (user_file && FMODE_FSNOTIFY_NONE(user_file->f_mode))
+ return dentry_open_nonotify(path, flags, current_cred());
+
return dentry_open(path, flags, current_cred());
}
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-02 12:34 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-29 21:18 [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file Edvard Conrad
2026-10-30 9:26 ` Amir Goldstein
2026-10-30 14:57 ` Christian Brauner
2026-10-30 17:13 ` Amir Goldstein
2026-11-02 10:57 ` Edvard Conrad
2026-11-02 12:34 ` Amir Goldstein
2026-10-30 14:58 ` Christian Brauner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®