Re: [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file
From: Amir Goldstein
Date: Fri Oct 30 2026 - 13:17:47 EST
On Fri, Oct 30, 2026 at 3:57 PM Christian Brauner <brauner@xxxxxxxxxx> 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.
From ae0e3584fe9c32040889271b3ecec4580123a5cf Mon Sep 17 00:00:00 2001
From: Amir Goldstein <amir73il@xxxxxxxxx>
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@xxxxxxxxxx>
Closes: https://lore.kernel.org/all/20261030-welpen-anbinden-nullen-b2432cee1d10@brauner/
Fixes: ebe559609d78 ("fs: get rid of __FMODE_NONOTIFY kludge")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Amir Goldstein <amir73il@xxxxxxxxx>
---
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