summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2024-11-21 09:55:45 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2024-11-21 09:55:45 -0800
commit2dde263d81dc6ded2df086bf9db05396c7c215ee (patch)
tree39af457872c697320cb24e49052f161381f97c51
parentc01f664e4ca210823b7594b50669bbd9b0a3c3b0 (diff)
parent21d1b618b6b9da46c5116c640ac4b1cc8d40d63a (diff)
downloadlinux-2dde263d81dc6ded2df086bf9db05396c7c215ee.tar.gz
linux-2dde263d81dc6ded2df086bf9db05396c7c215ee.tar.bz2
linux-2dde263d81dc6ded2df086bf9db05396c7c215ee.zip
Merge tag 'fsnotify_for_v6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs
Pull fsnotify updates from Jan Kara: "A couple of smaller random fsnotify fixes" * tag 'fsnotify_for_v6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs: fsnotify: Fix ordering of iput() and watched_objects decrement fsnotify: fix sending inotify event with unexpected filename fanotify: allow reporting errors on failure to open fd fsnotify, lsm: Decouple fsnotify from lsm
-rw-r--r--fs/notify/fanotify/Kconfig1
-rw-r--r--fs/notify/fanotify/fanotify_user.c85
-rw-r--r--fs/notify/fsnotify.c23
-rw-r--r--fs/notify/mark.c12
-rw-r--r--fs/open.c4
-rw-r--r--include/linux/fanotify.h1
-rw-r--r--include/uapi/linux/fanotify.h1
-rw-r--r--security/security.c9
8 files changed, 77 insertions, 59 deletions
diff --git a/fs/notify/fanotify/Kconfig b/fs/notify/fanotify/Kconfig
index a511f9d8677b..0e36aaf379b7 100644
--- a/fs/notify/fanotify/Kconfig
+++ b/fs/notify/fanotify/Kconfig
@@ -15,7 +15,6 @@ config FANOTIFY
config FANOTIFY_ACCESS_PERMISSIONS
bool "fanotify permissions checking"
depends on FANOTIFY
- depends on SECURITY
default n
help
Say Y here is you want fanotify listeners to be able to make permissions
diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c
index 35159fa0b063..2d85c71717d6 100644
--- a/fs/notify/fanotify/fanotify_user.c
+++ b/fs/notify/fanotify/fanotify_user.c
@@ -265,13 +265,6 @@ static int create_fd(struct fsnotify_group *group, const struct path *path,
group->fanotify_data.f_flags | __FMODE_NONOTIFY,
current_cred());
if (IS_ERR(new_file)) {
- /*
- * we still send an event even if we can't open the file. this
- * can happen when say tasks are gone and we try to open their
- * /proc files or we try to open a WRONLY file like in sysfs
- * we just send the errno to userspace since there isn't much
- * else we can do.
- */
put_unused_fd(client_fd);
client_fd = PTR_ERR(new_file);
} else {
@@ -662,7 +655,7 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
unsigned int info_mode = FAN_GROUP_FLAG(group, FANOTIFY_INFO_MODES);
unsigned int pidfd_mode = info_mode & FAN_REPORT_PIDFD;
struct file *f = NULL, *pidfd_file = NULL;
- int ret, pidfd = FAN_NOPIDFD, fd = FAN_NOFD;
+ int ret, pidfd = -ESRCH, fd = -EBADF;
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
@@ -690,10 +683,39 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
if (!FAN_GROUP_FLAG(group, FANOTIFY_UNPRIV) &&
path && path->mnt && path->dentry) {
fd = create_fd(group, path, &f);
- if (fd < 0)
- return fd;
+ /*
+ * Opening an fd from dentry can fail for several reasons.
+ * For example, when tasks are gone and we try to open their
+ * /proc files or we try to open a WRONLY file like in sysfs
+ * or when trying to open a file that was deleted on the
+ * remote network server.
+ *
+ * For a group with FAN_REPORT_FD_ERROR, we will send the
+ * event with the error instead of the open fd, otherwise
+ * Userspace may not get the error at all.
+ * In any case, userspace will not know which file failed to
+ * open, so add a debug print for further investigation.
+ */
+ if (fd < 0) {
+ pr_debug("fanotify: create_fd(%pd2) failed err=%d\n",
+ path->dentry, fd);
+ if (!FAN_GROUP_FLAG(group, FAN_REPORT_FD_ERROR)) {
+ /*
+ * Historically, we've handled EOPENSTALE in a
+ * special way and silently dropped such
+ * events. Now we have to keep it to maintain
+ * backward compatibility...
+ */
+ if (fd == -EOPENSTALE)
+ fd = 0;
+ return fd;
+ }
+ }
}
- metadata.fd = fd;
+ if (FAN_GROUP_FLAG(group, FAN_REPORT_FD_ERROR))
+ metadata.fd = fd;
+ else
+ metadata.fd = fd >= 0 ? fd : FAN_NOFD;
if (pidfd_mode) {
/*
@@ -708,18 +730,16 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
* The PIDTYPE_TGID check for an event->pid is performed
* preemptively in an attempt to catch out cases where the event
* listener reads events after the event generating process has
- * already terminated. Report FAN_NOPIDFD to the event listener
- * in those cases, with all other pidfd creation errors being
- * reported as FAN_EPIDFD.
+ * already terminated. Depending on flag FAN_REPORT_FD_ERROR,
+ * report either -ESRCH or FAN_NOPIDFD to the event listener in
+ * those cases with all other pidfd creation errors reported as
+ * the error code itself or as FAN_EPIDFD.
*/
- if (metadata.pid == 0 ||
- !pid_has_task(event->pid, PIDTYPE_TGID)) {
- pidfd = FAN_NOPIDFD;
- } else {
+ if (metadata.pid && pid_has_task(event->pid, PIDTYPE_TGID))
pidfd = pidfd_prepare(event->pid, 0, &pidfd_file);
- if (pidfd < 0)
- pidfd = FAN_EPIDFD;
- }
+
+ if (!FAN_GROUP_FLAG(group, FAN_REPORT_FD_ERROR) && pidfd < 0)
+ pidfd = pidfd == -ESRCH ? FAN_NOPIDFD : FAN_EPIDFD;
}
ret = -EFAULT;
@@ -736,9 +756,6 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
buf += FAN_EVENT_METADATA_LEN;
count -= FAN_EVENT_METADATA_LEN;
- if (fanotify_is_perm_event(event->mask))
- FANOTIFY_PERM(event)->fd = fd;
-
if (info_mode) {
ret = copy_info_records_to_user(event, info, info_mode, pidfd,
buf, count);
@@ -752,15 +769,18 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
if (pidfd_file)
fd_install(pidfd, pidfd_file);
+ if (fanotify_is_perm_event(event->mask))
+ FANOTIFY_PERM(event)->fd = fd;
+
return metadata.event_len;
out_close_fd:
- if (fd != FAN_NOFD) {
+ if (f) {
put_unused_fd(fd);
fput(f);
}
- if (pidfd >= 0) {
+ if (pidfd_file) {
put_unused_fd(pidfd);
fput(pidfd_file);
}
@@ -827,15 +847,6 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
}
ret = copy_event_to_user(group, event, buf, count);
- if (unlikely(ret == -EOPENSTALE)) {
- /*
- * We cannot report events with stale fd so drop it.
- * Setting ret to 0 will continue the event loop and
- * do the right thing if there are no more events to
- * read (i.e. return bytes read, -EAGAIN or wait).
- */
- ret = 0;
- }
/*
* Permission events get queued to wait for response. Other
@@ -844,7 +855,7 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
if (!fanotify_is_perm_event(event->mask)) {
fsnotify_destroy_event(group, &event->fse);
} else {
- if (ret <= 0) {
+ if (ret <= 0 || FANOTIFY_PERM(event)->fd < 0) {
spin_lock(&group->notification_lock);
finish_permission_event(group,
FANOTIFY_PERM(event), FAN_DENY, NULL);
@@ -1941,7 +1952,7 @@ static int __init fanotify_user_setup(void)
FANOTIFY_DEFAULT_MAX_USER_MARKS);
BUILD_BUG_ON(FANOTIFY_INIT_FLAGS & FANOTIFY_INTERNAL_GROUP_FLAGS);
- BUILD_BUG_ON(HWEIGHT32(FANOTIFY_INIT_FLAGS) != 12);
+ BUILD_BUG_ON(HWEIGHT32(FANOTIFY_INIT_FLAGS) != 13);
BUILD_BUG_ON(HWEIGHT32(FANOTIFY_MARK_FLAGS) != 11);
fanotify_mark_cache = KMEM_CACHE(fanotify_mark,
diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c
index 82ae8254c068..f976949d2634 100644
--- a/fs/notify/fsnotify.c
+++ b/fs/notify/fsnotify.c
@@ -333,16 +333,19 @@ static int fsnotify_handle_event(struct fsnotify_group *group, __u32 mask,
if (!inode_mark)
return 0;
- if (mask & FS_EVENT_ON_CHILD) {
- /*
- * Some events can be sent on both parent dir and child marks
- * (e.g. FS_ATTRIB). If both parent dir and child are
- * watching, report the event once to parent dir with name (if
- * interested) and once to child without name (if interested).
- * The child watcher is expecting an event without a file name
- * and without the FS_EVENT_ON_CHILD flag.
- */
- mask &= ~FS_EVENT_ON_CHILD;
+ /*
+ * Some events can be sent on both parent dir and child marks (e.g.
+ * FS_ATTRIB). If both parent dir and child are watching, report the
+ * event once to parent dir with name (if interested) and once to child
+ * without name (if interested).
+ *
+ * In any case regardless whether the parent is watching or not, the
+ * child watcher is expecting an event without the FS_EVENT_ON_CHILD
+ * flag. The file name is expected if and only if this is a directory
+ * event.
+ */
+ mask &= ~FS_EVENT_ON_CHILD;
+ if (!(mask & ALL_FSNOTIFY_DIRENT_EVENTS)) {
dir = NULL;
name = NULL;
}
diff --git a/fs/notify/mark.c b/fs/notify/mark.c
index c45b222cf9c1..4981439e6209 100644
--- a/fs/notify/mark.c
+++ b/fs/notify/mark.c
@@ -138,8 +138,11 @@ static void fsnotify_get_sb_watched_objects(struct super_block *sb)
static void fsnotify_put_sb_watched_objects(struct super_block *sb)
{
- if (atomic_long_dec_and_test(fsnotify_sb_watched_objects(sb)))
- wake_up_var(fsnotify_sb_watched_objects(sb));
+ atomic_long_t *watched_objects = fsnotify_sb_watched_objects(sb);
+
+ /* the superblock can go away after this decrement */
+ if (atomic_long_dec_and_test(watched_objects))
+ wake_up_var(watched_objects);
}
static void fsnotify_get_inode_ref(struct inode *inode)
@@ -150,8 +153,11 @@ static void fsnotify_get_inode_ref(struct inode *inode)
static void fsnotify_put_inode_ref(struct inode *inode)
{
- fsnotify_put_sb_watched_objects(inode->i_sb);
+ /* read ->i_sb before the inode can go away */
+ struct super_block *sb = inode->i_sb;
+
iput(inode);
+ fsnotify_put_sb_watched_objects(sb);
}
/*
diff --git a/fs/open.c b/fs/open.c
index 4b37c5912e6c..e6911101fe71 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -929,6 +929,10 @@ static int do_dentry_open(struct file *f,
if (error)
goto cleanup_all;
+ error = fsnotify_open_perm(f);
+ if (error)
+ goto cleanup_all;
+
error = break_lease(file_inode(f), f->f_flags);
if (error)
goto cleanup_all;
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 4f1c4f603118..89ff45bd6f01 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -36,6 +36,7 @@
#define FANOTIFY_ADMIN_INIT_FLAGS (FANOTIFY_PERM_CLASSES | \
FAN_REPORT_TID | \
FAN_REPORT_PIDFD | \
+ FAN_REPORT_FD_ERROR | \
FAN_UNLIMITED_QUEUE | \
FAN_UNLIMITED_MARKS)
diff --git a/include/uapi/linux/fanotify.h b/include/uapi/linux/fanotify.h
index a37de58ca571..34f221d3a1b9 100644
--- a/include/uapi/linux/fanotify.h
+++ b/include/uapi/linux/fanotify.h
@@ -60,6 +60,7 @@
#define FAN_REPORT_DIR_FID 0x00000400 /* Report unique directory id */
#define FAN_REPORT_NAME 0x00000800 /* Report events with name */
#define FAN_REPORT_TARGET_FID 0x00001000 /* Report dirent target id */
+#define FAN_REPORT_FD_ERROR 0x00002000 /* event->fd can report error */
/* Convenience macro - FAN_REPORT_NAME requires FAN_REPORT_DIR_FID */
#define FAN_REPORT_DFID_NAME (FAN_REPORT_DIR_FID | FAN_REPORT_NAME)
diff --git a/security/security.c b/security/security.c
index e2d47dd4087a..09664e09fec9 100644
--- a/security/security.c
+++ b/security/security.c
@@ -19,7 +19,6 @@
#include <linux/kernel.h>
#include <linux/kernel_read_file.h>
#include <linux/lsm_hooks.h>
-#include <linux/fsnotify.h>
#include <linux/mman.h>
#include <linux/mount.h>
#include <linux/personality.h>
@@ -3103,13 +3102,7 @@ int security_file_receive(struct file *file)
*/
int security_file_open(struct file *file)
{
- int ret;
-
- ret = call_int_hook(file_open, file);
- if (ret)
- return ret;
-
- return fsnotify_open_perm(file);
+ return call_int_hook(file_open, file);
}
/**