summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdvard Conrad <edvard@tuta.io>2026-10-29 22:18:40 +0100
committerChristian Brauner <brauner@kernel.org>2026-10-30 15:57:59 +0100
commitc3d355f427cea70fbe31b49e21f1915dbf37f2e6 (patch)
tree4124fa4b77cf2b7fe571d6f3c3e9ed28cf075299
parent79b496ebe382866e3a05090b7dc9ae95e85e65b2 (diff)
downloadlinux-c3d355f427cea70fbe31b49e21f1915dbf37f2e6.tar.gz
linux-c3d355f427cea70fbe31b49e21f1915dbf37f2e6.zip
fs: let backing files inherit FMODE_NONOTIFY from the user file
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> Link: https://patch.msgid.link/P58Wrer--F-9@tuta.io Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
-rw-r--r--fs/backing-file.c11
1 files changed, 11 insertions, 0 deletions
diff --git a/fs/backing-file.c b/fs/backing-file.c
index ad573f5153c7..5780f60662d0 100644
--- a/fs/backing-file.c
+++ b/fs/backing-file.c
@@ -55,6 +55,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);