Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e66e423 authored by Eric Paris's avatar Eric Paris
Browse files

fanotify: permissions and blocking



This is the backend work needed for fanotify to support the new
FS_OPEN_PERM and FS_ACCESS_PERM fsnotify events.  This is done using the
new fsnotify secondary queue.  No userspace interface is provided actually
respond to or request these events.

Signed-off-by: default avatarEric Paris <eparis@redhat.com>
parent c4ec54b4
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -10,3 +10,17 @@ config FANOTIFY
	   the event.

	   If unsure, say Y.

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
	   decisions concerning filesystem events.  This is used by some fanotify
	   listeners which need to scan files before allowing the system access to
	   use those files.  This is used by some anti-malware vendors and by some
	   hierarchical storage managent systems.

	   If unsure, say N.
+50 −4
Original line number Diff line number Diff line
@@ -2,9 +2,12 @@
#include <linux/fdtable.h>
#include <linux/fsnotify_backend.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/kernel.h> /* UINT_MAX */
#include <linux/mount.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/wait.h>

static bool should_merge(struct fsnotify_event *old, struct fsnotify_event *new)
{
@@ -88,10 +91,37 @@ out:
	return ret;
}

#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
static int fanotify_get_response_from_access(struct fsnotify_group *group,
					     struct fsnotify_event *event)
{
	int ret;

	pr_debug("%s: group=%p event=%p\n", __func__, group, event);

	wait_event(group->fanotify_data.access_waitq, event->response);

	/* userspace responded, convert to something usable */
	spin_lock(&event->lock);
	switch (event->response) {
	case FAN_ALLOW:
		ret = 0;
		break;
	case FAN_DENY:
	default:
		ret = -EPERM;
	}
	event->response = 0;
	spin_unlock(&event->lock);

	return ret;
}
#endif

static int fanotify_handle_event(struct fsnotify_group *group, struct fsnotify_event *event)
{
	int ret;
	struct fsnotify_event *used_event;
	struct fsnotify_event *notify_event = NULL;

	BUILD_BUG_ON(FAN_ACCESS != FS_ACCESS);
	BUILD_BUG_ON(FAN_MODIFY != FS_MODIFY);
@@ -100,15 +130,31 @@ static int fanotify_handle_event(struct fsnotify_group *group, struct fsnotify_e
	BUILD_BUG_ON(FAN_OPEN != FS_OPEN);
	BUILD_BUG_ON(FAN_EVENT_ON_CHILD != FS_EVENT_ON_CHILD);
	BUILD_BUG_ON(FAN_Q_OVERFLOW != FS_Q_OVERFLOW);
	BUILD_BUG_ON(FAN_OPEN_PERM != FS_OPEN_PERM);
	BUILD_BUG_ON(FAN_ACCESS_PERM != FS_ACCESS_PERM);

	pr_debug("%s: group=%p event=%p\n", __func__, group, event);

	ret = fsnotify_add_notify_event(group, event, NULL, fanotify_merge, (void **)&used_event);
	ret = fsnotify_add_notify_event(group, event, NULL, fanotify_merge,
					(void **)&notify_event);
	/* -EEXIST means this event was merged with another, not that it was an error */
	if (ret == -EEXIST)
		ret = 0;
	if (used_event)
		fsnotify_put_event(used_event);
	if (ret)
		goto out;

#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
	if (event->mask & FAN_ALL_PERM_EVENTS) {
		/* if we merged we need to wait on the new event */
		if (notify_event)
			event = notify_event;
		ret = fanotify_get_response_from_access(group, event);
	}
#endif

out:
	if (notify_event)
		fsnotify_put_event(notify_event);
	return ret;
}

+5 −0
Original line number Diff line number Diff line
@@ -482,6 +482,11 @@ SYSCALL_DEFINE3(fanotify_init, unsigned int, flags, unsigned int, event_f_flags,
		return PTR_ERR(group);

	group->priority = priority;
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
	mutex_init(&group->fanotify_data.access_mutex);
	init_waitqueue_head(&group->fanotify_data.access_waitq);
	INIT_LIST_HEAD(&group->fanotify_data.access_list);
#endif

	fd = anon_inode_getfd("[fanotify]", &fanotify_fops, group, f_flags);
	if (fd < 0)
+18 −0
Original line number Diff line number Diff line
@@ -15,6 +15,9 @@
/* FIXME currently Q's have no limit.... */
#define FAN_Q_OVERFLOW		0x00004000	/* Event queued overflowed */

#define FAN_OPEN_PERM		0x00010000	/* File open in perm check */
#define FAN_ACCESS_PERM		0x00020000	/* File accessed in perm check */

/* helper events */
#define FAN_CLOSE		(FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */

@@ -52,7 +55,14 @@
			FAN_CLOSE |\
			FAN_OPEN)

/*
 * All events which require a permission response from userspace
 */
#define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM |\
			     FAN_ACCESS_PERM)

#define FAN_ALL_OUTGOING_EVENTS	(FAN_ALL_EVENTS |\
				 FAN_ALL_PERM_EVENTS |\
				 FAN_Q_OVERFLOW)

#define FANOTIFY_METADATA_VERSION	1
@@ -65,6 +75,10 @@ struct fanotify_event_metadata {
	__s64 pid;
} __attribute__ ((packed));

/* Legit userspace responses to a _PERM event */
#define FAN_ALLOW	0x01
#define FAN_DENY	0x02

/* Helper functions to deal with fanotify_event_metadata buffers */
#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata))

@@ -78,5 +92,9 @@ struct fanotify_event_metadata {

#ifdef __KERNEL__

struct fanotify_wait {
	struct fsnotify_event *event;
	__s32 fd;
};
#endif /* __KERNEL__ */
#endif /* _LINUX_FANOTIFY_H */
+12 −0
Original line number Diff line number Diff line
@@ -159,6 +159,14 @@ struct fsnotify_group {
			struct fasync_struct    *fa;    /* async notification */
			struct user_struct      *user;
		} inotify_data;
#endif
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
		struct fanotify_group_private_data {
			/* allows a group to block waiting for a userspace response */
			struct mutex access_mutex;
			struct list_head access_list;
			wait_queue_head_t access_waitq;
		} fanotify_data;
#endif
	};
};
@@ -227,6 +235,10 @@ struct fsnotify_event {
	size_t name_len;
	struct pid *tgid;

#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
	__u32 response;	/* userspace answer to question */
#endif /* CONFIG_FANOTIFY_ACCESS_PERMISSIONS */

	struct list_head private_data_list;	/* groups can store private data here */
};