Merge tag 'fsnotify_for_v6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs

Pull fsnotify updates from Jan Kara:

 - a couple of small cleanups and fixes

 - implement fanotify watchdog that reports processes that fail to
   respond to fanotify permission events in a given time

* tag 'fsnotify_for_v6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs:
  fanotify: add watchdog for permission events
  fanotify: Validate the return value of mnt_ns_from_dentry() before dereferencing
  fsnotify: fix "rewriten"->"rewritten"
This commit is contained in:
Linus Torvalds
2025-10-03 13:23:10 -07:00
4 changed files with 110 additions and 1 deletions

View File

@@ -441,7 +441,9 @@ struct fanotify_perm_event {
size_t count;
u32 response; /* userspace answer to the event */
unsigned short state; /* state of the event */
unsigned short watchdog_cnt; /* already scanned by watchdog? */
int fd; /* fd we passed to userspace for this event */
pid_t recv_pid; /* pid of task receiving the event */
union {
struct fanotify_response_info_header hdr;
struct fanotify_response_info_audit_rule audit_rule;

View File

@@ -50,6 +50,7 @@
/* configurable via /proc/sys/fs/fanotify/ */
static int fanotify_max_queued_events __read_mostly;
static int perm_group_timeout __read_mostly;
#ifdef CONFIG_SYSCTL
@@ -85,6 +86,14 @@ static const struct ctl_table fanotify_table[] = {
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO
},
{
.procname = "watchdog_timeout",
.data = &perm_group_timeout,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
},
};
static void __init fanotify_sysctls_init(void)
@@ -95,6 +104,91 @@ static void __init fanotify_sysctls_init(void)
#define fanotify_sysctls_init() do { } while (0)
#endif /* CONFIG_SYSCTL */
static LIST_HEAD(perm_group_list);
static DEFINE_SPINLOCK(perm_group_lock);
static void perm_group_watchdog(struct work_struct *work);
static DECLARE_DELAYED_WORK(perm_group_work, perm_group_watchdog);
static void perm_group_watchdog_schedule(void)
{
schedule_delayed_work(&perm_group_work, secs_to_jiffies(perm_group_timeout));
}
static void perm_group_watchdog(struct work_struct *work)
{
struct fsnotify_group *group;
struct fanotify_perm_event *event;
struct task_struct *task;
pid_t failed_pid = 0;
guard(spinlock)(&perm_group_lock);
if (list_empty(&perm_group_list))
return;
list_for_each_entry(group, &perm_group_list,
fanotify_data.perm_grp_list) {
/*
* Ok to test without lock, racing with an addition is
* fine, will deal with it next round
*/
if (list_empty(&group->fanotify_data.access_list))
continue;
spin_lock(&group->notification_lock);
list_for_each_entry(event, &group->fanotify_data.access_list,
fae.fse.list) {
if (likely(event->watchdog_cnt == 0)) {
event->watchdog_cnt = 1;
} else if (event->watchdog_cnt == 1) {
/* Report on event only once */
event->watchdog_cnt = 2;
/* Do not report same pid repeatedly */
if (event->recv_pid == failed_pid)
continue;
failed_pid = event->recv_pid;
rcu_read_lock();
task = find_task_by_pid_ns(event->recv_pid,
&init_pid_ns);
pr_warn_ratelimited(
"PID %u (%s) failed to respond to fanotify queue for more than %d seconds\n",
event->recv_pid,
task ? task->comm : NULL,
perm_group_timeout);
rcu_read_unlock();
}
}
spin_unlock(&group->notification_lock);
}
perm_group_watchdog_schedule();
}
static void fanotify_perm_watchdog_group_remove(struct fsnotify_group *group)
{
if (!list_empty(&group->fanotify_data.perm_grp_list)) {
/* Perm event watchdog can no longer scan this group. */
spin_lock(&perm_group_lock);
list_del_init(&group->fanotify_data.perm_grp_list);
spin_unlock(&perm_group_lock);
}
}
static void fanotify_perm_watchdog_group_add(struct fsnotify_group *group)
{
if (!perm_group_timeout)
return;
spin_lock(&perm_group_lock);
if (list_empty(&group->fanotify_data.perm_grp_list)) {
/* Add to perm_group_list for monitoring by watchdog. */
if (list_empty(&perm_group_list))
perm_group_watchdog_schedule();
list_add_tail(&group->fanotify_data.perm_grp_list, &perm_group_list);
}
spin_unlock(&perm_group_lock);
}
/*
* All flags that may be specified in parameter event_f_flags of fanotify_init.
*
@@ -953,6 +1047,7 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
spin_lock(&group->notification_lock);
list_add_tail(&event->fse.list,
&group->fanotify_data.access_list);
FANOTIFY_PERM(event)->recv_pid = current->pid;
spin_unlock(&group->notification_lock);
}
}
@@ -1012,6 +1107,8 @@ static int fanotify_release(struct inode *ignored, struct file *file)
*/
fsnotify_group_stop_queueing(group);
fanotify_perm_watchdog_group_remove(group);
/*
* Process all permission events on access_list and notification queue
* and simulate reply from userspace.
@@ -1465,6 +1562,10 @@ out:
fsnotify_group_unlock(group);
fsnotify_put_mark(fsn_mark);
if (!ret && (mask & FANOTIFY_PERM_EVENTS))
fanotify_perm_watchdog_group_add(group);
return ret;
}
@@ -1625,6 +1726,7 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
group->fanotify_data.f_flags = event_f_flags;
init_waitqueue_head(&group->fanotify_data.access_waitq);
INIT_LIST_HEAD(&group->fanotify_data.access_list);
INIT_LIST_HEAD(&group->fanotify_data.perm_grp_list);
switch (class) {
case FAN_CLASS_NOTIF:
group->priority = FSNOTIFY_PRIO_NORMAL;
@@ -1999,7 +2101,10 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
user_ns = path.mnt->mnt_sb->s_user_ns;
obj = path.mnt->mnt_sb;
} else if (obj_type == FSNOTIFY_OBJ_TYPE_MNTNS) {
ret = -EINVAL;
mntns = mnt_ns_from_dentry(path.dentry);
if (!mntns)
goto path_put_and_out;
user_ns = mntns->user_ns;
obj = mntns;
}

View File

@@ -10,7 +10,7 @@
* Copyright 2006 Hewlett-Packard Development Company, L.P.
*
* Copyright (C) 2009 Eric Paris <Red Hat Inc>
* inotify was largely rewriten to make use of the fsnotify infrastructure
* inotify was largely rewritten to make use of the fsnotify infrastructure
*/
#include <linux/dcache.h> /* d_unlinked */

View File

@@ -273,6 +273,8 @@ struct fsnotify_group {
int f_flags; /* event_f_flags from fanotify_init() */
struct ucounts *ucounts;
mempool_t error_events_pool;
/* chained on perm_group_list */
struct list_head perm_grp_list;
} fanotify_data;
#endif /* CONFIG_FANOTIFY */
};