lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Tue, 14 Oct 2008 16:52:23 -0400
From:	Eric Paris <eparis@...hat.com>
To:	linux-kernel@...r.kernel.org, malware-list@...ts.printk.net
Cc:	viro@...IV.linux.org.uk, alan@...rguk.ukuu.org.uk,
	arjan@...radead.org, greg@...ah.com, tytso@....edu
Subject: [PATCH -v2 10/16] fanotify: blocking and access granting

This patch introduces blocking an access granting for fanotify.  Events are
sent to userspace and the original process is blocked (assuming not
O_NONBLOCK) for at most the amount of time determined by the group.  Total
maximum blocking time is the sum of all groups possible blocking time.

O_NONBLOCK processes trying to read and a group registered to require
blocking/access control on read MUST have a fastpath entry in order to ever
make progress.

Signed-off-by: Eric Paris <eparis@...hat.com>
---

 fs/aio.c                 |    7 ++
 fs/notify/Makefile       |    2 -
 fs/notify/access.c       |  164 ++++++++++++++++++++++++++++++++++++++++++++++
 fs/notify/fanotify.c     |   46 +++++++++++--
 fs/notify/fanotify.h     |   17 ++++-
 fs/notify/group.c        |    8 ++
 fs/notify/notification.c |    2 +
 fs/open.c                |    5 +
 fs/read_write.c          |    6 ++
 include/linux/fanotify.h |   35 +++++++++-
 10 files changed, 278 insertions(+), 14 deletions(-)
 create mode 100644 fs/notify/access.c

diff --git a/fs/aio.c b/fs/aio.c
index f658441..008de79 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -31,6 +31,7 @@
 #include <linux/workqueue.h>
 #include <linux/security.h>
 #include <linux/eventfd.h>
+#include <linux/fanotify.h>
 
 #include <asm/kmap_types.h>
 #include <asm/uaccess.h>
@@ -1454,6 +1455,9 @@ static ssize_t aio_setup_iocb(struct kiocb *kiocb)
 		ret = security_file_permission(file, MAY_READ);
 		if (unlikely(ret))
 			break;
+		ret = fanotify(file, FAN_ACCESS_PERM);
+		if (unlikely(ret))
+			break;
 		ret = aio_setup_single_vector(kiocb);
 		if (ret)
 			break;
@@ -1486,6 +1490,9 @@ static ssize_t aio_setup_iocb(struct kiocb *kiocb)
 		ret = security_file_permission(file, MAY_READ);
 		if (unlikely(ret))
 			break;
+		ret = fanotify(file, FAN_ACCESS_PERM);
+		if (unlikely(ret))
+			break;
 		ret = aio_setup_vectored_rw(READ, kiocb);
 		if (ret)
 			break;
diff --git a/fs/notify/Makefile b/fs/notify/Makefile
index 90cb910..0af890a 100644
--- a/fs/notify/Makefile
+++ b/fs/notify/Makefile
@@ -3,4 +3,4 @@ obj-$(CONFIG_INOTIFY_USER)	+= inotify_user.o
 
 obj-$(CONFIG_DNOTIFY)		+= dnotify.o
 
-obj-$(CONFIG_FANOTIFY)		+= fanotify.o notification.o group.o fastpath.o
+obj-$(CONFIG_FANOTIFY)		+= fanotify.o notification.o group.o fastpath.o access.o
diff --git a/fs/notify/access.c b/fs/notify/access.c
new file mode 100644
index 0000000..6a2606c
--- /dev/null
+++ b/fs/notify/access.c
@@ -0,0 +1,164 @@
+/*
+ *  Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@...hat.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <asm/atomic.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/path.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include <linux/fanotify.h>
+#include "fanotify.h"
+
+int fanotify_add_event_to_access(struct fanotify_group *group, struct fanotify_event *event)
+{
+	struct fanotify_event_holder *holder;
+
+	if (list_empty(&event->holder.event_list))
+		holder = (struct fanotify_event_holder *)event;
+	else
+		holder = alloc_event_holder();
+	if (!holder)
+		return -ENOMEM;
+
+	fanotify_get_event(event);
+
+	mutex_lock(&group->access_mutex);
+	spin_lock(&event->holder_spinlock);
+	holder->event = event;
+	list_add_tail(&holder->event_list, &group->access_list);
+	spin_unlock(&event->holder_spinlock);
+	mutex_unlock(&group->access_mutex);
+
+	return 0;
+}
+
+int fanotify_get_response_from_access(struct fanotify_group *group, struct fanotify_event *event)
+{
+	int ret;
+	ret = wait_event_interruptible_timeout(group->access_waitq,
+						event->response,
+						msecs_to_jiffies(5000));
+	/* Timeout or signal? */
+	if (ret <= 0) {
+		struct fanotify_event_holder *holder;
+
+		/* pull the event off the access_list */
+		mutex_lock(&group->access_mutex);
+		list_for_each_entry(holder, &group->access_list, event_list) {
+			if (holder->event != event)
+				continue;
+
+			spin_lock(&event->holder_spinlock);
+			holder->event = NULL;
+			/* as soon as we do this the event.holder might get reused */
+			list_del_init(&holder->event_list);
+			spin_unlock(&event->holder_spinlock);
+
+			if (event != (struct fanotify_event *)holder)
+				fanotify_destroy_event_holder(holder);
+			fanotify_put_event(event);
+			break;
+		}
+		mutex_unlock(&group->access_mutex);
+
+		/*
+		 * if we took a signal, return ERESTARTSYS
+		 * if we timed out userspace is broken so return ALLOW
+		 */
+		return ret;
+	}
+
+	/* userspace responded, convert to something usable */
+	switch (event->response) {
+	case FAN_ALLOW:
+		return 0;
+	case FAN_DENY:
+	default:
+		return -EPERM;
+	}
+}
+
+int fanotify_process_access_response(struct fanotify_group *group, unsigned long cookie, unsigned int response)
+{
+	struct fanotify_event *event = NULL;
+	struct fanotify_event_holder *holder;
+
+	/*
+	 * make sure the response is valid, if invalid we do nothing and either
+	 * userspace can send a valid responce or we will clean it up after the
+	 * timeout
+	 */
+	if (response & ~(FAN_ALLOW | FAN_DENY))
+		return -EINVAL;
+
+	mutex_lock(&group->access_mutex);
+	list_for_each_entry(holder, &group->access_list, event_list) {
+		if (holder->event->cookie != cookie)
+			continue;
+
+		event = holder->event;
+		spin_lock(&event->holder_spinlock);
+		holder->event = NULL;
+		/* as soon as we do this the event.holder might be reused */
+		list_del_init(&holder->event_list);
+		spin_unlock(&event->holder_spinlock);
+
+		if (event != (struct fanotify_event *)holder)
+			fanotify_destroy_event_holder(holder);
+		break;
+	}
+	mutex_unlock(&group->access_mutex);
+
+	if (!event)
+		return -ENOENT;
+
+	event->response = response;
+	wake_up(&group->access_waitq);
+	fanotify_put_event(event);
+
+	return 0;
+}
+
+void fanotify_access_clear_group(struct fanotify_group *group)
+{
+	struct fanotify_event *event = NULL;
+	struct fanotify_event_holder *holder;
+
+	mutex_lock(&group->access_mutex);
+	list_for_each_entry(holder, &group->access_list, event_list) {
+		event = holder->event;
+		spin_lock(&event->holder_spinlock);
+		holder->event = NULL;
+		/* as soon as we do this the event.holder might be reused */
+		list_del_init(&holder->event_list);
+		spin_unlock(&event->holder_spinlock);
+
+		if (event != (struct fanotify_event *)holder)
+			fanotify_destroy_event_holder(holder);
+
+		event->response = FAN_ALLOW;
+		wake_up(&group->access_waitq);
+		fanotify_put_event(event);
+	}
+	mutex_unlock(&group->access_mutex);
+}
diff --git a/fs/notify/fanotify.c b/fs/notify/fanotify.c
index aa12e91..d995c14 100644
--- a/fs/notify/fanotify.c
+++ b/fs/notify/fanotify.c
@@ -44,22 +44,22 @@ void fanotify_inode_delete(struct inode *inode)
 }
 EXPORT_SYMBOL_GPL(fanotify_inode_delete);
 
-void fanotify(struct file *file, unsigned int mask)
+int fanotify(struct file *file, unsigned int mask)
 {
 	struct fanotify_group *group;
 	struct fanotify_event *event = NULL;
 	struct task_struct *tsk = current;
 	struct inode *inode = file->f_path.dentry->d_inode;
-	int idx;
+	int idx, ret = 0;
 
 	if (likely(list_empty(&fanotify_groups)))
-		return;
+		return 0;
 
 	if (tsk->flags & PF_NOFACCESS)
-		return;
+		return 0;
 
 	if (!S_ISREG(inode->i_mode))
-		return;
+		return 0;
 
 	if (mask & FAN_MODIFY)
 		fanotify_fastpath_clear(inode);
@@ -86,7 +86,39 @@ void fanotify(struct file *file, unsigned int mask)
 				if (!event)
 					break;
 			}
-			fanotify_add_event_to_notif(group, event);
+			if (mask & FAN_ALL_EVENTS_PERM) {
+				/*
+				 * if you register for READ_ACCESS you MUST be setting
+				 * fastpath events or the client will NEVER make progress.
+				 * open is a blocking event so we don't require fastpath
+				 * entries and will wait for a decision.
+				 *
+				 * someday we could do an add_timer here, schedule a workqueue,
+				 * and in there we could check to see if this file got a fastpath
+				 * groups which don't obey the protocol could be evicted or an
+				 * O_NONBLOCK exclusion added.  There are some locking and
+				 * security implications to doing it that way, so for now we
+				 * are just going to assume userspace is adding fastpaths for
+				 * O_NONBLOCK files.....
+				 */
+				if ((mask & FAN_ACCESS_PERM) && (file->f_flags & O_NONBLOCK)) {
+					fanotify_add_event_to_notif(group, event);
+					ret = -EWOULDBLOCK;
+					break;
+				}
+				event->cookie = atomic_long_inc_return(&group->cookie);
+				event->response = 0;
+				/* put on access_list first so userspace can't be so fast responding it isn't on the list yet */
+				fanotify_add_event_to_access(group, event);
+
+				fanotify_add_event_to_notif(group, event);
+
+				ret = fanotify_get_response_from_access(group, event);
+				if (ret)
+					break;
+			} else {
+				fanotify_add_event_to_notif(group, event);
+			}
 		}
 	}
 	srcu_read_unlock(&fanotify_grp_srcu_struct, idx);
@@ -96,6 +128,8 @@ void fanotify(struct file *file, unsigned int mask)
 	 */
 	if (event)
 		fanotify_put_event(event);
+
+	return ret;
 }
 EXPORT_SYMBOL_GPL(fanotify);
 
diff --git a/fs/notify/fanotify.h b/fs/notify/fanotify.h
index 091ea5e..e07703f 100644
--- a/fs/notify/fanotify.h
+++ b/fs/notify/fanotify.h
@@ -14,12 +14,14 @@
 #include <linux/wait.h>
 
 /*
- * A single event can be queued in multiple group->notification_lists.
+ * A single event can be queued in multiple group->notification_lists and to at
+ * most one group->access_list at the same time.
  *
- * each group->notification_list will point to an event_holer which in turns points
- * to the actual event that needs to be sent to userspace.
+ * each group->notification_list or group->access_list will point to an
+ * event_holer which in turns points to the actual event that needs to be sent
+ * to userspace.
  *
- * Seemed cheaper to create a refcnt'd event and a small holder for every group
+ * Seemed cheaper to create a refcnt'd event and a small holder for every list
  * than create a different event for every group
  * 
  */
@@ -44,6 +46,9 @@ struct fanotify_event {
 	struct path path;	/* path from the original access */
 	unsigned int mask;	/* the type of access */
 	atomic_t refcnt;	/* how many groups still are using/need to send this event */
+	/* if waiting for a userspace access answer this is the cookie they will send back */
+	unsigned long cookie;
+	unsigned int response;	/* userspace answer to question */
 };
 
 /*
@@ -95,4 +100,8 @@ extern void fanotify_fastpath_clear(struct inode *inode);
 extern __init int fanotify_fastpath_init(void);
 extern __init int fanotify_fastpath_uninit(void);
 
+extern int fanotify_add_event_to_access(struct fanotify_group *group, struct fanotify_event *event);
+extern int fanotify_get_response_from_access(struct fanotify_group *group, struct fanotify_event *event);
+extern void fanotify_access_clear_group(struct fanotify_group *group);
+
 #endif	/* _LINUX_FANOTIFY_PRIVATE_H */
diff --git a/fs/notify/group.c b/fs/notify/group.c
index 37b8630..68f895e 100644
--- a/fs/notify/group.c
+++ b/fs/notify/group.c
@@ -79,6 +79,11 @@ struct fanotify_group *fanotify_find_group(unsigned int priority, unsigned int g
 	mutex_init(&group->fastpath_mutex);
 	INIT_LIST_HEAD(&group->fastpath_entries);
 
+	atomic_set(&group->cookie, 0);
+	mutex_init(&group->access_mutex);
+	INIT_LIST_HEAD(&group->access_list);
+	init_waitqueue_head(&group->access_waitq);
+
 	/* Do we need to be the first entry? */
 	if (list_empty(&fanotify_groups)) {
 		list_add_rcu(&group->group_list, &fanotify_groups);
@@ -112,6 +117,9 @@ void fanotify_kill_group(struct fanotify_group *group)
 	/* clear all fastpath entries for this group */
 	fanotify_fastpath_clear_group(group);
 
+	/* set an allow for all outstanding access decisions */
+	fanotify_access_clear_group(group);
+
 	kfree(group);
 }
 
diff --git a/fs/notify/notification.c b/fs/notify/notification.c
index 3dd69ef..89bb02c 100644
--- a/fs/notify/notification.c
+++ b/fs/notify/notification.c
@@ -176,6 +176,7 @@ struct fanotify_event *create_event(struct file *file, unsigned int mask)
 	event->path.mnt = file->f_path.mnt;
 	path_get(&event->path);
 
+	event->cookie = 0;
 	event->mask = mask;
 
 	WARN_ON(!event->path.dentry);
@@ -241,6 +242,7 @@ int fanotify_create_event_fd(struct fanotify_group *group, struct fanotify_event
 
 	data->fd = client_fd;
 	data->mask = event->mask;
+	data->cookie = event->cookie;
 
 	fanotify_put_event(event);
 
diff --git a/fs/open.c b/fs/open.c
index 1057c34..b9d9335 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -29,6 +29,7 @@
 #include <linux/rcupdate.h>
 #include <linux/audit.h>
 #include <linux/falloc.h>
+#include <linux/fanotify.h>
 
 int vfs_statfs(struct dentry *dentry, struct kstatfs *buf)
 {
@@ -820,6 +821,10 @@ static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
 	if (error)
 		goto cleanup_all;
 
+	error = fanotify(f, FAN_OPEN_PERM);
+	if (error)
+		goto cleanup_all;
+
 	if (!open && f->f_op)
 		open = f->f_op->open;
 	if (open) {
diff --git a/fs/read_write.c b/fs/read_write.c
index 39f1fce..a74cf76 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -16,6 +16,7 @@
 #include <linux/syscalls.h>
 #include <linux/pagemap.h>
 #include <linux/splice.h>
+#include <linux/fanotify.h>
 #include "read_write.h"
 
 #include <asm/uaccess.h>
@@ -201,6 +202,11 @@ int rw_verify_area(int read_write, struct file *file, loff_t *ppos, size_t count
 				read_write == READ ? MAY_READ : MAY_WRITE);
 	if (retval)
 		return retval;
+	if (read_write == READ) {
+		retval = fanotify(file, FAN_ACCESS_PERM);
+		if (retval)
+			return retval;
+	}
 	return count > MAX_RW_COUNT ? MAX_RW_COUNT : count;
 }
 
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 22496b2..0405e4c 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -17,6 +17,10 @@
 #define FAN_OPEN_NOEXEC		0x00000010	/* File was opened */
 #define FAN_OPEN_EXEC		0x00000020	/* File was opened with the intention of being exec'ed */
 
+/* userspace may also request blocking for permission checks for open and read */
+#define FAN_ACCESS_PERM		0x00000100
+#define FAN_OPEN_PERM		0x00000200
+
 /* FIXME currently Q's have no limit.... */
 #define FAN_Q_OVERFLOW		0x80000000	/* Event queued overflowed */
 
@@ -33,6 +37,16 @@
 			 FAN_MODIFY |\
 			 FAN_CLOSE |\
 			 FAN_OPEN)
+/*
+ * Like the above list of events only these are the event types in which the
+ * kernel will wait for answers.
+ */
+#define FAN_ALL_EVENTS_PERM    (FAN_OPEN_PERM |\
+				FAN_ACCESS_PERM)
+
+/* answers will need to be either allow or deny */
+#define FAN_ALLOW		0x00000001
+#define FAN_DENY		0x00000002
 
 #ifdef __KERNEL__
 #include <linux/types.h>
@@ -53,6 +67,7 @@ struct fanotify_addr {
 struct fanotify_event_metadata {
 	int32_t fd;
 	uint32_t mask;
+	uint64_t cookie;
 };
 
 /* fanotify getsockopt optvals */
@@ -91,12 +106,18 @@ struct fanotify_group {
 	struct mutex fastpath_mutex;	/* protect fastpath_entries list */
 	struct list_head fastpath_entries; /* all fastpath entries for this group */
 
+	/* needed to track outstanding requests we expect to hear from userspace */
+	atomic_long_t cookie;		/* next cookie send to userspace for a decision */
+	struct mutex access_mutex;	/* protect access_list list */
+	struct list_head access_list;	/* event_holder we need an answer from userspace */
+	wait_queue_head_t access_waitq;	/* we wait here for userspace access decisions */
+
 	unsigned int priority;		/* order this group should receive msgs.  low first */
 };
 
 #ifdef CONFIG_FANOTIFY
 
-extern void fanotify(struct file *file, unsigned int mask);
+extern int fanotify(struct file *file, unsigned int mask);
 extern void fanotify_inode_delete(struct inode *inode);
 
 extern void fanotify_get_group(struct fanotify_group *group);
@@ -106,11 +127,14 @@ extern void fanotify_put_group(struct fanotify_group *group);
 /* things called from the socket */
 extern int fanotify_create_event_fd(struct fanotify_group *group, struct fanotify_event_metadata *data, int nonblock);
 extern int fanotify_fastpath_add(struct fanotify_group *group, int fd, unsigned int mask);
+extern int fanotify_process_access_response(struct fanotify_group *group, unsigned long cookie, unsigned int response);
 
 #else
 
-static inline void fanotify(struct file *file, unsigned int mask)
-{}
+static inline int fanotify(struct file *file, unsigned int mask)
+{
+	return 0;
+}
 
 static inline void fanotify_get_group(struct fanotify_group *group)
 {}
@@ -137,6 +161,11 @@ static inline int fanotify_fastpath_add(struct fanotify_group *group, int fd, un
 static inline void fanotify_inode_delete(struct inode *inode)
 {}
 
+static inline int fanotify_process_access_response(struct fanotify_group *group, unsigned long cookie, unsigned int response)
+{
+	return 0;
+}
+
 #endif	/* CONFIG_FANOTIFY */
 
 #endif	/* __KERNEL __ */

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ