linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications
@ 2018-07-23 15:25 David Howells
  2018-07-23 15:25 ` [PATCH 1/5] General notification queue with user mmap()'able ring buffer David Howells
                   ` (7 more replies)
  0 siblings, 8 replies; 18+ messages in thread
From: David Howells @ 2018-07-23 15:25 UTC (permalink / raw)
  To: viro
  Cc: linux-fsdevel, dhowells, linux-kernel, raven, keyrings,
	linux-security-module


Hi Al,

Here's a set of patches to add a general variable-length notification queue
concept and to add sources of events for:

 (1) Mount topology and reconfiguration change events.

 (2) Superblocks EIO, ENOSPC and EDQUOT events (not complete yet).

 (3) Key/keyring changes events

One of the reasons for this is so that we can remove the issue of processes
having to repeatedly and regularly scan /proc/mounts, which has proven to be a
system performance problem.


Design decisions:

 (1) A misc chardev is used to create and open a ring buffer:

	fd = open("/dev/watch_queue", O_RDWR);

     which is then configured and mmap'd into userspace:

	ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE);
	ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
	buf = mmap(NULL, BUF_SIZE * page_size, PROT_READ | PROT_WRITE,
		   MAP_SHARED, fd, 0);

     The fd cannot be read or written (though there is a facility to use write
     to inject records for debugging) and userspace just pulls data directly
     out of the buffer.

 (2) The ring index pointers are stored inside the ring and are thus
     accessible to userspace.  Userspace should only update the tail pointer
     and never the head pointer or risk breaking the buffer.  The kernel
     checks that the pointers appear valid before trying to use them.  A
     'skip' record is maintained around the pointers.

 (3) poll() can be used to wait for data to appear in the buffer.

 (4) Records in the buffer are binary, typed and have a length so that they
     can be of varying size.

     This means that multiple heterogeneous sources can share a common
     buffer.  Tags may be specified when a watchpoint is created to help
     distinguish the sources.

 (5) The queue is reusable as there are 16 million types available, of which
     I've used 4, so there is scope for others to be used.

 (6) Records are filterable as types have up to 256 subtypes that can be
     individually filtered.  Other filtration is also available.

 (7) Each time the buffer is opened, a new buffer is created - this means that
     there's no interference between watchers.

 (8) When recording a notification, the kernel will not sleep, but will rather
     mark a queue as overrun if there's insufficient space, thereby avoiding
     userspace causing the kernel to hang.

 (9) The 'watchpoint' should be specific where possible, meaning that you
     specify the object that you want to watch.

(10) The buffer is created and then watchpoints are attached to it, using one
     of:

	keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01);
	mount_notify(AT_FDCWD, "/", 0, fd, 0x02);
	sb_notify(AT_FDCWD, "/mnt", 0, fd, 0x03);

     where in all three cases, fd indicates the queue and the number after is
     a tag between 0 and 255.

(11) The watch must be removed if either the watch buffer is destroyed or the
     watched object is destroyed.


Things I want to avoid:

 (1) Introducing features that make the core VFS dependent on the network
     stack or networking namespaces (ie. usage of netlink).

 (2) Dumping all this stuff into dmesg and having a daemon that sits there
     parsing the output and distributing it as this then puts the
     responsibility for security into userspace and makes handling namespaces
     tricky.  Further, dmesg might not exist or might be inaccessible inside a
     container.

 (3) Letting users see events they shouldn't be able to see.


Further things that need to be done:

 (1) fsinfo() syscall needs to find superblocks by ID as well as by path so
     that it can query a superblock for information without the need to try
     and work out how to reach it - if the calling process even can.

 (2) A mount_info() syscall is needed that can enumerate all the children of a
     mount.  This is necessary because mountpoints can hide each other by
     stacking, so paths are not unique keys.  This will require the ability to
     look up a mount by ID.  This avoids the need to parse /proc/mounts.

 (3) A keyctl call is needed to allow a watch on a keyring to be extended to
     "children" of that keyring, such that the watch is removed from the child
     if it is unlinked from the keyring.

 (4) A global superblock event queue maybe?

 (5) Propagating watches to child superblock over automounts?


The patches can be found here also:

	http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=notifications

David
---
David Howells (5):
      General notification queue with user mmap()'able ring buffer
      KEYS: Add a notification facility
      vfs: Add a mount-notification facility
      vfs: Add superblock notifications
      Add sample notification program


 Documentation/security/keys/core.rst   |   59 ++
 Documentation/watch_queue.rst          |  305 ++++++++++++
 arch/x86/entry/syscalls/syscall_32.tbl |    2 
 arch/x86/entry/syscalls/syscall_64.tbl |    2 
 drivers/misc/Kconfig                   |    9 
 drivers/misc/Makefile                  |    1 
 drivers/misc/watch_queue.c             |  835 ++++++++++++++++++++++++++++++++
 fs/Kconfig                             |   21 +
 fs/Makefile                            |    1 
 fs/fs_context.c                        |    1 
 fs/mount.h                             |   26 +
 fs/mount_notify.c                      |  178 +++++++
 fs/namespace.c                         |   18 +
 fs/super.c                             |  116 ++++
 include/linux/dcache.h                 |    1 
 include/linux/fs.h                     |   77 +++
 include/linux/key.h                    |    4 
 include/linux/syscalls.h               |    4 
 include/linux/watch_queue.h            |   87 +++
 include/uapi/linux/keyctl.h            |    1 
 include/uapi/linux/watch_queue.h       |  156 ++++++
 kernel/sys_ni.c                        |    6 
 mm/interval_tree.c                     |    2 
 mm/memory.c                            |    1 
 samples/Kconfig                        |    6 
 samples/Makefile                       |    2 
 samples/watch_queue/Makefile           |    9 
 samples/watch_queue/watch_test.c       |  232 +++++++++
 security/keys/Kconfig                  |   10 
 security/keys/compat.c                 |    3 
 security/keys/gc.c                     |    5 
 security/keys/internal.h               |   29 +
 security/keys/key.c                    |   37 +
 security/keys/keyctl.c                 |   90 +++
 security/keys/keyring.c                |   17 -
 security/keys/request_key.c            |    4 
 36 files changed, 2332 insertions(+), 25 deletions(-)
 create mode 100644 Documentation/watch_queue.rst
 create mode 100644 drivers/misc/watch_queue.c
 create mode 100644 fs/mount_notify.c
 create mode 100644 include/linux/watch_queue.h
 create mode 100644 include/uapi/linux/watch_queue.h
 create mode 100644 samples/watch_queue/Makefile
 create mode 100644 samples/watch_queue/watch_test.c


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 1/5] General notification queue with user mmap()'able ring buffer
  2018-07-23 15:25 [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications David Howells
@ 2018-07-23 15:25 ` David Howells
  2018-07-23 15:25 ` [PATCH 2/5] KEYS: Add a notification facility David Howells
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: David Howells @ 2018-07-23 15:25 UTC (permalink / raw)
  To: viro
  Cc: linux-fsdevel, dhowells, linux-kernel, raven, keyrings,
	linux-security-module

Implement a misc device that implements a general notification queue as a
ring buffer that can be mmap()'d from userspace.

The way this is done is:

 (1) An application opens the device and indicates the size of the ring
     buffer that it wants to reserve in pages (this can only be set once):

	fd = open("/dev/sync_user", O_RDWR);
	ioctl(fd, IOC_WATCH_QUEUE_NR_PAGES, nr_of_pages);

 (2) The application should then map the pages that the device has
     reserved.  Each instance of the device created by open() allocates
     separate pages so that maps of different fds don't interfere with one
     another.  Multiple mmap() calls on the same fd, however, will all work
     together.

	page_size = sysconf(_SC_PAGESIZE);
	mapping_size = nr_of_pages * page_size;
	char *buf = mmap(NULL, mapping_size, PROT_READ|PROT_WRITE,
			 MAP_SHARED, fd, 0);

The ring is divided into 8-byte slots.  Entries written into the ring are
variable size and can use between 1 and 63 slots.  A special entry is
maintained in the first two slots of the ring that contains the head and tail
pointers.  This is skipped when the ring wraps round.  Note that multislot
entries, therefore, aren't allowed to be broken over the end of the ring, but
instead "skip" entries are inserted to pad out the buffer.

Each entry has a 1-slot header that describes it:

	struct watch_notification {
		__u32	type:24;
		__u32	subtype:8;
		__u32	info;
	};

The type indicates the source (eg. mount tree changes, superblock events,
keyring changes) and the subtype indicates the event type (eg. mount, unmount;
EIO, EDQUOT; link, unlink).  The info field indicates a number of things,
including the entry length, an ID assigned to a watchpoint contributing to
this buffer, type-specific flags and meta flags, such as an overrun indicator.

Supplementary data, such as the key ID that generated an event, are attached
in additional slots.

Signed-off-by: David Howells <dhowells@redhat.com>
---

 Documentation/watch_queue.rst    |  305 ++++++++++++++
 drivers/misc/Kconfig             |    9 
 drivers/misc/Makefile            |    1 
 drivers/misc/watch_queue.c       |  835 ++++++++++++++++++++++++++++++++++++++
 include/linux/watch_queue.h      |   87 ++++
 include/uapi/linux/watch_queue.h |   81 ++++
 mm/interval_tree.c               |    2 
 mm/memory.c                      |    1 
 8 files changed, 1321 insertions(+)
 create mode 100644 Documentation/watch_queue.rst
 create mode 100644 drivers/misc/watch_queue.c
 create mode 100644 include/linux/watch_queue.h
 create mode 100644 include/uapi/linux/watch_queue.h

diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
new file mode 100644
index 000000000000..e7dc1007976d
--- /dev/null
+++ b/Documentation/watch_queue.rst
@@ -0,0 +1,305 @@
+============================
+Mappable notifications queue
+============================
+
+This is a misc device that acts as a mapped ring buffer by which userspace can
+receive notifications from the kernel.  This is can be used in conjunction
+with::
+
+  * Key/keyring notifications
+
+  * Mount topology change notifications
+
+  * Superblock event notifications
+
+
+The notifications buffers can be enabled by:
+
+	"Device Drivers"/"Misc devices"/"Mappable notification queue"
+	(CONFIG_WATCH_QUEUE)
+
+This document has the following sections:
+
+.. contents:: :local:
+
+
+Overview
+========
+
+This facility appears as a misc device file that is opened and then mapped and
+polled.  Each time it is opened, it creates a new buffer specific to the
+returned file descriptor.  Then, when the opening process sets watches, it
+indicates that particular buffer it wants notifications from that watch to be
+written into.  Note that there are no read() and write() methods (except for
+debugging).  The user is expected to access the ring directly and to use poll
+to wait for new data.
+
+If a watch is in place, notifications are only written into the buffer if the
+filter criteria are passed and if there's sufficient space available in the
+ring.  If neither of those is so, a notification will be discarded.  In the
+latter case, an overrun indicator will also be set.
+
+Note that when producing a notification, the kernel does not wait for the
+consumers to collect it, but rather just continues on.  This means that
+notifications can be generated whilst spinlocks are held and also protects the
+kernel from being held up indefinitely by a userspace malfunction.
+
+As far as the ring goes, the head index belongs to the kernel and the tail
+index belongs to userspace.  The kernel will refuse to write anything if the
+tail index becomes invalid.  Userspace *must* use appropriate memory barriers
+between reading or updating the tail index and reading the ring.
+
+
+Record Structure
+================
+
+Notification records in the ring may occupy a variable number of slots within
+the buffer, beginning with a 1-slot header::
+
+	struct watch_notification {
+		__u16	type;
+		__u16	subtype;
+		__u32	info;
+	};
+
+"type" indicates the source of the notification record and "subtype" indicates
+the type of record from that source (see the Watch Sources section below).  The
+type may also be "WATCH_TYPE_META".  This is a special record type generated
+internally by the watch queue driver itself.  There are two subtypes, one of
+which indicates records that should be just skipped (padding or metadata):
+
+    * watch_meta_skip_notification
+    * watch_meta_removal_notification
+
+The former indicates a record that should just be skipped and the latter
+indicates that an object on which a watchpoint was installed was removed or
+destroyed.
+
+"info" indicates a bunch of things, including:
+
+  * The length of the record (mask with WATCH_INFO_LENGTH).  This indicates the
+    size of the record, which may be between 1 and 63 slots.  Note that this is
+    placed appropriately within the info value so that no shifting is required
+    to convert number of occupied slots to byte length.
+
+  * The watchpoint ID (mask with WATCH_INFO_ID).  This indicates that caller's
+    ID of the watchpoint, which may be between 0 and 255.  Multiple watchpoints
+    may share a queue, and this provides a means to distinguish them.
+
+  * A buffer overrun flag (WATCH_INFO_OVERRUN flag).  If this is set in a
+    notification record, some of the preceding records were discarded.
+
+  * An ENOMEM-loss flag (WATCH_INFO_ENOMEM flag).  This is set to indicate that
+    an event was lost to ENOMEM.
+
+  * A recursive-change flag (WATCH_INFO_RECURSIVE flag).  This is set to
+    indicate that the change that happened was recursive - for instance
+    changing the attributes on an entire mount subtree.
+
+  * An exact-match flag (WATCH_INFO_IN_SUBTREE flag).  This is set if the event
+    didn't happen exactly at the watchpoint, but rather somewhere in the
+    subtree thereunder.
+
+  * Some type-specific flags (WATCH_INFO_TYPE_FLAGS).  These are set by the
+    notification producer to indicate some meaning to the kernel.
+
+Everything in info apart from the length can be used for filtering.
+
+
+Ring Structure
+==============
+
+The ring is divided into 8-byte slots.  The caller uses an ioctl() to set the
+size of the ring after opening and this must be a power-of-2 multiple of the
+system page size (so that the mask can be used with AND).
+
+The head and tail indices are stored in the first two slots in the ring, which
+are marked out as a skippable entry::
+
+	struct watch_queue_buffer {
+		union {
+			struct {
+				struct watch_notification watch;
+				volatile __u32	head;
+				volatile __u32	tail;
+				__u32		mask;
+			} meta;
+			struct watch_notification slots[0];
+		};
+	};
+
+In "meta.watch", type will be set to WATCH_TYPE_META and subtype to
+watch_meta_skip_notification so that anyone processing the buffer will just
+skip this record.  Also, because this record is here, records cannot wrap round
+the end of the buffer, so a skippable padding element will be inserted at the
+end of the buffer if needed.  Thus the contents of a notification record in the
+buffer are always contiguous.
+
+"meta.mask" is an AND'able mask to turn the index counters into slots array
+indices.
+
+The buffer is empty if "meta.head" == "meta.tail".
+
+[!] NOTE that the ring indices "meta.head" and "meta.tail" are indices into
+"slots[]" not byte offsets into the buffer.
+
+[!] NOTE that userspace must never change the head pointer.  This belongs to
+the kernel and will be updated by that.  The kernel will never change the tail
+pointer.
+
+[!] NOTE that userspace must never AND-off the tail pointer before updating it,
+but should just keep adding to it and letting it wrap naturally.  The value
+*should* be masked off when used as an index into slots[].
+
+[!] NOTE that if the distance between head and tail becomes too great, the
+kernel will assume the buffer is full and write no more until the issue is
+resolved.
+
+
+Watch Sources
+=============
+
+Any particular buffer can be fed from multiple sources.  Sources include:
+
+  * WATCH_TYPE_MOUNT_NOTIFY
+
+    Notifications of this type indicate mount tree topology changes and mount
+    attribute changes.  A watchpoint can be set on a particular file or
+    directory and notifications from the path subtree rooted at that point will
+    be intercepted.
+
+  * WATCH_TYPE_SB_NOTIFY
+
+    Notifications of this type indicate superblock events, such as quota limits
+    being hit, I/O errors being produced or network server loss/reconnection.
+    Watchpoints of this type are set directly on superblocks.
+
+  * WATCH_TYPE_KEY_NOTIFY
+
+    Notifications of this type indicate changes to keys and keyrings, including
+    the changes of keyring contents or the attributes of keys.
+
+    See Documentation/security/keys/core.rst for more information.
+
+
+Configuring Watchpoints
+=======================
+
+When a watchpoint is set up, the caller assigns an ID and can set filtering
+parameters.  The following structure is filled out and passed to the
+watchpoint creation system call::
+
+	struct watch_notification_filter {
+		__u64	subtype_filter[4];
+		__u32	info_filter;
+		__u32	info_mask;
+		__u32	info_id;
+		__u32	__reserved;
+	};
+
+"subtype_filter" is a bitmask indicating the subtypes that are of interest.  In
+this version of the structure, only the first 256 subtypes are supported.  Bit
+0 of subtype_filter[0] corresponds to subtype 0, bit 1 to subtype 1, and so on.
+
+"info_filter" and "info_mask" act as a filter on the info field of the
+notification record.  The notification is only written into the buffer if::
+
+	(watch.info & info_mask) == info_filter
+
+This can be used, for example, to ignore events that are not exactly on the
+watched point in a mount tree by specifying WATCH_INFO_IN_SUBTREE must be 0.
+
+"info_id" is OR'd into watch.info.  This indicates the watchpoint ID in the top
+8 bits.  All bits outside of WATCH_INFO_ID must be 0.
+
+"__reserved" must be 0.
+
+If the pointer to this structure is NULL, this indicates to the system call
+that the watchpoint should be removed.
+
+
+Polling
+=======
+
+The file descriptor that holds the buffer may be used with poll() and similar.
+POLLIN and POLLRDNORM are set if the buffer indices differ.  POLLERR is set if
+the buffer indices are further apart than the size of the buffer.  Wake-up
+events are only generated if the buffer is transitioned from an empty state.
+
+
+Example
+=======
+
+A buffer is created with something like the following::
+
+	fd = open("/dev/watch_queue", O_RDWR);
+
+	#define BUF_SIZE 4
+	ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE);
+
+	page_size = sysconf(_SC_PAGESIZE);
+	buf = mmap(NULL, BUF_SIZE * page_size,
+		   PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+It can then be set to receive mount topology change notifications, keyring
+change notifications and superblock notifications::
+
+	memset(&filter, 0, sizeof(filter));
+	filter.subtype_filter[0] = ~0ULL;
+	filter.info_mask	 = WATCH_INFO_IN_SUBTREE;
+	filter.info_filter	 = 0;
+	filter.info_id		 = 0x01000000;
+
+	keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fd, &filter);
+
+	mount_notify(AT_FDCWD, "/", 0, fd, &filter);
+
+	sb_notify(AT_FDCWD, "/", 0, fd, &filter);
+
+The notifications can then be consumed by something like the following::
+
+	extern void saw_mount_change(struct watch_notification *n);
+	extern void saw_key_change(struct watch_notification *n);
+
+	static int consumer(int fd, struct watch_queue_buffer *buf)
+	{
+		struct watch_notification *n;
+		struct pollfd p[1];
+		unsigned int head, tail, mask = buf->meta.mask;
+
+		for (;;) {
+			p[0].fd = fd;
+			p[0].events = POLLIN | POLLERR;
+			p[0].revents = 0;
+
+			if (poll(p, 1, -1) == -1 || p[0].revents & POLLERR)
+				goto went_wrong;
+
+			while (head = _atomic_load_acquire(buf->meta.head),
+			       tail = buf->meta.tail,
+			       tail != head
+			       ) {
+				n = &buf->slots[tail & mask];
+				if ((n->info & WATCH_INFO_LENGTH) == 0)
+					goto went_wrong;
+
+				switch (n->type) {
+				case WATCH_TYPE_MOUNT_NOTIFY:
+					saw_mount_change(n);
+					break;
+				case WATCH_TYPE_KEY_NOTIFY:
+					saw_key_change(n);
+					break;
+				}
+
+				tail += (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+				_atomic_store_release(buf->meta.tail, tail);
+			}
+		}
+
+	went_wrong:
+		return 0;
+	}
+
+Note the memory barriers when loading the head pointer and storing the tail
+pointer!
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 3726eacdf65d..fa381a8620a7 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -4,6 +4,15 @@
 
 menu "Misc devices"
 
+config WATCH_QUEUE
+	bool "Mappable notification queue"
+	default n
+	help
+	  This is a general notification queue for the kernel to pass events to
+	  userspace through a mmap()'able ring buffer.  It can be used in
+	  conjunction with watches for mount topology change notifications,
+	  superblock change notifications and key/keyring change notifications.
+
 config SENSORS_LIS3LV02D
 	tristate
 	depends on INPUT
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index af22bbc3d00c..823f2f99b183 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -3,6 +3,7 @@
 # Makefile for misc devices that really don't fit anywhere else.
 #
 
+obj-$(CONFIG_WATCH_QUEUE)	+= watch_queue.o
 obj-$(CONFIG_IBM_ASM)		+= ibmasm/
 obj-$(CONFIG_IBMVMC)		+= ibmvmc.o
 obj-$(CONFIG_AD525X_DPOT)	+= ad525x_dpot.o
diff --git a/drivers/misc/watch_queue.c b/drivers/misc/watch_queue.c
new file mode 100644
index 000000000000..4e4d9262622d
--- /dev/null
+++ b/drivers/misc/watch_queue.c
@@ -0,0 +1,835 @@
+/* User-mappable watch queue
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#define pr_fmt(fmt) "[%05u] watchq: " fmt, current->pid
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/printk.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/file.h>
+#include <linux/watch_queue.h>
+
+#define DEBUG_WITH_WRITE /* Allow use of write() to record notifications */
+
+MODULE_DESCRIPTION("Watch queue");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL");
+
+struct watch_type_filter {
+	enum watch_notification_type type;
+	__u32		subtype_filter[1];	/* Bitmask of subtypes to filter on */
+	__u32		info_filter;		/* Filter on watch_notification::info */
+	__u32		info_mask;		/* Mask of relevant bits in info_filter */
+};
+
+struct watch_filter {
+	union {
+		struct rcu_head	rcu;
+		unsigned long	type_filter[2];	/* Bitmask of accepted types */
+	};
+	u32		nr_filters;		/* Number of filters */
+	struct watch_type_filter filters[];
+};
+
+struct watch_queue {
+	struct rcu_head		rcu;
+	struct address_space	mapping;
+	struct watch_filter	*filter;
+	wait_queue_head_t	waiters;
+	struct hlist_head	watches;	/* Contributory watches */
+	refcount_t		usage;
+	spinlock_t		lock;
+	bool			defunct;	/* T when queues closed */
+	u8			nr_pages;	/* Size of pages[] */
+	u8			flag_next;	/* Flag to apply to next item */
+#ifdef DEBUG_WITH_WRITE
+	u8			debug;
+#endif
+	u32			size;
+	struct watch_queue_buffer *buffer;	/* Pointer to first record */
+
+	/* The mappable pages.  The zeroth page holds the ring pointers. */
+	struct page		**pages;
+};
+
+/**
+ * post_one_notification - Post an event notification to one queue
+ * @wqueue: The watch queue to add the event to.
+ * @n: The notification record to post.
+ *
+ * Post a notification of an event into an mmap'd queue and let the user know.
+ * Returns true if successful and false on failure (eg. buffer overrun or
+ * userspace mucked up the ring indices).
+ *
+ *
+ * The size of the notification should be set in n->flags & WATCH_LENGTH and
+ * should be in units of sizeof(*n).
+ */
+static bool post_one_notification(struct watch_queue *wqueue,
+				  struct watch_notification *n)
+{
+	struct watch_queue_buffer *buf = wqueue->buffer;
+	unsigned int metalen = sizeof(buf->meta) / sizeof(buf->slots[0]);
+	unsigned int size = wqueue->size, mask = size - 1;
+	unsigned int len;
+	unsigned int ring_tail, tail, head, used, segment, h;
+
+	if (!buf)
+		return false;
+
+	len = (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+	if (len == 0)
+		return false;
+
+	spin_lock(&wqueue->lock); /* Protect head pointer */
+
+	if (wqueue->defunct)
+		goto out;
+
+	ring_tail = READ_ONCE(buf->meta.tail);
+	head = READ_ONCE(buf->meta.head);
+	used = head - ring_tail;
+
+	/* Check to see if userspace mucked up the pointers */
+	if (used >= size)
+		goto overrun;
+	tail = ring_tail & mask;
+	if (tail > 0 && tail < metalen)
+		goto overrun;
+
+	h = head & mask;
+	if (h >= tail) {
+		/* Head is at or after tail in the buffer.  There may then be
+		 * two segments: one to the end of buffer and one at the
+		 * beginning of the buffer between the metadata block and the
+		 * tail pointer.
+		 */
+		segment = size - h;
+		if (len > segment) {
+			/* Not enough space in the post-head segment; we need
+			 * to wrap.  When wrapping, we will have to skip the
+			 * metadata at the beginning of the buffer.
+			 */
+			if (len > tail - metalen)
+				goto overrun;
+
+			/* Fill the space at the end of the page */
+			buf->slots[h].type	= WATCH_TYPE_META;
+			buf->slots[h].subtype	= watch_meta_skip_notification;
+			buf->slots[h].info	= segment << WATCH_LENGTH_SHIFT;
+			head += segment;
+			h = 0;
+			if (h >= tail)
+				goto overrun;
+		}
+	}
+
+	if (h == 0) {
+		/* Reset and skip the header metadata */
+		buf->meta.watch.type = WATCH_TYPE_META;
+		buf->meta.watch.subtype = watch_meta_skip_notification;
+		buf->meta.watch.info = metalen << WATCH_LENGTH_SHIFT;
+		head += metalen;
+		h = metalen;
+		if (h >= tail)
+			goto overrun;
+	}
+
+	if (h < tail) {
+		/* Head is before tail in the buffer.  There may be one segment
+		 * between the two, but we may need to skip the metadata block.
+		 */
+		segment = tail - h;
+		if (len > segment)
+			goto overrun;
+	}
+
+	n->info |= wqueue->flag_next;
+	wqueue->flag_next = 0;
+	memcpy(buf->slots + h, n, len * sizeof(buf->slots[0]));
+	head += len;
+
+	smp_store_release(&buf->meta.head, head);
+	spin_unlock(&wqueue->lock);
+	if (used == 0)
+		wake_up(&wqueue->waiters);
+	return true;
+
+overrun:
+	wqueue->flag_next = WATCH_INFO_OVERRUN;
+out:
+	spin_unlock(&wqueue->lock);
+	return false;
+}
+
+/*
+ * Apply filter rules to a notification.
+ */
+static bool filter_watch_notification(const struct watch_filter *wf,
+				      const struct watch_notification *n)
+{
+	const struct watch_type_filter *wt;
+	int i;
+
+	if (!test_bit(n->type, wf->type_filter))
+		return false;
+
+	for (i = 0; i < wf->nr_filters; i++) {
+		wt = &wf->filters[i];
+		if (n->type == wt->type &&
+		    ((1U << n->subtype) & wt->subtype_filter[0]) &&
+		    (n->info & wt->info_mask) == wt->info_filter)
+			return true;
+	}
+
+	return false; /* If there is a filter, the default is to reject. */
+}
+
+/**
+ * __post_watch_notification - Post an event notification
+ * @wlist: The watch list to post the event to.
+ * @n: The notification record to post.
+ * @id: The ID to match on the watch.
+ *
+ * Post a notification of an event into a set of watch queues and let the users
+ * know.
+ *
+ * If @n is NULL then WATCH_INFO_LENGTH will be set on the next event posted.
+ *
+ * The size of the notification should be set in n->info & WATCH_INFO_LENGTH and
+ * should be in units of sizeof(*n).
+ */
+void __post_watch_notification(struct watch_list *wlist,
+			       struct watch_notification *n,
+			       u64 id)
+{
+	const struct watch_filter *wf;
+	struct watch_queue *wqueue;
+	struct watch *watch;
+
+	rcu_read_lock();
+
+	hlist_for_each_entry_rcu(watch, &wlist->watchers, list_node) {
+		if (watch->id != id)
+			continue;
+		n->info &= ~(WATCH_INFO_ID | WATCH_INFO_OVERRUN);
+		n->info |= watch->info_id;
+
+		wqueue = rcu_dereference(watch->queue);
+		wf = rcu_dereference(wqueue->filter);
+		if (wf && !filter_watch_notification(wf, n))
+			continue;
+
+		post_one_notification(wqueue, n);
+	}
+
+	rcu_read_unlock();
+}
+EXPORT_SYMBOL(__post_watch_notification);
+
+/*
+ * Allow the queue to be polled.
+ */
+static unsigned int watch_queue_poll(struct file *file, poll_table *wait)
+{
+	struct watch_queue *wqueue = file->private_data;
+	struct watch_queue_buffer *buf = wqueue->buffer;
+	unsigned int mask = 0, head, tail;
+
+	poll_wait(file, &wqueue->waiters, wait);
+
+	head = READ_ONCE(buf->meta.head);
+	tail = READ_ONCE(buf->meta.tail);
+	if (head != tail)
+		mask |= POLLIN | POLLRDNORM;
+	if (head - tail > wqueue->size)
+		mask |= POLLERR;
+	return mask;
+}
+
+static int watch_queue_set_page_dirty(struct page *page)
+{
+	SetPageDirty(page);
+	return 0;
+}
+
+static const struct address_space_operations watch_queue_aops = {
+	.set_page_dirty	= watch_queue_set_page_dirty,
+};
+
+static int watch_queue_fault(struct vm_fault *vmf)
+{
+	struct watch_queue *wqueue = vmf->vma->vm_file->private_data;
+	struct page *page;
+
+	page = wqueue->pages[vmf->pgoff];
+	get_page(page);
+	if (!lock_page_or_retry(page, vmf->vma->vm_mm, vmf->flags)) {
+		put_page(page);
+		return VM_FAULT_RETRY;
+	}
+	vmf->page = page;
+	return VM_FAULT_LOCKED;
+}
+
+static void watch_queue_map_pages(struct vm_fault *vmf,
+				  pgoff_t start_pgoff, pgoff_t end_pgoff)
+{
+	struct watch_queue *wqueue = vmf->vma->vm_file->private_data;
+	struct page *page;
+
+	rcu_read_lock();
+
+	do {
+		page = wqueue->pages[start_pgoff];
+		if (trylock_page(page)) {
+			int ret;
+			get_page(page);
+			ret = alloc_set_pte(vmf, NULL, page);
+			if (ret != 0)
+				put_page(page);
+
+			unlock_page(page);
+		}
+	} while (++start_pgoff < end_pgoff);
+
+	rcu_read_unlock();
+}
+
+static const struct vm_operations_struct watch_queue_vm_ops = {
+	.fault		= watch_queue_fault,
+	.map_pages	= watch_queue_map_pages,
+};
+
+/*
+ * Map the buffer.
+ */
+static int watch_queue_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct watch_queue *wqueue = file->private_data;
+
+	if (vma->vm_pgoff != 0 ||
+	    vma->vm_end - vma->vm_start > wqueue->nr_pages * PAGE_SIZE ||
+	    !(pgprot_val(vma->vm_page_prot) & pgprot_val(PAGE_SHARED)))
+		return -EINVAL;
+
+	vma->vm_ops = &watch_queue_vm_ops;
+
+	vma_interval_tree_insert(vma, &wqueue->mapping.i_mmap);
+	return 0;
+}
+
+/*
+ * Allocate the required number of pages.
+ */
+static long watch_queue_set_size(struct watch_queue *wqueue, unsigned long nr_pages)
+{
+	struct watch_queue_buffer *buf;
+	u32 len;
+	int i;
+
+	if (nr_pages == 0 ||
+	    nr_pages > 16 || /* TODO: choose a better hard limit */
+	    !is_power_of_2(nr_pages))
+		return -EINVAL;
+
+	wqueue->pages = kcalloc(nr_pages, sizeof(struct page *), GFP_KERNEL);
+	if (!wqueue->pages)
+		goto err;
+
+	for (i = 0; i < nr_pages; i++) {
+		wqueue->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
+		if (!wqueue->pages[i])
+			goto err_some_pages;
+		wqueue->pages[i]->mapping = &wqueue->mapping;
+		SetPageUptodate(wqueue->pages[i]);
+	}
+
+	buf = vmap(wqueue->pages, nr_pages, VM_MAP, PAGE_SHARED);
+	if (!buf)
+		goto err_some_pages;
+
+	wqueue->buffer = buf;
+	wqueue->nr_pages = nr_pages;
+	wqueue->size = ((nr_pages * PAGE_SIZE) / sizeof(struct watch_notification));
+
+	/* The first four slots in the buffer contain metadata about the ring,
+	 * including the head and tail indices and mask.
+	 */
+	len = sizeof(buf->meta) / sizeof(buf->slots[0]);
+	buf->meta.watch.info	= len << WATCH_LENGTH_SHIFT;
+	buf->meta.watch.type	= WATCH_TYPE_META;
+	buf->meta.watch.subtype	= watch_meta_skip_notification;
+	buf->meta.tail		= len;
+	buf->meta.mask		= wqueue->size - 1;
+	smp_store_release(&buf->meta.head, len);
+	return 0;
+
+err_some_pages:
+	for (i--; i >= 0; i--) {
+		ClearPageUptodate(wqueue->pages[i]);
+		wqueue->pages[i]->mapping = NULL;
+		put_page(wqueue->pages[i]);
+	}
+
+	kfree(wqueue->pages);
+	wqueue->pages = NULL;
+err:
+	return -ENOMEM;
+}
+
+/*
+ * Set the filter on a watch queue.
+ */
+static long watch_queue_set_filter(struct watch_queue *wqueue,
+				   struct watch_notification_filter __user *_filter)
+{
+	struct watch_notification_type_filter *tf;
+	struct watch_notification_filter filter;
+	struct watch_type_filter *q;
+	struct watch_filter *old, *wfilter;
+	int ret, nr_filter = 0, i;
+
+	if (!_filter) {
+		/* Remove the old filter */
+		wfilter = NULL;
+		goto set;
+	}
+
+	/* Grab the user's filter specification */
+	if (copy_from_user(&filter, _filter, sizeof(filter)) != 0)
+		return -EFAULT;
+	if (filter.nr_filters == 0 ||
+	    filter.nr_filters > 16 ||
+	    filter.__reserved != 0)
+		return -EINVAL;
+
+	tf = memdup_user(_filter->filters, filter.nr_filters * sizeof(*tf));
+	if (IS_ERR(tf))
+		return PTR_ERR(tf);
+
+	ret = -EINVAL;
+	for (i = 0; i < filter.nr_filters; i++) {
+		if ((tf[i].info_filter & ~tf[i].info_mask) ||
+		    tf[i].info_mask & WATCH_INFO_LENGTH)
+			goto err_filter;
+		/* Ignore any unknown types */
+		if (tf[i].type >= sizeof(wfilter->type_filter) * 8)
+			continue;
+		nr_filter++;
+	}
+
+	/* Now we need to build the internal filter from only the
+	 * relevant user-specified filters.
+	 */
+	ret = -ENOMEM;
+	wfilter = kzalloc(sizeof(*wfilter) + nr_filter * sizeof(*q), GFP_KERNEL);
+	if (!wfilter)
+		goto err_filter;
+	wfilter->nr_filters = nr_filter;
+
+	q = wfilter->filters;
+	for (i = 0; i < filter.nr_filters; i++) {
+		if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG)
+			continue;
+
+		q->type			= tf[i].type;
+		q->info_filter		= tf[i].info_filter;
+		q->info_mask		= tf[i].info_mask;
+		q->subtype_filter[0]	= tf[i].subtype_filter[0];
+		__set_bit(q->type, wfilter->type_filter);
+		q++;
+	}
+
+	kfree(tf);
+set:
+	rcu_swap_protected(wqueue->filter, wfilter, true /* inode lock */);
+	if (old)
+		kfree_rcu(wfilter, rcu);
+	return 0;
+
+err_filter:
+	kfree(tf);
+	return ret;
+}
+
+/*
+ * Set parameters.
+ */
+static long watch_queue_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct watch_queue *wqueue = file->private_data;
+	long ret;
+
+	switch (cmd) {
+	case IOC_WATCH_QUEUE_SET_SIZE:
+		if (wqueue->buffer)
+			return -EBUSY;
+		inode_lock(file_inode(file));
+		ret = watch_queue_set_size(wqueue, arg);
+		inode_unlock(file_inode(file));
+		return ret;
+
+	case IOC_WATCH_QUEUE_SET_FILTER:
+		inode_lock(file_inode(file));
+		ret = watch_queue_set_filter(
+			wqueue, (struct watch_notification_filter __user *)arg);
+		inode_unlock(file_inode(file));
+		return ret;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+/*
+ * Open the file.
+ */
+static int watch_queue_open(struct inode *inode, struct file *file)
+{
+	struct watch_queue *wqueue;
+
+	wqueue = kzalloc(sizeof(*wqueue), GFP_KERNEL);
+	if (!wqueue)
+		return -ENOMEM;
+
+	wqueue->mapping.a_ops = &watch_queue_aops;
+	wqueue->mapping.i_mmap = RB_ROOT_CACHED;
+	init_rwsem(&wqueue->mapping.i_mmap_rwsem);
+	spin_lock_init(&wqueue->mapping.private_lock);
+
+	refcount_set(&wqueue->usage, 1);
+	spin_lock_init(&wqueue->lock);
+	init_waitqueue_head(&wqueue->waiters);
+
+	file->private_data = wqueue;
+	return 0;
+}
+
+/**
+ * put_watch_queue - Dispose of a ref on a watchqueue.
+ * @wq: The watch queue to unref.
+ */
+void put_watch_queue(struct watch_queue *wqueue)
+{
+	if (refcount_dec_and_test(&wqueue->usage))
+		kfree_rcu(wqueue, rcu);
+}
+EXPORT_SYMBOL(put_watch_queue);
+
+/*
+ * Discard a watch.
+ */
+static void put_watch(struct watch *watch)
+{
+	if (refcount_dec_and_test(&watch->usage)) {
+		put_watch_queue(watch->queue);
+		kfree_rcu(watch, rcu);
+	}
+}
+
+/**
+ * add_watch_to_object - Add a watch on an object
+ * @watch: The watch to add
+ *
+ * @watch->queue and @watch->watch_list must have been set to point to the
+ * queue to post notifications to and the watch list of the object to be
+ * watched.
+ *
+ * The caller must pin the queue and the list both and must hold the list
+ * locked against racing watch additions/removals.
+ */
+int add_watch_to_object(struct watch *watch)
+{
+	struct watch_queue *wqueue = watch->queue;
+	struct watch_list *wlist = watch->watch_list;
+	struct watch *w;
+
+	hlist_for_each_entry(w, &wlist->watchers, list_node) {
+		if (watch->id == w->id)
+			return -EBUSY;
+	}
+
+	spin_lock(&wqueue->lock);
+	refcount_inc(&wqueue->usage);
+	hlist_add_head(&watch->queue_node, &wqueue->watches);
+	spin_unlock(&wqueue->lock);
+
+	hlist_add_head(&watch->list_node, &wlist->watchers);
+	return 0;
+}
+EXPORT_SYMBOL(add_watch_to_object);
+
+/**
+ * remove_watch_from_object - Remove a watch or all watches from an object.
+ * @wlist: The watch list to remove from
+ * @wq: The watch queue of interest (ignored if @all is true)
+ * @id: The ID of the watch to remove (ignored if @all is true)
+ * @all: True to remove all objects
+ *
+ * Remove a specific watch or all watches from an object.  A notification is
+ * sent to the watcher to tell them that this happened.
+ */
+int remove_watch_from_object(struct watch_list *wlist, struct watch_queue *wq,
+			     u64 id, bool all)
+{
+	struct watch_notification n;
+	struct watch_queue *wqueue;
+	struct watch *watch;
+	int ret = -EBADSLT;
+
+	rcu_read_lock();
+
+again:
+	spin_lock(&wlist->lock);
+	hlist_for_each_entry(watch, &wlist->watchers, list_node) {
+		if (all || (watch->id == id && watch->queue == wq))
+			goto found;
+	}
+	spin_unlock(&wlist->lock);
+	goto out;
+
+found:
+	ret = 0;
+	hlist_del_init_rcu(&watch->list_node);
+	rcu_assign_pointer(watch->watch_list, NULL);
+	spin_unlock(&wlist->lock);
+
+	n.type = WATCH_TYPE_META;
+	n.subtype = watch_meta_removal_notification;
+	n.info = watch->info_id | sizeof(n);
+
+	post_one_notification(watch->queue, &n);
+
+	/* We don't need the watch list lock for the next bit as RCU is
+	 * protecting everything from being deallocated.
+	 */
+	wqueue = rcu_dereference(watch->queue);
+	if (wqueue) {
+		spin_lock(&wqueue->lock);
+
+		if (!hlist_unhashed(&watch->queue_node)) {
+			hlist_del_init_rcu(&watch->queue_node);
+			put_watch(watch);
+		}
+
+		spin_unlock(&wqueue->lock);
+	}
+
+	if (wlist->release_watch) {
+		rcu_read_unlock();
+		wlist->release_watch(wlist, watch);
+		rcu_read_lock();
+	}
+	put_watch(watch);
+
+	if (all && !hlist_empty(&wlist->watchers))
+		goto again;
+out:
+	rcu_read_unlock();
+	return ret;
+}
+EXPORT_SYMBOL(remove_watch_from_object);
+
+/*
+ * Remove all the watches that are contributory to a queue.  This will
+ * potentially race with removal of the watches by the destruction of the
+ * objects being watched or the distribution of notifications.
+ */
+static void watch_queue_clear(struct watch_queue *wqueue)
+{
+	struct watch_list *wlist;
+	struct watch *watch;
+	bool release;
+
+	rcu_read_lock();
+	spin_lock(&wqueue->lock);
+
+	/* Prevent new additions and prevent notifications from happening */
+	wqueue->defunct = true;
+
+	while (!hlist_empty(&wqueue->watches)) {
+		watch = hlist_entry(wqueue->watches.first, struct watch, queue_node);
+		hlist_del_init_rcu(&watch->queue_node);
+		spin_unlock(&wqueue->lock);
+
+		/* We can't do the next bit under the queue lock as we need to
+		 * get the list lock - which would cause a deadlock if someone
+		 * was removing from the opposite direction at the same time or
+		 * posting a notification.
+		 */
+		wlist = rcu_dereference(watch->watch_list);
+		if (wlist) {
+			spin_lock(&wlist->lock);
+
+			release = !hlist_unhashed(&watch->list_node);
+			if (release) {
+				hlist_del_init_rcu(&watch->list_node);
+				rcu_assign_pointer(watch->watch_list, NULL);
+			}
+
+			spin_unlock(&wlist->lock);
+
+			if (release) {
+				if (wlist->release_watch) {
+					rcu_read_unlock();
+					/* This might need to call dput(), so
+					 * we have to drop all the locks.
+					 */
+					wlist->release_watch(wlist, watch);
+					rcu_read_lock();
+				}
+				put_watch(watch);
+			}
+		}
+
+		put_watch(watch);
+		spin_lock(&wqueue->lock);
+	}
+
+	spin_unlock(&wqueue->lock);
+	rcu_read_unlock();
+}
+
+/*
+ * Release the file.
+ */
+static int watch_queue_release(struct inode *inode, struct file *file)
+{
+	struct watch_queue *wqueue = file->private_data;
+	int i, pgref;
+
+	watch_queue_clear(wqueue);
+
+	if (wqueue->pages && wqueue->pages[0])
+		WARN_ON(page_ref_count(wqueue->pages[0]) != 1);
+
+	if (wqueue->buffer)
+		vfree(wqueue->buffer);
+	for (i = 0; i < wqueue->nr_pages; i++) {
+		ClearPageUptodate(wqueue->pages[i]);
+		wqueue->pages[i]->mapping = NULL;
+		pgref = page_ref_count(wqueue->pages[i]);
+		WARN(pgref != 1,
+		     "FREE PAGE[%d] refcount %d\n", i, page_ref_count(wqueue->pages[i]));
+		__free_page(wqueue->pages[i]);
+	}
+	if (wqueue->filter)
+		kfree_rcu(wqueue->filter, rcu);
+	kfree(wqueue->pages);
+	put_watch_queue(wqueue);
+	return 0;
+}
+
+#ifdef DEBUG_WITH_WRITE
+static ssize_t watch_queue_write(struct file *file,
+				 const char __user *_buf, size_t len, loff_t *pos)
+{
+	struct watch_notification *n;
+	struct watch_queue *wqueue = file->private_data;
+	ssize_t ret;
+
+	if (!wqueue->buffer)
+		return -ENOBUFS;
+
+	if (len & ~WATCH_INFO_LENGTH || len == 0 || !_buf)
+		return -EINVAL;
+
+	n = memdup_user(_buf, len);
+	if (IS_ERR(n))
+		return PTR_ERR(n);
+
+	ret = -EINVAL;
+	if ((n->info & WATCH_INFO_LENGTH) != len)
+		goto error;
+	n->info &= (WATCH_INFO_LENGTH | WATCH_INFO_TYPE_FLAGS | WATCH_INFO_ID);
+
+	if (post_one_notification(wqueue, n))
+		wqueue->debug = 0;
+	else
+		wqueue->debug++;
+	ret = len;
+	if (wqueue->debug > 20)
+		ret = -EIO;
+
+error:
+	kfree(n);
+	return ret;
+}
+#endif
+
+static const struct file_operations watch_queue_fops = {
+	.owner		= THIS_MODULE,
+	.open		= watch_queue_open,
+	.release	= watch_queue_release,
+	.unlocked_ioctl	= watch_queue_ioctl,
+	.poll		= watch_queue_poll,
+	.mmap		= watch_queue_mmap,
+#ifdef DEBUG_WITH_WRITE
+	.write		= watch_queue_write,
+#endif
+	.llseek		= no_llseek,
+};
+
+/**
+ * get_watch_queue - Get a watch queue from its file descriptor.
+ * @fd: The fd to query.
+ */
+struct watch_queue *get_watch_queue(int fd)
+{
+	struct watch_queue *wqueue = ERR_PTR(-EBADF);
+	struct fd f;
+
+	f = fdget(fd);
+	if (f.file) {
+		wqueue = ERR_PTR(-EINVAL);
+		if (f.file->f_op == &watch_queue_fops) {
+			wqueue = f.file->private_data;
+			refcount_inc(&wqueue->usage);
+		}
+		fdput(f);
+	}
+
+	return wqueue;
+}
+EXPORT_SYMBOL(get_watch_queue);
+
+static struct miscdevice watch_queue_dev = {
+	.minor	= MISC_DYNAMIC_MINOR,
+	.name	= "watch_queue",
+	.fops	= &watch_queue_fops,
+	.mode	= 0666,
+};
+
+static int __init watch_queue_init(void)
+{
+	int ret;
+
+	ret = misc_register(&watch_queue_dev);
+	if (ret < 0)
+		pr_err("Failed to register %d\n", ret);
+	return ret;
+}
+fs_initcall(watch_queue_init);
+
+static void __exit watch_queue_exit(void)
+{
+	misc_deregister(&watch_queue_dev);
+}
+module_exit(watch_queue_exit);
diff --git a/include/linux/watch_queue.h b/include/linux/watch_queue.h
new file mode 100644
index 000000000000..465b14f3ad42
--- /dev/null
+++ b/include/linux/watch_queue.h
@@ -0,0 +1,87 @@
+/* User-mappable watch queue
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#ifndef _LINUX_WATCH_QUEUE_H
+#define _LINUX_WATCH_QUEUE_H
+
+#include <uapi/linux/watch_queue.h>
+
+#ifdef CONFIG_WATCH_QUEUE
+
+struct watch_queue;
+
+/*
+ * Representation of a watch on an object.
+ */
+struct watch {
+	union {
+		struct rcu_head	rcu;
+		u32		info_id;	/* ID to be OR'd in to info field */
+	};
+	struct watch_queue __rcu *queue;	/* Queue to post events to */
+	struct hlist_node	queue_node;	/* Link in queue->watches */
+	struct watch_list __rcu	*watch_list;
+	struct hlist_node	list_node;	/* Link in watch_list->watchers */
+	void			*private;	/* Private data for the watched object */
+	u64			id;		/* Internal identifier */
+	refcount_t		usage;
+};
+
+/*
+ * List of watches on an object.
+ */
+struct watch_list {
+	struct rcu_head		rcu;
+	struct hlist_head	watchers;
+	void (*release_watch)(struct watch_list *, struct watch *);
+	spinlock_t		lock;
+};
+
+extern void __post_watch_notification(struct watch_list *,
+				      struct watch_notification *, u64);
+extern struct watch_queue *get_watch_queue(int);
+extern void put_watch_queue(struct watch_queue *);
+extern void put_watch_list(struct watch_list *);
+extern int add_watch_to_object(struct watch *);
+extern int remove_watch_from_object(struct watch_list *, struct watch_queue *, u64, bool);
+
+static inline void init_watch_list(struct watch_list *wlist)
+{
+	INIT_HLIST_HEAD(&wlist->watchers);
+	spin_lock_init(&wlist->lock);
+}
+
+static inline void init_watch(struct watch *watch)
+{
+	refcount_set(&watch->usage, 1);
+	INIT_HLIST_NODE(&watch->list_node);
+	INIT_HLIST_NODE(&watch->queue_node);
+}
+
+static inline void post_watch_notification(struct watch_list *wlist,
+					   struct watch_notification *n,
+					   u64 id)
+{
+	if (unlikely(wlist))
+		__post_watch_notification(wlist, n, id);
+}
+
+static inline void remove_watch_list(struct watch_list *wlist)
+{
+	if (wlist) {
+		remove_watch_from_object(wlist, NULL, 0, true);
+		kfree_rcu(wlist, rcu);
+	}
+}
+
+#endif
+
+#endif /* _LINUX_WATCH_QUEUE_H */
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
new file mode 100644
index 000000000000..98ac4d46ab0d
--- /dev/null
+++ b/include/uapi/linux/watch_queue.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_WATCH_QUEUE_H
+#define _UAPI_LINUX_WATCH_QUEUE_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define IOC_WATCH_QUEUE_SET_SIZE	_IO('s', 0x01)	/* Set the size in pages */
+#define IOC_WATCH_QUEUE_SET_FILTER	_IO('s', 0x02)	/* Set the filter */
+
+enum watch_notification_type {
+	WATCH_TYPE_META		= 0,	/* Special record */
+	WATCH_TYPE_MOUNT_NOTIFY	= 1,	/* Mount notification record */
+	WATCH_TYPE_SB_NOTIFY	= 2,	/* Superblock notification */
+	WATCH_TYPE_KEY_NOTIFY	= 3,	/* Key/keyring change notification */
+#define WATCH_TYPE___NR 4
+};
+
+enum watch_meta_notification_subtype {
+	watch_meta_skip_notification	= 0,	/* Just skip this record */
+	watch_meta_removal_notification	= 1,	/* Watched object was removed */
+};
+
+/*
+ * Notification record
+ */
+struct watch_notification {
+	__u32			type:24;	/* enum watch_notification_type */
+	__u32			subtype:8;	/* Type-specific subtype (filterable) */
+	__u32			info;
+#define WATCH_INFO_OVERRUN	0x00000001	/* Event(s) lost due to overrun */
+#define WATCH_INFO_ENOMEM	0x00000002	/* Event(s) lost due to ENOMEM */
+#define WATCH_INFO_RECURSIVE	0x00000004	/* Change was recursive */
+#define WATCH_INFO_LENGTH	0x000001f8	/* Length of record / sizeof(watch_notification) */
+#define WATCH_INFO_IN_SUBTREE	0x00000200	/* Change was not at watched root */
+#define WATCH_INFO_TYPE_FLAGS	0x00ff0000	/* Type-specific flags */
+#define WATCH_INFO_FLAG_0	0x00010000
+#define WATCH_INFO_FLAG_1	0x00020000
+#define WATCH_INFO_FLAG_2	0x00040000
+#define WATCH_INFO_FLAG_3	0x00080000
+#define WATCH_INFO_FLAG_4	0x00100000
+#define WATCH_INFO_FLAG_5	0x00200000
+#define WATCH_INFO_FLAG_6	0x00400000
+#define WATCH_INFO_FLAG_7	0x00800000
+#define WATCH_INFO_ID		0xff000000	/* ID of watchpoint */
+};
+
+#define WATCH_LENGTH_SHIFT	3
+
+struct watch_queue_buffer {
+	union {
+		/* The first few entries are special, containing the
+		 * ring management variables.
+		 */
+		struct {
+			struct watch_notification watch; /* WATCH_TYPE_SKIP */
+			volatile __u32	head;		/* Ring head index */
+			volatile __u32	tail;		/* Ring tail index */
+			__u32		mask;		/* Ring index mask */
+		} meta;
+		struct watch_notification slots[0];
+	};
+};
+
+/*
+ * Notification filtering rules (IOC_WATCH_QUEUE_SET_FILTER).
+ */
+struct watch_notification_type_filter {
+	__u32	type;			/* Type to apply filter to */
+	__u32	info_filter;		/* Filter on watch_notification::info */
+	__u32	info_mask;		/* Mask of relevant bits in info_filter */
+	__u32	subtype_filter[8];	/* Bitmask of subtypes to filter on */
+};
+
+struct watch_notification_filter {
+	__u32	nr_filters;		/* Number of filters */
+	__u32	__reserved;		/* Must be 0 */
+	struct watch_notification_type_filter filters[];
+};
+
+#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/mm/interval_tree.c b/mm/interval_tree.c
index 27ddfd29112a..9a53ddf4bd62 100644
--- a/mm/interval_tree.c
+++ b/mm/interval_tree.c
@@ -25,6 +25,8 @@ INTERVAL_TREE_DEFINE(struct vm_area_struct, shared.rb,
 		     unsigned long, shared.rb_subtree_last,
 		     vma_start_pgoff, vma_last_pgoff,, vma_interval_tree)
 
+EXPORT_SYMBOL_GPL(vma_interval_tree_insert);
+
 /* Insert node immediately after prev in the interval tree */
 void vma_interval_tree_insert_after(struct vm_area_struct *node,
 				    struct vm_area_struct *prev,
diff --git a/mm/memory.c b/mm/memory.c
index 7206a634270b..79a2b13cc7cc 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -3462,6 +3462,7 @@ int alloc_set_pte(struct vm_fault *vmf, struct mem_cgroup *memcg,
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(alloc_set_pte);
 
 
 /**


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 2/5] KEYS: Add a notification facility
  2018-07-23 15:25 [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications David Howells
  2018-07-23 15:25 ` [PATCH 1/5] General notification queue with user mmap()'able ring buffer David Howells
@ 2018-07-23 15:25 ` David Howells
  2018-07-23 15:26 ` [PATCH 3/5] vfs: Add a mount-notification facility David Howells
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: David Howells @ 2018-07-23 15:25 UTC (permalink / raw)
  To: viro
  Cc: linux-fsdevel, dhowells, linux-kernel, raven, keyrings,
	linux-security-module

Add a key/keyring change notification facility whereby notifications about
changes in key and keyring content and attributes can be received.

Firstly, an event queue needs to be created:

	fd = open("/dev/event_queue", O_RDWR);

then a notification can be set up to report notifications via that queue:

	struct watch_notification_filter filter;
	memset(&filter, 0, sizeof(filter));
	filter.subtype_filter[0] = ~0ULL;
	filter.info_id		 = 0x01000000;
	keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, &filter);

Note that the queue can be shared between multiple notifications of various
types.

Signed-off-by: David Howells <dhowells@redhat.com>
---

 Documentation/security/keys/core.rst |   59 ++++++++++++++++++++++
 include/linux/key.h                  |    4 ++
 include/uapi/linux/keyctl.h          |    1 
 include/uapi/linux/watch_queue.h     |   25 +++++++++
 security/keys/Kconfig                |   10 ++++
 security/keys/compat.c               |    3 +
 security/keys/gc.c                   |    5 ++
 security/keys/internal.h             |   29 +++++++++++
 security/keys/key.c                  |   37 +++++++++-----
 security/keys/keyctl.c               |   90 +++++++++++++++++++++++++++++++++-
 security/keys/keyring.c              |   17 +++++-
 security/keys/request_key.c          |    4 +-
 12 files changed, 260 insertions(+), 24 deletions(-)

diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 9ce7256c6edb..d243a2ef7802 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -808,6 +808,7 @@ The keyctl syscall functions are:
      A process must have search permission on the key for this function to be
      successful.
 
+
   *  Compute a Diffie-Hellman shared secret or public key::
 
 	long keyctl(KEYCTL_DH_COMPUTE, struct keyctl_dh_params *params,
@@ -859,6 +860,7 @@ The keyctl syscall functions are:
      and either the buffer length or the OtherInfo length exceeds the
      allowed length.
 
+
   *  Restrict keyring linkage::
 
 	long keyctl(KEYCTL_RESTRICT_KEYRING, key_serial_t keyring,
@@ -890,6 +892,63 @@ The keyctl syscall functions are:
      applicable to the asymmetric key type.
 
 
+  *  Watch a key or keyring for changes::
+
+	long keyctl(KEYCTL_WATCH_KEY, key_serial_t key, int queue_fd,
+		    const struct watch_notification_filter *filter);
+
+     This will set or remove a watch for changes on the specified key or
+     keyring.
+
+     "key" is the ID of the key to be watched.
+
+     "queue_fd" is a file descriptor referring to an open "/dev/watch_queue"
+     which manages the buffer into which notifications will be delivered.
+
+     "filter" is either NULL to remove a watch or a filter specification to
+     indicate what events are required from the key.
+
+     See Documentation/watch_queue.rst for more information.
+
+     Note that only one watch may be emplaced for any particular { key,
+     queue_fd } combination.
+
+     Notification records look like::
+
+	struct key_notification {
+		struct watch_notification watch;
+		__u32	key_id;
+		__u32	aux;
+	};
+
+     In this, watch::type will be "WATCH_TYPE_KEY_NOTIFY" and subtype will be
+     one of::
+
+     	notify_key_instantiated
+	notify_key_updated
+	notify_key_linked
+	notify_key_unlinked
+	notify_key_cleared
+	notify_key_revoked
+	notify_key_invalidated
+	notify_key_setattr
+
+     Where these indicate a key being instantiated/rejected, updated, a link
+     being made in a keyring, a link being removed from a keyring, a keyring
+     being cleared, a key being revoked, a key being invalidated or a key
+     having one of its attributes changed (user, group, perm, timeout,
+     restriction).
+
+     If a watched key is deleted, a basic watch_notification will be issued
+     with "type" set to WATCH_TYPE_META and "subtype" set to
+     watch_meta_removal_notification.  The watchpoint ID will be set in the
+     "info" field.
+
+     This needs to be configured by enabling:
+
+	"Provide key/keyring change notifications" (KEY_NOTIFICATIONS)
+
+
 Kernel Services
 ===============
 
diff --git a/include/linux/key.h b/include/linux/key.h
index e58ee10f6e58..40e0d76731ae 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -159,6 +159,9 @@ struct key {
 		struct list_head graveyard_link;
 		struct rb_node	serial_node;
 	};
+#ifdef CONFIG_KEY_NOTIFICATIONS
+	struct watch_list	*watchers;	/* Entities watching this key for changes */
+#endif
 	struct rw_semaphore	sem;		/* change vs change sem */
 	struct key_user		*user;		/* owner of this key */
 	void			*security;	/* security data for this key */
@@ -193,6 +196,7 @@ struct key {
 #define KEY_FLAG_ROOT_CAN_INVAL	7	/* set if key can be invalidated by root without permission */
 #define KEY_FLAG_KEEP		8	/* set if key should not be removed */
 #define KEY_FLAG_UID_KEYRING	9	/* set if key is a user or user session keyring */
+#define KEY_FLAG_SET_WATCH_PROXY 10	/* Set if watch_proxy should be set on added keys */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index 7b8c9e19bad1..173fdcdc1470 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -62,6 +62,7 @@
 #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
 #define KEYCTL_DH_COMPUTE		23	/* Compute Diffie-Hellman values */
 #define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
+#define KEYCTL_WATCH_KEY		30	/* Watch a key or ring of keys for changes */
 
 /* keyctl structures */
 struct keyctl_dh_params {
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 98ac4d46ab0d..3e0ab5fe388d 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -78,4 +78,29 @@ struct watch_notification_filter {
 	struct watch_notification_type_filter filters[];
 };
 
+/*
+ * Type of key/keyring change notification.
+ */
+enum key_notification_subtype {
+	notify_key_instantiated	= 0, /* Key was instantiated (aux is error code) */
+	notify_key_updated	= 1, /* Key was updated */
+	notify_key_linked	= 2, /* Key (aux) was added to watched keyring */
+	notify_key_unlinked	= 3, /* Key (aux) was removed from watched keyring */
+	notify_key_cleared	= 4, /* Keyring was cleared */
+	notify_key_revoked	= 5, /* Key was revoked */
+	notify_key_invalidated	= 6, /* Key was invalidated */
+	notify_key_setattr	= 7, /* Key's attributes got changed */
+};
+
+/*
+ * Key/keyring notification record.
+ * - watch.type = WATCH_TYPE_KEY_NOTIFY
+ * - watch.subtype = enum key_notification_type
+ */
+struct key_notification {
+	struct watch_notification watch;
+	__u32	key_id;		/* The key/keyring affected */
+	__u32	aux;		/* Per-type auxiliary data */
+};
+
 #endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index 6462e6654ccf..fbe064fa0a17 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -101,3 +101,13 @@ config KEY_DH_OPERATIONS
 	 in the kernel.
 
 	 If you are unsure as to whether this is required, answer N.
+
+config KEY_NOTIFICATIONS
+	bool "Provide key/keyring change notifications"
+	depends on KEYS
+	select WATCH_QUEUE
+	help
+	  This option provides support for getting change notifications on keys
+	  and keyrings on which the caller has View permission.  This makes use
+	  of the /dev/watch_queue misc device to handle the notification
+	  buffer and provides KEYCTL_WATCH_KEY to enable/disable watches.
diff --git a/security/keys/compat.c b/security/keys/compat.c
index e87c89c0177c..5aac4ee9005c 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -141,6 +141,9 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
 		return keyctl_restrict_keyring(arg2, compat_ptr(arg3),
 					       compat_ptr(arg4));
 
+	case KEYCTL_WATCH_KEY:
+		return keyctl_watch_key(arg2, arg3, arg4);
+
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 7207e6094dc1..58bcdad55787 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -136,6 +136,11 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 		kdebug("- %u", key->serial);
 		key_check(key);
 
+#ifdef CONFIG_KEY_NOTIFICATIONS
+		remove_watch_list(key->watchers);
+		key->watchers = NULL;
+#endif
+
 		/* Throw away the key data if the key is instantiated */
 		if (state == KEY_IS_POSITIVE && key->type->destroy)
 			key->type->destroy(key);
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 9f8208dc0e55..66b424e8c104 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -19,6 +19,7 @@
 #include <linux/task_work.h>
 #include <linux/keyctl.h>
 #include <linux/refcount.h>
+#include <linux/watch_queue.h>
 #include <linux/compat.h>
 
 struct iovec;
@@ -97,7 +98,8 @@ extern int __key_link_begin(struct key *keyring,
 			    const struct keyring_index_key *index_key,
 			    struct assoc_array_edit **_edit);
 extern int __key_link_check_live_key(struct key *keyring, struct key *key);
-extern void __key_link(struct key *key, struct assoc_array_edit **_edit);
+extern void __key_link(struct key *keyring, struct key *key,
+		       struct assoc_array_edit **_edit);
 extern void __key_link_end(struct key *keyring,
 			   const struct keyring_index_key *index_key,
 			   struct assoc_array_edit *edit);
@@ -180,6 +182,22 @@ extern int key_task_permission(const key_ref_t key_ref,
 			       const struct cred *cred,
 			       key_perm_t perm);
 
+static inline void notify_key(struct key *key,
+			      enum key_notification_subtype subtype, u32 aux)
+{
+#ifdef CONFIG_KEY_NOTIFICATIONS
+	struct key_notification n = {
+		.watch.type	= WATCH_TYPE_KEY_NOTIFY,
+		.watch.subtype	= subtype,
+		.watch.info	= sizeof(n),
+		.key_id		= key_serial(key),
+		.aux		= aux,
+	};
+
+	post_watch_notification(key->watchers, &n.watch, n.key_id);
+#endif
+}
+
 /*
  * Check to see whether permission is granted to use a key in the desired way.
  */
@@ -298,6 +316,15 @@ static inline long compat_keyctl_dh_compute(
 #endif
 #endif
 
+#ifdef CONFIG_KEY_NOTIFICATIONS
+extern long keyctl_watch_key(key_serial_t, int, int);
+#else
+static inline long keyctl_watch_key(key_serial_t key_id, int watch_fd, int watch_id)
+{
+	return -EOPNOTSUPP;
+}
+#endif
+
 /*
  * Debugging key validation
  */
diff --git a/security/keys/key.c b/security/keys/key.c
index d97c9394b5dd..000d288a4c4a 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -411,6 +411,7 @@ static void mark_key_instantiated(struct key *key, int reject_error)
 	 */
 	smp_store_release(&key->state,
 			  (reject_error < 0) ? reject_error : KEY_IS_POSITIVE);
+	notify_key(key, notify_key_instantiated, reject_error);
 }
 
 /*
@@ -453,7 +454,7 @@ static int __key_instantiate_and_link(struct key *key,
 				if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
 					set_bit(KEY_FLAG_KEEP, &key->flags);
 
-				__key_link(key, _edit);
+				__key_link(keyring, key, _edit);
 			}
 
 			/* disable the authorisation key */
@@ -602,7 +603,7 @@ int key_reject_and_link(struct key *key,
 
 		/* and link it into the destination keyring */
 		if (keyring && link_ret == 0)
-			__key_link(key, &edit);
+			__key_link(keyring, key, &edit);
 
 		/* disable the authorisation key */
 		if (authkey)
@@ -755,9 +756,11 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
 	down_write(&key->sem);
 
 	ret = key->type->update(key, prep);
-	if (ret == 0)
+	if (ret == 0) {
 		/* Updating a negative key positively instantiates it */
 		mark_key_instantiated(key, 0);
+		notify_key(key, notify_key_updated, 0);
+	}
 
 	up_write(&key->sem);
 
@@ -998,9 +1001,11 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
 	down_write(&key->sem);
 
 	ret = key->type->update(key, &prep);
-	if (ret == 0)
+	if (ret == 0) {
 		/* Updating a negative key positively instantiates it */
 		mark_key_instantiated(key, 0);
+		notify_key(key, notify_key_updated, 0);
+	}
 
 	up_write(&key->sem);
 
@@ -1032,15 +1037,17 @@ void key_revoke(struct key *key)
 	 *   instantiated
 	 */
 	down_write_nested(&key->sem, 1);
-	if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) &&
-	    key->type->revoke)
-		key->type->revoke(key);
-
-	/* set the death time to no more than the expiry time */
-	time = ktime_get_real_seconds();
-	if (key->revoked_at == 0 || key->revoked_at > time) {
-		key->revoked_at = time;
-		key_schedule_gc(key->revoked_at + key_gc_delay);
+	if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags)) {
+		notify_key(key, notify_key_revoked, 0);
+		if (key->type->revoke)
+			key->type->revoke(key);
+
+		/* set the death time to no more than the expiry time */
+		time = ktime_get_real_seconds();
+		if (key->revoked_at == 0 || key->revoked_at > time) {
+			key->revoked_at = time;
+			key_schedule_gc(key->revoked_at + key_gc_delay);
+		}
 	}
 
 	up_write(&key->sem);
@@ -1062,8 +1069,10 @@ void key_invalidate(struct key *key)
 
 	if (!test_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
 		down_write_nested(&key->sem, 1);
-		if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags))
+		if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
+			notify_key(key, notify_key_invalidated, 0);
 			key_schedule_gc_links();
+		}
 		up_write(&key->sem);
 	}
 }
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 1ffe60bb2845..f2c767edac87 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -914,6 +914,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 	if (group != (gid_t) -1)
 		key->gid = gid;
 
+	notify_key(key, notify_key_setattr, 0);
 	ret = 0;
 
 error_put:
@@ -964,6 +965,7 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
 	/* if we're not the sysadmin, we can only change a key that we own */
 	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
 		key->perm = perm;
+		notify_key(key, notify_key_setattr, 0);
 		ret = 0;
 	}
 
@@ -1355,10 +1357,12 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
 okay:
 	key = key_ref_to_ptr(key_ref);
 	ret = 0;
-	if (test_bit(KEY_FLAG_KEEP, &key->flags))
+	if (test_bit(KEY_FLAG_KEEP, &key->flags)) {
 		ret = -EPERM;
-	else
+	} else {
 		key_set_timeout(key, timeout);
+		notify_key(key, notify_key_setattr, 0);
+	}
 	key_put(key);
 
 error:
@@ -1631,6 +1635,85 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
 	return ret;
 }
 
+#ifdef CONFIG_KEY_NOTIFICATIONS
+/*
+ * Watch for changes to a key.
+ *
+ * The caller must have View permission to watch a key or keyring.
+ */
+long keyctl_watch_key(key_serial_t id, int watch_queue_fd, int watch_id)
+{
+	struct watch_queue *wqueue;
+	struct watch_list *wlist = NULL;
+	struct watch *watch;
+	struct key *key;
+	key_ref_t key_ref;
+	long ret = -ENOMEM;
+
+	if (watch_id < -1 || watch_id > 0xff)
+		return -EINVAL;
+
+	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE, KEY_NEED_VIEW);
+	if (IS_ERR(key_ref))
+		return PTR_ERR(key_ref);
+	key = key_ref_to_ptr(key_ref);
+
+	wqueue = get_watch_queue(watch_queue_fd);
+	if (IS_ERR(wqueue)) {
+		ret = PTR_ERR(wqueue);
+		goto err_key;
+	}
+
+	if (watch_id >= 0) {
+		if (!key->watchers) {
+			wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+			if (!wlist)
+				goto err_wqueue;
+			INIT_HLIST_HEAD(&wlist->watchers);
+			spin_lock_init(&wlist->lock);
+		}
+
+		watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+		if (!watch)
+			goto err_wlist;
+
+		init_watch(watch);
+		watch->id		= key->serial;
+		watch->queue		= wqueue;
+		watch->info_id		= (u32)watch_id << 24;
+
+		down_write(&key->sem);
+		if (!key->watchers) {
+			key->watchers = wlist;
+			wlist = NULL;
+		}
+
+		watch->watch_list = key->watchers;
+		ret = add_watch_to_object(watch);
+		up_write(&key->sem);
+
+		if (ret < 0)
+			kfree(watch);
+	} else if (key->watchers) {
+		down_write(&key->sem);
+		ret = remove_watch_from_object(key->watchers,
+					       wqueue, key_serial(key),
+					       false);
+		up_write(&key->sem);
+	} else {
+		ret = -EBADSLT;
+	}
+
+err_wlist:
+	kfree(wlist);
+err_wqueue:
+	put_watch_queue(wqueue);
+err_key:
+	key_put(key);
+	return ret;
+}
+#endif /* CONFIG_KEY_NOTIFICATIONS */
+
 /*
  * The key control system call
  */
@@ -1747,6 +1830,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 					       (const char __user *) arg3,
 					       (const char __user *) arg4);
 
+	case KEYCTL_WATCH_KEY:
+		return keyctl_watch_key((key_serial_t)arg2, (int)arg3, (int)arg4);
+
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 41bcf57e96f2..65be9a72d179 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -1019,12 +1019,14 @@ int keyring_restrict(key_ref_t keyring_ref, const char *type,
 	down_write(&keyring->sem);
 	down_write(&keyring_serialise_restrict_sem);
 
-	if (keyring->restrict_link)
+	if (keyring->restrict_link) {
 		ret = -EEXIST;
-	else if (keyring_detect_restriction_cycle(keyring, restrict_link))
+	} else if (keyring_detect_restriction_cycle(keyring, restrict_link)) {
 		ret = -EDEADLK;
-	else
+	} else {
 		keyring->restrict_link = restrict_link;
+		notify_key(keyring, notify_key_setattr, 0);
+	}
 
 	up_write(&keyring_serialise_restrict_sem);
 	up_write(&keyring->sem);
@@ -1287,12 +1289,14 @@ int __key_link_check_live_key(struct key *keyring, struct key *key)
  * holds at most one link to any given key of a particular type+description
  * combination.
  */
-void __key_link(struct key *key, struct assoc_array_edit **_edit)
+void __key_link(struct key *keyring, struct key *key,
+		struct assoc_array_edit **_edit)
 {
 	__key_get(key);
 	assoc_array_insert_set_object(*_edit, keyring_key_to_ptr(key));
 	assoc_array_apply_edit(*_edit);
 	*_edit = NULL;
+	notify_key(keyring, notify_key_linked, key_serial(key));
 }
 
 /*
@@ -1370,7 +1374,7 @@ int key_link(struct key *keyring, struct key *key)
 		if (ret == 0)
 			ret = __key_link_check_live_key(keyring, key);
 		if (ret == 0)
-			__key_link(key, &edit);
+			__key_link(keyring, key, &edit);
 		__key_link_end(keyring, &key->index_key, edit);
 	}
 
@@ -1399,6 +1403,7 @@ EXPORT_SYMBOL(key_link);
 int key_unlink(struct key *keyring, struct key *key)
 {
 	struct assoc_array_edit *edit;
+	key_serial_t target = key_serial(key);
 	int ret;
 
 	key_check(keyring);
@@ -1420,6 +1425,7 @@ int key_unlink(struct key *keyring, struct key *key)
 		goto error;
 
 	assoc_array_apply_edit(edit);
+	notify_key(keyring, notify_key_unlinked, target);
 	key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES);
 	ret = 0;
 
@@ -1453,6 +1459,7 @@ int keyring_clear(struct key *keyring)
 	} else {
 		if (edit)
 			assoc_array_apply_edit(edit);
+		notify_key(keyring, notify_key_cleared, 0);
 		key_payload_reserve(keyring, 0);
 		ret = 0;
 	}
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 114f7408feee..a88145b57fec 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -401,7 +401,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 		goto key_already_present;
 
 	if (dest_keyring)
-		__key_link(key, &edit);
+		__key_link(dest_keyring, key, &edit);
 
 	mutex_unlock(&key_construction_mutex);
 	if (dest_keyring)
@@ -420,7 +420,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 	if (dest_keyring) {
 		ret = __key_link_check_live_key(dest_keyring, key);
 		if (ret == 0)
-			__key_link(key, &edit);
+			__key_link(dest_keyring, key, &edit);
 		__key_link_end(dest_keyring, &ctx->index_key, edit);
 		if (ret < 0)
 			goto link_check_failed;


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 3/5] vfs: Add a mount-notification facility
  2018-07-23 15:25 [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications David Howells
  2018-07-23 15:25 ` [PATCH 1/5] General notification queue with user mmap()'able ring buffer David Howells
  2018-07-23 15:25 ` [PATCH 2/5] KEYS: Add a notification facility David Howells
@ 2018-07-23 15:26 ` David Howells
  2018-07-23 15:26 ` [PATCH 4/5] vfs: Add superblock notifications David Howells
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: David Howells @ 2018-07-23 15:26 UTC (permalink / raw)
  To: viro
  Cc: linux-fsdevel, dhowells, linux-kernel, raven, keyrings,
	linux-security-module

[*] Note that this needs some cleaning up and not all the events work yet.

Add a mount notification facility whereby notifications about changes in
mount topology and configuration can be received.  Note that this only
covers vfsmount topology changes and not superblock events.  A separate
facility will be added for that.

Firstly, an event queue needs to be created:

	fd = open("/dev/event_queue", O_RDWR);

then a notification can be set up to report notifications via that queue:

	struct watch_notification_filter filter;
	memset(&filter, 0, sizeof(filter));
	filter.subtype_filter[0] = ~0ULL;
	filter.info_id		 = 0x02000000;
	mount_notify(AT_FDCWD, "/", 0, fd, &filter);

Note that the queue can be shared between multiple notifications of various
types.

Mount notifications propagate up the tree towards the root, so a watch will
catch all of the events happening in the subtree rooted at the watch.

Signed-off-by: David Howells <dhowells@redhat.com>
---

 arch/x86/entry/syscalls/syscall_32.tbl |    1 
 arch/x86/entry/syscalls/syscall_64.tbl |    1 
 fs/Kconfig                             |    9 ++
 fs/Makefile                            |    1 
 fs/fs_context.c                        |    1 
 fs/mount.h                             |   26 +++++
 fs/mount_notify.c                      |  178 ++++++++++++++++++++++++++++++++
 fs/namespace.c                         |   18 +++
 include/linux/dcache.h                 |    1 
 include/linux/syscalls.h               |    2 
 include/uapi/linux/watch_queue.h       |   24 ++++
 kernel/sys_ni.c                        |    3 +
 12 files changed, 265 insertions(+)
 create mode 100644 fs/mount_notify.c

diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index 806760188a31..449bbcc19a6d 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -405,3 +405,4 @@
 391	i386	fsmount			sys_fsmount			__ia32_sys_fsmount
 392	i386	fspick			sys_fspick			__ia32_sys_fspick
 393	i386	fsinfo			sys_fsinfo			__ia32_sys_fsinfo
+394	i386	mount_notify		sys_mount_notify		__ia32_sys_mount_notify
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 0823eed2b02e..f25fa7ff5fb9 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -350,6 +350,7 @@
 339	common	fsmount			__x64_sys_fsmount
 340	common	fspick			__x64_sys_fspick
 341	common	fsinfo			__x64_sys_fsinfo
+342	common	mount_notify		__x64_sys_mount_notify
 
 #
 # x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/Kconfig b/fs/Kconfig
index ac474a61be37..cbcca62d32e9 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -107,6 +107,15 @@ source "fs/crypto/Kconfig"
 
 source "fs/notify/Kconfig"
 
+config MOUNT_NOTIFICATIONS
+	bool "Mount topology change notifications"
+	select WATCH_QUEUE
+	help
+	  This option provides support for getting change notifications on the
+	  mount tree topology.  This makes use of the /dev/watch_queue misc
+	  device to handle the notification buffer and provides the
+	  mount_notify() system call to enable/disable watchpoints.
+
 source "fs/quota/Kconfig"
 
 source "fs/autofs/Kconfig"
diff --git a/fs/Makefile b/fs/Makefile
index d3b33798998e..49b60030d905 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -129,3 +129,4 @@ obj-y				+= exofs/ # Multiple modules
 obj-$(CONFIG_CEPH_FS)		+= ceph/
 obj-$(CONFIG_PSTORE)		+= pstore/
 obj-$(CONFIG_EFIVAR_FS)		+= efivarfs/
+obj-$(CONFIG_MOUNT_NOTIFICATIONS) += mount_notify.o
diff --git a/fs/fs_context.c b/fs/fs_context.c
index 071723cf11c8..4fa99a438471 100644
--- a/fs/fs_context.c
+++ b/fs/fs_context.c
@@ -321,6 +321,7 @@ struct fs_context *vfs_new_fs_context(struct file_system_type *fs_type,
 	case FS_CONTEXT_FOR_SUBMOUNT:
 		fc->user_ns = get_user_ns(reference->d_sb->s_user_ns);
 		fc->net_ns = get_net(current->nsproxy->net_ns);
+		fc->sb_flags |= SB_SUBMOUNT;
 		break;
 	case FS_CONTEXT_FOR_RECONFIGURE:
 		/* We don't pin any namespaces as the superblock's
diff --git a/fs/mount.h b/fs/mount.h
index f39bc9da4d73..7f72f824b958 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -4,6 +4,7 @@
 #include <linux/poll.h>
 #include <linux/ns_common.h>
 #include <linux/fs_pin.h>
+#include <linux/watch_queue.h>
 
 struct mnt_namespace {
 	atomic_t		count;
@@ -67,9 +68,13 @@ struct mount {
 	int mnt_id;			/* mount identifier */
 	int mnt_group_id;		/* peer group identifier */
 	int mnt_expiry_mark;		/* true if marked for expiry */
+	int mnt_nr_watchers;		/* The number of subtree watches tracking this */
 	struct hlist_head mnt_pins;
 	struct fs_pin mnt_umount;
 	struct dentry *mnt_ex_mountpoint;
+#ifdef CONFIG_MOUNT_NOTIFICATIONS
+	struct watch_list *mnt_watchers; /* Watches on dentries within this mount */
+#endif
 } __randomize_layout;
 
 #define MNT_NS_INTERNAL ERR_PTR(-EINVAL) /* distinct from any mnt_namespace */
@@ -146,3 +151,24 @@ static inline bool is_local_mountpoint(struct dentry *dentry)
 
 	return __is_local_mountpoint(dentry);
 }
+
+extern void post_mount_notification(struct mount *changed,
+				    struct mount_notification *notify);
+
+static inline void notify_mount(struct mount *changed,
+				struct mount *aux,
+				enum mount_notification_subtype subtype,
+				u32 info_flags)
+{
+#ifdef CONFIG_MOUNT_NOTIFICATIONS
+	struct mount_notification n = {
+		.watch.type	= WATCH_TYPE_MOUNT_NOTIFY,
+		.watch.subtype	= subtype,
+		.watch.info	= info_flags | sizeof(n),
+		.triggered_on	= changed->mnt_id,
+		.changed_mount	= aux ? aux->mnt_id : 0,
+	};
+
+	post_mount_notification(changed, &n);
+#endif
+}
diff --git a/fs/mount_notify.c b/fs/mount_notify.c
new file mode 100644
index 000000000000..b4905c363136
--- /dev/null
+++ b/fs/mount_notify.c
@@ -0,0 +1,178 @@
+/* Provide mount topology/attribute change notifications.
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/syscalls.h>
+#include "mount.h"
+
+/*
+ * Post mount notifications to all watches going rootwards along the tree.
+ *
+ * Must be called with the mount_lock held.
+ */
+void post_mount_notification(struct mount *changed,
+			     struct mount_notification *notify)
+{
+	struct path cursor;
+	struct mount *mnt;
+	unsigned seq;
+
+	seq = 0;
+	rcu_read_lock();
+restart:
+	cursor.mnt = &changed->mnt;
+	cursor.dentry = changed->mnt.mnt_root;
+	mnt = real_mount(cursor.mnt);
+	notify->watch.info &= ~WATCH_INFO_IN_SUBTREE;
+
+	read_seqbegin_or_lock(&rename_lock, &seq);
+	for (;;) {
+		if (mnt->mnt_watchers &&
+		    !hlist_empty(&mnt->mnt_watchers->watchers)) {
+			if (cursor.dentry->d_flags & DCACHE_MOUNT_WATCH)
+				post_watch_notification(mnt->mnt_watchers,
+							&notify->watch,
+							(unsigned long)cursor.dentry);
+		} else {
+			cursor.dentry = mnt->mnt.mnt_root;
+		}
+		notify->watch.info |= WATCH_INFO_IN_SUBTREE;
+
+		if (cursor.dentry == cursor.mnt->mnt_root ||
+		    IS_ROOT(cursor.dentry)) {
+			struct mount *parent = READ_ONCE(mnt->mnt_parent);
+
+			/* Escaped? */
+			if (cursor.dentry != cursor.mnt->mnt_root)
+				break;
+
+			/* Global root? */
+			if (mnt != parent) {
+				cursor.dentry = READ_ONCE(mnt->mnt_mountpoint);
+				mnt = parent;
+				cursor.mnt = &mnt->mnt;
+				continue;
+			}
+			break;
+		}
+
+		cursor.dentry = cursor.dentry->d_parent;
+	}
+
+	if (need_seqretry(&rename_lock, seq)) {
+		seq = 1;
+		goto restart;
+	}
+
+	done_seqretry(&rename_lock, seq);
+	rcu_read_unlock();
+}
+
+static void release_mount_watch(struct watch_list *wlist, struct watch *watch)
+{
+	struct vfsmount *mnt = watch->private;
+	struct dentry *dentry = (struct dentry *)(unsigned long)watch->id;
+
+	dput(dentry);
+	mntput(mnt);
+}
+
+/**
+ * sys_mount_notify - Watch for mount topology/attribute changes
+ * @dfd: Base directory to pathwalk from or fd referring to mount.
+ * @filename: Path to mount to place the watch upon
+ * @at_flags: Pathwalk control flags
+ * @watch_fd: The watch queue to send notifications to.
+ * @watch_id: The watch ID to be placed in the notification (-1 to remove watch)
+ */
+SYSCALL_DEFINE5(mount_notify,
+		int, dfd,
+		const char __user *, filename,
+		unsigned int, at_flags,
+		int, watch_fd,
+		int, watch_id)
+{
+	struct watch_queue *wqueue;
+	struct watch_list *wlist = NULL;
+	struct watch *watch;
+	struct mount *m;
+	struct path path;
+	int ret;
+
+	if (watch_id < -1 || watch_id > 0xff)
+		return -EINVAL;
+
+	ret = user_path_at(dfd, filename, at_flags, &path);
+	if (ret)
+		return ret;
+
+	wqueue = get_watch_queue(watch_fd);
+	if (IS_ERR(wqueue))
+		goto err_path;
+
+	m = real_mount(path.mnt);
+
+	if (watch_id >= 0) {
+		if (!m->mnt_watchers) {
+			wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+			if (!wlist)
+				goto err_wqueue;
+			INIT_HLIST_HEAD(&wlist->watchers);
+			spin_lock_init(&wlist->lock);
+			wlist->release_watch = release_mount_watch;
+		}
+
+		watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+		if (!watch)
+			goto err_wlist;
+
+		init_watch(watch);
+		watch->id		= (unsigned long)path.dentry;
+		watch->queue		= wqueue;
+		watch->private		= path.mnt;
+		watch->info_id		= (u32)watch_id << 24;
+
+		down_write(&m->mnt.mnt_sb->s_umount);
+		if (!m->mnt_watchers) {
+			m->mnt_watchers = wlist;
+			wlist = NULL;
+		}
+
+		watch->watch_list = m->mnt_watchers;
+		ret = add_watch_to_object(watch);
+		if (ret == 0) {
+			spin_lock(&path.dentry->d_lock);
+			path.dentry->d_flags |= DCACHE_MOUNT_WATCH;
+			spin_unlock(&path.dentry->d_lock);
+			path_get(&path);
+		}
+		up_write(&m->mnt.mnt_sb->s_umount);
+		if (ret < 0)
+			kfree(watch);
+	} else if (m->mnt_watchers) {
+		down_write(&m->mnt.mnt_sb->s_umount);
+		ret = remove_watch_from_object(m->mnt_watchers, wqueue,
+					       (unsigned long)path.dentry,
+					       false);
+		up_write(&m->mnt.mnt_sb->s_umount);
+	} else {
+		ret = -EBADSLT;
+	}
+
+err_wlist:
+	kfree(wlist);
+err_wqueue:
+	put_watch_queue(wqueue);
+err_path:
+	path_put(&path);
+	return ret;
+}
diff --git a/fs/namespace.c b/fs/namespace.c
index 7e7b1145d15d..d4d16111659d 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -589,6 +589,9 @@ static int mnt_make_readonly(struct mount *mnt)
 	smp_wmb();
 	mnt->mnt.mnt_flags &= ~MNT_WRITE_HOLD;
 	unlock_mount_hash();
+	if (ret == 0)
+		notify_mount(mnt, NULL, notify_mount_readonly,
+			     WATCH_INFO_FLAG_0);
 	return ret;
 }
 
@@ -597,6 +600,7 @@ static int __mnt_unmake_readonly(struct mount *mnt)
 	lock_mount_hash();
 	mnt->mnt.mnt_flags &= ~MNT_READONLY;
 	unlock_mount_hash();
+	notify_mount(mnt, NULL, notify_mount_readonly, 0);
 	return 0;
 }
 
@@ -900,6 +904,7 @@ static void umount_mnt(struct mount *mnt)
 {
 	/* old mountpoint will be dropped when we can do that */
 	mnt->mnt_ex_mountpoint = mnt->mnt_mountpoint;
+	notify_mount(mnt->mnt_parent, mnt, notify_mount_unmount, 0);
 	unhash_mnt(mnt);
 }
 
@@ -1451,6 +1456,11 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
 		p = list_first_entry(&tmp_list, struct mount, mnt_list);
 		list_del_init(&p->mnt_expire);
 		list_del_init(&p->mnt_list);
+
+#ifdef CONFIG_MOUNT_NOTIFICATIONS
+		if (p->mnt_watchers)
+			remove_watch_list(p->mnt_watchers);
+#endif
 		ns = p->mnt_ns;
 		if (ns) {
 			ns->mounts--;
@@ -2004,11 +2014,17 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 		lock_mount_hash();
 	}
 	if (parent_path) {
+		notify_mount(source_mnt->mnt_parent, source_mnt,
+			     notify_mount_move_from, 0);
 		detach_mnt(source_mnt, parent_path);
+		notify_mount(dest_mnt, source_mnt, notify_mount_move_to, 0);
 		attach_mnt(source_mnt, dest_mnt, dest_mp);
 		touch_mnt_namespace(source_mnt->mnt_ns);
 	} else {
 		mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
+		notify_mount(dest_mnt, source_mnt, notify_mount_new_mount,
+			     source_mnt->mnt.mnt_sb->s_flags & SB_SUBMOUNT ?
+			     WATCH_INFO_FLAG_0 : 0);
 		commit_tree(source_mnt);
 	}
 
@@ -2361,6 +2377,7 @@ static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags)
 	mnt->mnt.mnt_flags = mnt_flags;
 	touch_mnt_namespace(mnt->mnt_ns);
 	unlock_mount_hash();
+	notify_mount(mnt, NULL, notify_mount_setattr, 0);
 }
 
 /*
@@ -2767,6 +2784,7 @@ void mark_mounts_for_expiry(struct list_head *mounts)
 		if (!xchg(&mnt->mnt_expiry_mark, 1) ||
 			propagate_mount_busy(mnt, 1))
 			continue;
+		notify_mount(mnt, NULL, notify_mount_expiry, 0);
 		list_move(&mnt->mnt_expire, &graveyard);
 	}
 	while (!list_empty(&graveyard)) {
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 66c6e17e61e5..b0eb68ed5b9b 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -217,6 +217,7 @@ struct dentry_operations {
 
 #define DCACHE_PAR_LOOKUP		0x10000000 /* being looked up (with parent locked shared) */
 #define DCACHE_DENTRY_CURSOR		0x20000000
+#define DCACHE_MOUNT_WATCH		0x40000000 /* There's a mount watch here */
 
 extern seqlock_t rename_lock;
 
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 84b653874ab8..7db37c58289a 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -913,6 +913,8 @@ asmlinkage long sys_fspick(int dfd, const char __user *path, unsigned int flags)
 asmlinkage long sys_fsinfo(int dfd, const char __user *path,
 			   struct fsinfo_params __user *params,
 			   void __user *buffer, size_t buf_size);
+asmlinkage long sys_mount_notify(int dfd, const char __user *path,
+				 unsigned int at_flags, int watch_fd, int watch_id);
 
 /*
  * Architecture-specific system calls
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 3e0ab5fe388d..9d8e165e0065 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -103,4 +103,28 @@ struct key_notification {
 	__u32	aux;		/* Per-type auxiliary data */
 };
 
+/*
+ * Type of mount topology change notification.
+ */
+enum mount_notification_subtype {
+	notify_mount_new_mount	= 0, /* New mount added */
+	notify_mount_unmount	= 1, /* Mount removed manually */
+	notify_mount_expiry	= 2, /* Automount expired */
+	notify_mount_readonly	= 3, /* Mount R/O state changed */
+	notify_mount_setattr	= 4, /* Mount attributes changed */
+	notify_mount_move_from	= 5, /* Mount moved from here */
+	notify_mount_move_to	= 6, /* Mount moved to here (compare op_id) */
+};
+
+/*
+ * Mount topology/configuration change notification record.
+ * - watch.type = WATCH_TYPE_MOUNT_NOTIFY
+ * - watch.subtype = enum mount_notification_subtype
+ */
+struct mount_notification {
+	struct watch_notification watch; /* WATCH_TYPE_MOUNT_NOTIFY */
+	__u32	triggered_on;		/* The mount that the notify was on */
+	__u32	changed_mount;		/* The mount that got changed */
+};
+
 #endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index df556175be50..f608777be045 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -80,6 +80,9 @@ COND_SYSCALL(ioprio_get);
 /* fs/locks.c */
 COND_SYSCALL(flock);
 
+/* fs/mount_notify.c */
+COND_SYSCALL(mount_notify);
+
 /* fs/namei.c */
 
 /* fs/namespace.c */


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 4/5] vfs: Add superblock notifications
  2018-07-23 15:25 [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications David Howells
                   ` (2 preceding siblings ...)
  2018-07-23 15:26 ` [PATCH 3/5] vfs: Add a mount-notification facility David Howells
@ 2018-07-23 15:26 ` David Howells
  2018-07-23 15:26 ` [PATCH 5/5] Add sample notification program David Howells
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: David Howells @ 2018-07-23 15:26 UTC (permalink / raw)
  To: viro
  Cc: linux-fsdevel, dhowells, linux-kernel, raven, keyrings,
	linux-security-module

Add a superblock event notification facility whereby notifications about
superblock events, such as I/O errors (EIO), quota limits being hit
(EDQUOT) and running out of space (ENOSPC) can be reported to a monitoring
process asynchronously.  Note that this does not cover vfsmount topology
changes.  mount_notify() is used for that.

Firstly, an event queue needs to be created:

	fd = open("/dev/event_queue", O_RDWR);

then a notification can be set up to report notifications via that queue:

	struct watch_notification_filter filter;
	memset(&filter, 0, sizeof(filter));
	filter.subtype_filter[0] = ~0ULL;
	filter.info_id		 = 0x03000000;
	sb_notify(AT_FDCWD, "/home/dhowells", 0, fd, &filter);

In this case, it would let me monitor my own homedir for events.

Note that the queue can be shared between multiple notifications of various
types.

[*] QUESTION: Does this want to be per-sb, per-mount_namespace,
    per-some-new-notify-ns or per-system?  Or do multiple options make
    sense?

[*] QUESTION: I've done it this way so that anyone could theoretically
    monitor the superblock of any filesystem they can pathwalk to, but do
    we need other security controls?

[*] QUESTION: Should the LSM be able to filter the events a queue can
    receive?  For instance the opener of the queue would grant that queue
    subject creds (by ->f_cred) that could be used to govern what events
    could be seen, assuming the target superblock to have some object
    creds, based on, say, the mounter.

Signed-off-by: David Howells <dhowells@redhat.com>
---

 arch/x86/entry/syscalls/syscall_32.tbl |    1 
 arch/x86/entry/syscalls/syscall_64.tbl |    1 
 fs/Kconfig                             |   12 +++
 fs/super.c                             |  116 ++++++++++++++++++++++++++++++++
 include/linux/fs.h                     |   77 +++++++++++++++++++++
 include/linux/syscalls.h               |    2 +
 include/uapi/linux/watch_queue.h       |   26 +++++++
 kernel/sys_ni.c                        |    3 +
 8 files changed, 238 insertions(+)

diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index 449bbcc19a6d..c9db9d51a7df 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -406,3 +406,4 @@
 392	i386	fspick			sys_fspick			__ia32_sys_fspick
 393	i386	fsinfo			sys_fsinfo			__ia32_sys_fsinfo
 394	i386	mount_notify		sys_mount_notify		__ia32_sys_mount_notify
+395	i386	sb_notify		sys_sb_notify			__ia32_sys_sb_notify
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index f25fa7ff5fb9..17869bf7788a 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -351,6 +351,7 @@
 340	common	fspick			__x64_sys_fspick
 341	common	fsinfo			__x64_sys_fsinfo
 342	common	mount_notify		__x64_sys_mount_notify
+343	common	sb_notify		__x64_sys_sb_notify
 
 #
 # x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/Kconfig b/fs/Kconfig
index cbcca62d32e9..0551abf08504 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -116,6 +116,18 @@ config MOUNT_NOTIFICATIONS
 	  device to handle the notification buffer and provides the
 	  mount_notify() system call to enable/disable watchpoints.
 
+config SB_NOTIFICATIONS
+	bool "Superblock event notifications"
+	select WATCH_QUEUE
+	help
+	  This option provides support for receiving superblock event
+	  notifications.  This makes use of the /dev/watch_queue misc device to
+	  handle the notification buffer and provides the sb_notify() system
+	  call to enable/disable watches.
+
+	  Events can include things like changing between R/W and R/O, EIO
+	  generation, ENOSPC generation and EDQUOT generation.
+
 source "fs/quota/Kconfig"
 
 source "fs/autofs/Kconfig"
diff --git a/fs/super.c b/fs/super.c
index 3fe5d12b7697..1a1cf517dbd8 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -37,6 +37,8 @@
 #include <linux/user_namespace.h>
 #include <uapi/linux/mount.h>
 #include <linux/fs_context.h>
+#include <linux/syscalls.h>
+#include <linux/namei.h>
 #include "internal.h"
 
 static int thaw_super_locked(struct super_block *sb);
@@ -320,6 +322,10 @@ void deactivate_locked_super(struct super_block *s)
 {
 	struct file_system_type *fs = s->s_type;
 	if (atomic_dec_and_test(&s->s_active)) {
+#ifdef CONFIG_SB_NOTIFICATIONS
+		if (s->s_watchers)
+			remove_watch_list(s->s_watchers);
+#endif
 		cleancache_invalidate_fs(s);
 		unregister_shrinker(&s->s_shrink);
 		fs->kill_sb(s);
@@ -997,6 +1003,8 @@ int do_remount_sb(struct super_block *sb, int sb_flags, void *data,
 	/* Needs to be ordered wrt mnt_is_readonly() */
 	smp_wmb();
 	sb->s_readonly_remount = 0;
+	notify_sb(sb, notify_superblock_readonly,
+		  remount_ro ? WATCH_INFO_FLAG_0 : 0);
 
 	/*
 	 * Some filesystems modify their metadata via some other path than the
@@ -1810,3 +1818,111 @@ int vfs_get_tree(struct fs_context *fc)
 	return ret;
 }
 EXPORT_SYMBOL(vfs_get_tree);
+
+#ifdef CONFIG_SB_NOTIFICATIONS
+/*
+ * Post superblock notifications.
+ */
+void post_sb_notification(struct super_block *s, struct superblock_notification *n)
+{
+	post_watch_notification(s->s_watchers, &n->watch, s->s_watch_id);
+}
+
+static void release_sb_watch(struct watch_list *wlist, struct watch *watch)
+{
+	struct super_block *s = watch->private;
+
+	put_super(s);
+}
+
+/**
+ * sys_sb_notify - Watch for superblock events.
+ * @dfd: Base directory to pathwalk from or fd referring to superblock.
+ * @filename: Path to superblock to place the watch upon
+ * @at_flags: Pathwalk control flags
+ * @watch_fd: The watch queue to send notifications to.
+ * @watch_id: The watch ID to be placed in the notification (-1 to remove watch)
+ */
+SYSCALL_DEFINE5(sb_notify,
+		int, dfd,
+		const char __user *, filename,
+		unsigned int, at_flags,
+		int, watch_fd,
+		int, watch_id)
+{
+	struct watch_queue *wqueue;
+	struct super_block *s;
+	struct watch_list *wlist = NULL;
+	struct watch *watch;
+	struct path path;
+	int ret;
+
+	if (watch_id < -1 || watch_id > 0xff)
+		return -EINVAL;
+
+	ret = user_path_at(dfd, filename, at_flags, &path);
+	if (ret)
+		return ret;
+
+	wqueue = get_watch_queue(watch_fd);
+	if (IS_ERR(wqueue))
+		goto err_path;
+
+	s = path.dentry->d_sb;
+	if (watch_id >= 0) {
+		if (!s->s_watchers) {
+			wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+			if (!wlist)
+				goto err_wqueue;
+			INIT_HLIST_HEAD(&wlist->watchers);
+			spin_lock_init(&wlist->lock);
+			wlist->release_watch = release_sb_watch;
+		}
+
+		watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+		if (!watch)
+			goto err_wlist;
+
+		init_watch(watch);
+		watch->id		= s->s_watch_id;
+		watch->queue		= wqueue;
+		watch->private		= s;
+		watch->info_id		= (u32)watch_id << 24;
+
+		down_write(&s->s_umount);
+		ret = -EIO;
+		if (atomic_read(&s->s_active)) {
+			if (!s->s_watchers) {
+				s->s_watchers = wlist;
+				wlist = NULL;
+			}
+
+			watch->watch_list = s->s_watchers;
+			ret = add_watch_to_object(watch);
+			if (ret == 0) {
+				spin_lock(&sb_lock);
+				s->s_count++;
+				spin_unlock(&sb_lock);
+			}
+		}
+		up_write(&s->s_umount);
+		if (ret < 0)
+			kfree(watch);
+	} else if (s->s_watchers) {
+		down_write(&s->s_umount);
+		ret = remove_watch_from_object(s->s_watchers, wqueue,
+					       s->s_watch_id, false);
+		up_write(&s->s_umount);
+	} else {
+		ret = -EBADSLT;
+	}
+
+err_wlist:
+	kfree(wlist);
+err_wqueue:
+	put_watch_queue(wqueue);
+err_path:
+	path_put(&path);
+	return ret;
+}
+#endif
diff --git a/include/linux/fs.h b/include/linux/fs.h
index bcbe94c0dfe8..6dbc4f9aa6c3 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -37,6 +37,7 @@
 #include <linux/uuid.h>
 #include <linux/errseq.h>
 #include <linux/ioprio.h>
+#include <linux/watch_queue.h>
 
 #include <asm/byteorder.h>
 #include <uapi/linux/fs.h>
@@ -1463,6 +1464,12 @@ struct super_block {
 
 	spinlock_t		s_inode_wblist_lock;
 	struct list_head	s_inodes_wb;	/* writeback inodes */
+
+	/* Superblock event notifications */
+#ifdef CONFIG_SB_NOTIFICATIONS
+	struct watch_list	*s_watchers;
+	u64			s_watch_id;
+#endif
 } __randomize_layout;
 
 /* Helper functions so that in most cases filesystems will
@@ -3458,4 +3465,74 @@ static inline bool dir_relax_shared(struct inode *inode)
 extern bool path_noexec(const struct path *path);
 extern void inode_nohighmem(struct inode *inode);
 
+extern void post_sb_notification(struct super_block *, struct superblock_notification *);
+
+/**
+ * notify_sb: Post simple superblock notification.
+ * @s: The superblock the notification is about.
+ * @subtype: The type of notification.
+ */
+static inline void notify_sb(struct super_block *s,
+			     enum superblock_notification_type subtype,
+			     u32 info)
+{
+#ifdef CONFIG_SB_NOTIFICATIONS
+	if (unlikely(s->s_watchers)) {
+		struct superblock_notification n = {
+			.watch.type	= WATCH_TYPE_SB_NOTIFY,
+			.watch.subtype	= subtype,
+			.watch.info	= sizeof(n) | info,
+			.sb_id		= s->s_watch_id,
+		};
+
+		post_sb_notification(s, &n);
+	}
+			     
+#endif
+}
+
+/**
+ * sb_error: Post superblock error notification.
+ * @s: The superblock the notification is about.
+ */
+static inline int sb_error(struct super_block *s, int error)
+{
+#ifdef CONFIG_SB_NOTIFICATIONS
+	if (unlikely(s->s_watchers)) {
+		struct superblock_error_notification n = {
+			.s.watch.type	= WATCH_TYPE_SB_NOTIFY,
+			.s.watch.subtype = notify_superblock_error,
+			.s.watch.info	= sizeof(n),
+			.s.sb_id	= s->s_watch_id,
+			.error_number	= error,
+			.error_cookie	= 0,
+		};
+
+		post_sb_notification(s, &n.s);
+	}
+#endif
+	return error;
+}
+
+/**
+ * sb_EDQUOT: Post superblock quota overrun notification.
+ * @s: The superblock the notification is about.
+ */
+static inline int sb_EQDUOT(struct super_block *s)
+{
+#ifdef CONFIG_SB_NOTIFICATIONS
+	if (unlikely(s->s_watchers)) {
+		struct superblock_notification n = {
+			.watch.type	= WATCH_TYPE_SB_NOTIFY,
+			.watch.subtype	= notify_superblock_edquot,
+			.watch.info	= sizeof(n),
+			.sb_id		= s->s_watch_id,
+		};
+
+		post_sb_notification(s, &n);
+	}
+#endif
+	return -EDQUOT;
+}
+
 #endif /* _LINUX_FS_H */
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 7db37c58289a..4d852f218949 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -915,6 +915,8 @@ asmlinkage long sys_fsinfo(int dfd, const char __user *path,
 			   void __user *buffer, size_t buf_size);
 asmlinkage long sys_mount_notify(int dfd, const char __user *path,
 				 unsigned int at_flags, int watch_fd, int watch_id);
+asmlinkage long sys_sb_notify(int dfd, const char __user *path,
+			      unsigned int at_flags, int watch_fd, int watch_id);
 
 /*
  * Architecture-specific system calls
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 9d8e165e0065..40a3f809c73c 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -127,4 +127,30 @@ struct mount_notification {
 	__u32	changed_mount;		/* The mount that got changed */
 };
 
+/*
+ * Type of superblock notification.
+ */
+enum superblock_notification_type {
+	notify_superblock_readonly	= 0, /* Filesystem toggled between R/O and R/W */
+	notify_superblock_error		= 1, /* Error in filesystem or blockdev */
+	notify_superblock_edquot	= 2, /* EDQUOT notification */
+	notify_superblock_network	= 3, /* Network status change */
+};
+
+/*
+ * Superblock notification record.
+ * - watch.type = WATCH_TYPE_MOUNT_NOTIFY
+ * - watch.subtype = enum superblock_notification_subtype
+ */
+struct superblock_notification {
+	struct watch_notification watch; /* WATCH_TYPE_SB_NOTIFY */
+	__u64	sb_id;			/* 64-bit superblock ID [fsinfo_ids::f_sb_id] */
+};
+
+struct superblock_error_notification {
+	struct superblock_notification s; /* subtype = notify_superblock_error */
+	__u32	error_number;
+	__u32	error_cookie;
+};
+
 #endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index f608777be045..3b5aacb8a5a0 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -100,6 +100,9 @@ COND_SYSCALL(quotactl);
 
 /* fs/read_write.c */
 
+/* fs/sb_notify.c */
+COND_SYSCALL(sb_notify);
+
 /* fs/sendfile.c */
 
 /* fs/select.c */


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 5/5] Add sample notification program
  2018-07-23 15:25 [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications David Howells
                   ` (3 preceding siblings ...)
  2018-07-23 15:26 ` [PATCH 4/5] vfs: Add superblock notifications David Howells
@ 2018-07-23 15:26 ` David Howells
  2018-07-23 16:31 ` [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications Casey Schaufler
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: David Howells @ 2018-07-23 15:26 UTC (permalink / raw)
  To: viro
  Cc: linux-fsdevel, dhowells, linux-kernel, raven, keyrings,
	linux-security-module

This needs to be linked with -lkeyutils.

It is run like:

	./watch_test

and watches "/" for mount changes and the current session keyring for key
changes:

	# keyctl add user a a @s
	1035096409
	# keyctl unlink 1035096409 @s
	# mount -t tmpfs none /mnt/nfsv3tcp/
	# umount /mnt/nfsv3tcp

producing:

	# ./watch_test
	ptrs h=4 t=2 m=20003
	NOTIFY[00000004-00000002] ty=0003 sy=0002 i=01000010
	KEY 2ffc2e5d change=2[linked] aux=1035096409
	ptrs h=6 t=4 m=20003
	NOTIFY[00000006-00000004] ty=0003 sy=0003 i=01000010
	KEY 2ffc2e5d change=3[unlinked] aux=1035096409
	ptrs h=8 t=6 m=20003
	NOTIFY[00000008-00000006] ty=0001 sy=0000 i=02000010
	MOUNT 00000013 change=0[new_mount] aux=168
	ptrs h=a t=8 m=20003
	NOTIFY[0000000a-00000008] ty=0001 sy=0001 i=02000010
	MOUNT 00000013 change=1[unmount] aux=168
---

 samples/Kconfig                  |    6 +
 samples/Makefile                 |    2 
 samples/watch_queue/Makefile     |    9 +
 samples/watch_queue/watch_test.c |  232 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 248 insertions(+), 1 deletion(-)
 create mode 100644 samples/watch_queue/Makefile
 create mode 100644 samples/watch_queue/watch_test.c

diff --git a/samples/Kconfig b/samples/Kconfig
index 1c5658bc6224..dac30cefe895 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -156,4 +156,10 @@ config SAMPLE_MOUNT_API
 	help
 	  Build example userspace program(s) that use the new mount API.
 
+config SAMPLE_WATCH_QUEUE
+	bool "Build example /dev/watch_queue notification consumer"
+	help
+	  Build example userspace program to use the new mount_notify(),
+	  sb_notify() syscalls and the KEYCTL_WATCH_KEY keyctl() function.
+
 endif # SAMPLES
diff --git a/samples/Makefile b/samples/Makefile
index 31d08cc71a5c..ed545cbf65c8 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -3,4 +3,4 @@
 obj-$(CONFIG_SAMPLES)	+= kobject/ kprobes/ trace_events/ livepatch/ \
 			   hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \
 			   configfs/ connector/ v4l/ trace_printk/ \
-			   vfio-mdev/ statx/ qmi/ mount_api/
+			   vfio-mdev/ statx/ qmi/ mount_api/ watch_queue/
diff --git a/samples/watch_queue/Makefile b/samples/watch_queue/Makefile
new file mode 100644
index 000000000000..1f20ee2b0add
--- /dev/null
+++ b/samples/watch_queue/Makefile
@@ -0,0 +1,9 @@
+# List of programs to build
+hostprogs-$(CONFIG_SAMPLE_WATCH_QUEUE) := watch_test
+
+# Tell kbuild to always build the programs
+always := $(hostprogs-y)
+
+HOSTCFLAGS_watch_test.o += -I$(objtree)/usr/include
+
+HOSTLOADLIBES_watch_test += -lkeyutils
diff --git a/samples/watch_queue/watch_test.c b/samples/watch_queue/watch_test.c
new file mode 100644
index 000000000000..cdbaaf5a1163
--- /dev/null
+++ b/samples/watch_queue/watch_test.c
@@ -0,0 +1,232 @@
+/* Use /dev/watch_queue to watch for keyring and mount topology changes.
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <poll.h>
+#include <limits.h>
+#include <linux/watch_queue.h>
+#include <linux/unistd.h>
+#include <linux/keyctl.h>
+
+#define BUF_SIZE 4
+
+static const char *key_subtypes[] = {
+	[notify_key_instantiated]	= "instantiated",
+	[notify_key_updated]		= "updated",
+	[notify_key_linked]		= "linked",
+	[notify_key_unlinked]		= "unlinked",
+	[notify_key_cleared]		= "cleared",
+	[notify_key_revoked]		= "revoked",
+	[notify_key_invalidated]	= "invalidated",
+	[notify_key_setattr]		= "setattr",
+};
+
+static void saw_key_change(struct watch_notification *n)
+{
+	struct key_notification *k = (struct key_notification *)n;
+	unsigned int len = n->info & WATCH_INFO_LENGTH;
+
+	if (len != sizeof(struct key_notification))
+		return;
+
+	printf("KEY %08x change=%u[%s] aux=%u\n",
+	       k->key_id, n->subtype, key_subtypes[n->subtype], k->aux);
+}
+
+static const char *mount_subtypes[] = {
+	[notify_mount_new_mount]	= "new_mount",
+	[notify_mount_unmount]		= "unmount",
+	[notify_mount_expiry]		= "expiry",
+	[notify_mount_readonly]		= "readonly",
+	[notify_mount_setattr]		= "setattr",
+	[notify_mount_move_from]	= "move_from",
+	[notify_mount_move_to]		= "move_to",
+};
+
+static long keyctl_watch_key(int key, int watch_fd, int watch_id)
+{
+	return syscall(__NR_keyctl, KEYCTL_WATCH_KEY, key, watch_fd, watch_id);
+}
+
+static void saw_mount_change(struct watch_notification *n)
+{
+	struct mount_notification *m = (struct mount_notification *)n;
+	unsigned int len = n->info & WATCH_INFO_LENGTH;
+
+	if (len != sizeof(struct mount_notification))
+		return;
+
+	printf("MOUNT %08x change=%u[%s] aux=%u\n",
+	       m->triggered_on, n->subtype, mount_subtypes[n->subtype], m->changed_mount);
+}
+
+static const char *super_subtypes[] = {
+	[notify_superblock_readonly]	= "readonly",
+	[notify_superblock_error]	= "error",
+	[notify_superblock_edquot]	= "edquot",
+	[notify_superblock_network]	= "network",
+};
+
+static void saw_super_change(struct watch_notification *n)
+{
+	struct superblock_notification *s = (struct superblock_notification *)n;
+	unsigned int len = n->info & WATCH_INFO_LENGTH;
+
+	if (len < sizeof(struct superblock_notification))
+		return;
+
+	printf("SUPER %08llx change=%u[%s]\n",
+	       s->sb_id, n->subtype, super_subtypes[n->subtype]);
+}
+
+/*
+ * Consume and display events.
+ */
+static int consumer(int fd, struct watch_queue_buffer *buf)
+{
+	struct watch_notification *n;
+	struct pollfd p[1];
+	unsigned int head, tail, mask = buf->meta.mask;
+
+	for (;;) {
+		p[0].fd = fd;
+		p[0].events = POLLIN | POLLERR;
+		p[0].revents = 0;
+
+		if (poll(p, 1, -1) == -1) {
+			perror("poll");
+			break;
+		}
+
+		printf("ptrs h=%x t=%x m=%x\n",
+		       buf->meta.head, buf->meta.tail, buf->meta.mask);
+
+		while (head = buf->meta.head,
+		       tail = buf->meta.tail,
+		       tail != head
+		       ) {
+			asm ("lfence" : : : "memory" );
+			n = &buf->slots[tail & mask];
+			printf("NOTIFY[%08x-%08x] ty=%04x sy=%04x i=%08x\n",
+			       head, tail, n->type, n->subtype, n->info);
+			if ((n->info & WATCH_INFO_LENGTH) == 0)
+				goto out;
+
+			switch (n->type) {
+			case WATCH_TYPE_META:
+				if (n->subtype == watch_meta_removal_notification)
+					printf("REMOVAL of watchpoint %08x\n",
+					       n->info & WATCH_INFO_ID);
+				break;
+			case WATCH_TYPE_MOUNT_NOTIFY:
+				saw_mount_change(n);
+				break;
+			case WATCH_TYPE_SB_NOTIFY:
+				saw_super_change(n);
+				break;
+			case WATCH_TYPE_KEY_NOTIFY:
+				saw_key_change(n);
+				break;
+			}
+
+			tail += (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+			asm("mfence" ::: "memory");
+			buf->meta.tail = tail;
+		}
+	}
+
+out:
+	return 0;
+}
+
+static struct watch_notification_filter filter = {
+	.nr_filters	= 3,
+	.__reserved	= 0,
+	.filters = {
+		[0] = {
+			.type			= WATCH_TYPE_MOUNT_NOTIFY,
+			// Reject move-from notifications
+			.subtype_filter[0]	= UINT_MAX & ~(1 << notify_mount_move_from),
+		},
+		[1]	= {
+			.type			= WATCH_TYPE_SB_NOTIFY,
+			// Only accept notification of changes to R/O state
+			.subtype_filter[0]	= (1 << notify_superblock_readonly),
+			// Only accept notifications of change-to-R/O
+			.info_mask		= WATCH_INFO_FLAG_0,
+			.info_filter		= WATCH_INFO_FLAG_0,
+		},
+		[2]	= {
+			.type			= WATCH_TYPE_KEY_NOTIFY,
+			.subtype_filter[0]	= UINT_MAX,
+		},
+	},
+};
+
+int main(int argc, char **argv)
+{
+	struct watch_queue_buffer *buf;
+	size_t page_size;
+	int fd;
+
+	fd = open("/dev/watch_queue", O_RDWR);
+	if (fd == -1) {
+		perror("/dev/watch_queue");
+		exit(1);
+	}
+
+	if (ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE) == -1) {
+		perror("/dev/watch_queue(size)");
+		exit(1);
+	}
+
+	if (ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) == -1) {
+		perror("/dev/watch_queue(filter)");
+		exit(1);
+	}
+
+	page_size = sysconf(_SC_PAGESIZE);
+	buf = mmap(NULL, BUF_SIZE * page_size, PROT_READ | PROT_WRITE,
+		   MAP_SHARED, fd, 0);
+	if (buf == MAP_FAILED) {
+		perror("mmap");
+		exit(1);
+	}
+
+	if (keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01) == -1) {
+		perror("keyctl");
+		exit(1);
+	}
+
+	if (syscall(__NR_mount_notify, AT_FDCWD, "/", 0, fd, 0x02) == -1) {
+		perror("mount_notify");
+		exit(1);
+	}
+
+	if (syscall(__NR_sb_notify, AT_FDCWD, "/mnt", 0, fd, 0x03) == -1) {
+		perror("sb_notify");
+		exit(1);
+	}
+
+	return consumer(fd, buf);
+}


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications
  2018-07-23 15:25 [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications David Howells
                   ` (4 preceding siblings ...)
  2018-07-23 15:26 ` [PATCH 5/5] Add sample notification program David Howells
@ 2018-07-23 16:31 ` Casey Schaufler
  2018-07-24  0:37   ` Ian Kent
  2018-07-24 16:00 ` David Howells
  2018-08-01 21:04 ` LSM hook for mount, superblock and keys watch notifications David Howells
  7 siblings, 1 reply; 18+ messages in thread
From: Casey Schaufler @ 2018-07-23 16:31 UTC (permalink / raw)
  To: David Howells, viro
  Cc: linux-fsdevel, linux-kernel, raven, keyrings, linux-security-module

On 7/23/2018 8:25 AM, David Howells wrote:
> Hi Al,
>
> Here's a set of patches to add a general variable-length notification queue
> concept and to add sources of events for:

Overall I approve. The interface is a bit clunky. Some concerns below.

>
>  (1) Mount topology and reconfiguration change events.

With the possibility of unprivileged mounting you're
going to have to address access control on events.
If root in a user namespace mounts a filesystem you
may have a case where the "real" user wouldn't want the
listener to receive a notification.

>  (2) Superblocks EIO, ENOSPC and EDQUOT events (not complete yet).

Here, too. If SELinux (for example) policy says you can't see
anything on a filesystem you shouldn't get notifications about
things that happen to that filesystem.

>  (3) Key/keyring changes events

And again, I should only get notifications about keys and
keyrings I have access to.

I expect that you intentionally left off

   (4) User injected events

at this point, but it's an obvious extension. That is going
to require access controls (remember kdbus) so I think you'd
do well to design them in now rather than have some security
module hack like me come along later and "fix" it. 

> One of the reasons for this is so that we can remove the issue of processes
> having to repeatedly and regularly scan /proc/mounts, which has proven to be a
> system performance problem.
>
>
> Design decisions:
>
>  (1) A misc chardev is used to create and open a ring buffer:
>
> 	fd = open("/dev/watch_queue", O_RDWR);
>
>      which is then configured and mmap'd into userspace:
>
> 	ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE);
> 	ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
> 	buf = mmap(NULL, BUF_SIZE * page_size, PROT_READ | PROT_WRITE,
> 		   MAP_SHARED, fd, 0);
>
>      The fd cannot be read or written (though there is a facility to use write
>      to inject records for debugging) and userspace just pulls data directly
>      out of the buffer.
>
>  (2) The ring index pointers are stored inside the ring and are thus
>      accessible to userspace.  Userspace should only update the tail pointer
>      and never the head pointer or risk breaking the buffer.  The kernel
>      checks that the pointers appear valid before trying to use them.  A
>      'skip' record is maintained around the pointers.
>
>  (3) poll() can be used to wait for data to appear in the buffer.
>
>  (4) Records in the buffer are binary, typed and have a length so that they
>      can be of varying size.
>
>      This means that multiple heterogeneous sources can share a common
>      buffer.  Tags may be specified when a watchpoint is created to help
>      distinguish the sources.
>
>  (5) The queue is reusable as there are 16 million types available, of which
>      I've used 4, so there is scope for others to be used.
>
>  (6) Records are filterable as types have up to 256 subtypes that can be
>      individually filtered.  Other filtration is also available.
>
>  (7) Each time the buffer is opened, a new buffer is created - this means that
>      there's no interference between watchers.
>
>  (8) When recording a notification, the kernel will not sleep, but will rather
>      mark a queue as overrun if there's insufficient space, thereby avoiding
>      userspace causing the kernel to hang.
>
>  (9) The 'watchpoint' should be specific where possible, meaning that you
>      specify the object that you want to watch.
>
> (10) The buffer is created and then watchpoints are attached to it, using one
>      of:
>
> 	keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01);
> 	mount_notify(AT_FDCWD, "/", 0, fd, 0x02);
> 	sb_notify(AT_FDCWD, "/mnt", 0, fd, 0x03);
>
>      where in all three cases, fd indicates the queue and the number after is
>      a tag between 0 and 255.
>
> (11) The watch must be removed if either the watch buffer is destroyed or the
>      watched object is destroyed.
>
>
> Things I want to avoid:
>
>  (1) Introducing features that make the core VFS dependent on the network
>      stack or networking namespaces (ie. usage of netlink).
>
>  (2) Dumping all this stuff into dmesg and having a daemon that sits there
>      parsing the output and distributing it as this then puts the
>      responsibility for security into userspace and makes handling namespaces
>      tricky.  Further, dmesg might not exist or might be inaccessible inside a
>      container.
>
>  (3) Letting users see events they shouldn't be able to see.
>
>
> Further things that need to be done:
>
>  (1) fsinfo() syscall needs to find superblocks by ID as well as by path so
>      that it can query a superblock for information without the need to try
>      and work out how to reach it - if the calling process even can.
>
>  (2) A mount_info() syscall is needed that can enumerate all the children of a
>      mount.  This is necessary because mountpoints can hide each other by
>      stacking, so paths are not unique keys.  This will require the ability to
>      look up a mount by ID.  This avoids the need to parse /proc/mounts.
>
>  (3) A keyctl call is needed to allow a watch on a keyring to be extended to
>      "children" of that keyring, such that the watch is removed from the child
>      if it is unlinked from the keyring.
>
>  (4) A global superblock event queue maybe?
>
>  (5) Propagating watches to child superblock over automounts?
>
>
> The patches can be found here also:
>
> 	http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=notifications
>
> David
> ---
> David Howells (5):
>       General notification queue with user mmap()'able ring buffer
>       KEYS: Add a notification facility
>       vfs: Add a mount-notification facility
>       vfs: Add superblock notifications
>       Add sample notification program
>
>
>  Documentation/security/keys/core.rst   |   59 ++
>  Documentation/watch_queue.rst          |  305 ++++++++++++
>  arch/x86/entry/syscalls/syscall_32.tbl |    2 
>  arch/x86/entry/syscalls/syscall_64.tbl |    2 
>  drivers/misc/Kconfig                   |    9 
>  drivers/misc/Makefile                  |    1 
>  drivers/misc/watch_queue.c             |  835 ++++++++++++++++++++++++++++++++
>  fs/Kconfig                             |   21 +
>  fs/Makefile                            |    1 
>  fs/fs_context.c                        |    1 
>  fs/mount.h                             |   26 +
>  fs/mount_notify.c                      |  178 +++++++
>  fs/namespace.c                         |   18 +
>  fs/super.c                             |  116 ++++
>  include/linux/dcache.h                 |    1 
>  include/linux/fs.h                     |   77 +++
>  include/linux/key.h                    |    4 
>  include/linux/syscalls.h               |    4 
>  include/linux/watch_queue.h            |   87 +++
>  include/uapi/linux/keyctl.h            |    1 
>  include/uapi/linux/watch_queue.h       |  156 ++++++
>  kernel/sys_ni.c                        |    6 
>  mm/interval_tree.c                     |    2 
>  mm/memory.c                            |    1 
>  samples/Kconfig                        |    6 
>  samples/Makefile                       |    2 
>  samples/watch_queue/Makefile           |    9 
>  samples/watch_queue/watch_test.c       |  232 +++++++++
>  security/keys/Kconfig                  |   10 
>  security/keys/compat.c                 |    3 
>  security/keys/gc.c                     |    5 
>  security/keys/internal.h               |   29 +
>  security/keys/key.c                    |   37 +
>  security/keys/keyctl.c                 |   90 +++
>  security/keys/keyring.c                |   17 -
>  security/keys/request_key.c            |    4 
>  36 files changed, 2332 insertions(+), 25 deletions(-)
>  create mode 100644 Documentation/watch_queue.rst
>  create mode 100644 drivers/misc/watch_queue.c
>  create mode 100644 fs/mount_notify.c
>  create mode 100644 include/linux/watch_queue.h
>  create mode 100644 include/uapi/linux/watch_queue.h
>  create mode 100644 samples/watch_queue/Makefile
>  create mode 100644 samples/watch_queue/watch_test.c
>
>


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications
  2018-07-23 16:31 ` [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications Casey Schaufler
@ 2018-07-24  0:37   ` Ian Kent
  0 siblings, 0 replies; 18+ messages in thread
From: Ian Kent @ 2018-07-24  0:37 UTC (permalink / raw)
  To: Casey Schaufler, David Howells, viro
  Cc: linux-fsdevel, linux-kernel, keyrings, linux-security-module

On Mon, 2018-07-23 at 09:31 -0700, Casey Schaufler wrote:
> On 7/23/2018 8:25 AM, David Howells wrote:
> > Hi Al,
> > 
> > Here's a set of patches to add a general variable-length notification queue
> > concept and to add sources of events for:
> 
> Overall I approve. The interface is a bit clunky. Some concerns below.
> 
> > 
> >  (1) Mount topology and reconfiguration change events.
> 
> With the possibility of unprivileged mounting you're
> going to have to address access control on events.
> If root in a user namespace mounts a filesystem you
> may have a case where the "real" user wouldn't want the
> listener to receive a notification.
> 
> >  (2) Superblocks EIO, ENOSPC and EDQUOT events (not complete yet).
> 
> Here, too. If SELinux (for example) policy says you can't see
> anything on a filesystem you shouldn't get notifications about
> things that happen to that filesystem.
> 
> >  (3) Key/keyring changes events
> 
> And again, I should only get notifications about keys and
> keyrings I have access to.
> 
> I expect that you intentionally left off
> 
>    (4) User injected events
> 
> at this point, but it's an obvious extension. That is going
> to require access controls (remember kdbus) so I think you'd
> do well to design them in now rather than have some security
> module hack like me come along later and "fix" it. 

I thought mount name space should be considered too even
though I wasn't considering the cloning of file handles
into a user mount name space.

But can this happen in other ways besides user mount name
space creation (I'm fishing here)?

And nsenter(1) doesn't require an exec for anything other
than a pid name space change so a forced close on exec
wouldn't be enough. Or am I mistaken in that nsenter(1)
actually requires running a program (even though the man
page implies it's optional) ...

Are there other consideration my limited understanding is
missing?

> 
> > One of the reasons for this is so that we can remove the issue of processes
> > having to repeatedly and regularly scan /proc/mounts, which has proven to be
> > a
> > system performance problem.
> > 
> > 
> > Design decisions:
> > 
> >  (1) A misc chardev is used to create and open a ring buffer:
> > 
> > 	fd = open("/dev/watch_queue", O_RDWR);
> > 
> >      which is then configured and mmap'd into userspace:
> > 
> > 	ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE);
> > 	ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
> > 	buf = mmap(NULL, BUF_SIZE * page_size, PROT_READ | PROT_WRITE,
> > 		   MAP_SHARED, fd, 0);
> > 
> >      The fd cannot be read or written (though there is a facility to use
> > write
> >      to inject records for debugging) and userspace just pulls data directly
> >      out of the buffer.
> > 
> >  (2) The ring index pointers are stored inside the ring and are thus
> >      accessible to userspace.  Userspace should only update the tail pointer
> >      and never the head pointer or risk breaking the buffer.  The kernel
> >      checks that the pointers appear valid before trying to use them.  A
> >      'skip' record is maintained around the pointers.
> > 
> >  (3) poll() can be used to wait for data to appear in the buffer.
> > 
> >  (4) Records in the buffer are binary, typed and have a length so that they
> >      can be of varying size.
> > 
> >      This means that multiple heterogeneous sources can share a common
> >      buffer.  Tags may be specified when a watchpoint is created to help
> >      distinguish the sources.
> > 
> >  (5) The queue is reusable as there are 16 million types available, of which
> >      I've used 4, so there is scope for others to be used.
> > 
> >  (6) Records are filterable as types have up to 256 subtypes that can be
> >      individually filtered.  Other filtration is also available.
> > 
> >  (7) Each time the buffer is opened, a new buffer is created - this means
> > that
> >      there's no interference between watchers.
> > 
> >  (8) When recording a notification, the kernel will not sleep, but will
> > rather
> >      mark a queue as overrun if there's insufficient space, thereby avoiding
> >      userspace causing the kernel to hang.
> > 
> >  (9) The 'watchpoint' should be specific where possible, meaning that you
> >      specify the object that you want to watch.
> > 
> > (10) The buffer is created and then watchpoints are attached to it, using
> > one
> >      of:
> > 
> > 	keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01);
> > 	mount_notify(AT_FDCWD, "/", 0, fd, 0x02);
> > 	sb_notify(AT_FDCWD, "/mnt", 0, fd, 0x03);
> > 
> >      where in all three cases, fd indicates the queue and the number after
> > is
> >      a tag between 0 and 255.
> > 
> > (11) The watch must be removed if either the watch buffer is destroyed or
> > the
> >      watched object is destroyed.
> > 
> > 
> > Things I want to avoid:
> > 
> >  (1) Introducing features that make the core VFS dependent on the network
> >      stack or networking namespaces (ie. usage of netlink).
> > 
> >  (2) Dumping all this stuff into dmesg and having a daemon that sits there
> >      parsing the output and distributing it as this then puts the
> >      responsibility for security into userspace and makes handling
> > namespaces
> >      tricky.  Further, dmesg might not exist or might be inaccessible inside
> > a
> >      container.
> > 
> >  (3) Letting users see events they shouldn't be able to see.
> > 
> > 
> > Further things that need to be done:
> > 
> >  (1) fsinfo() syscall needs to find superblocks by ID as well as by path so
> >      that it can query a superblock for information without the need to try
> >      and work out how to reach it - if the calling process even can.
> > 
> >  (2) A mount_info() syscall is needed that can enumerate all the children of
> > a
> >      mount.  This is necessary because mountpoints can hide each other by
> >      stacking, so paths are not unique keys.  This will require the ability
> > to
> >      look up a mount by ID.  This avoids the need to parse /proc/mounts.
> > 
> >  (3) A keyctl call is needed to allow a watch on a keyring to be extended to
> >      "children" of that keyring, such that the watch is removed from the
> > child
> >      if it is unlinked from the keyring.
> > 
> >  (4) A global superblock event queue maybe?
> > 
> >  (5) Propagating watches to child superblock over automounts?
> > 
> > 
> > The patches can be found here also:
> > 
> > 	http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h
> > =notifications
> > 
> > David
> > ---
> > David Howells (5):
> >       General notification queue with user mmap()'able ring buffer
> >       KEYS: Add a notification facility
> >       vfs: Add a mount-notification facility
> >       vfs: Add superblock notifications
> >       Add sample notification program
> > 
> > 
> >  Documentation/security/keys/core.rst   |   59 ++
> >  Documentation/watch_queue.rst          |  305 ++++++++++++
> >  arch/x86/entry/syscalls/syscall_32.tbl |    2 
> >  arch/x86/entry/syscalls/syscall_64.tbl |    2 
> >  drivers/misc/Kconfig                   |    9 
> >  drivers/misc/Makefile                  |    1 
> >  drivers/misc/watch_queue.c             |  835
> > ++++++++++++++++++++++++++++++++
> >  fs/Kconfig                             |   21 +
> >  fs/Makefile                            |    1 
> >  fs/fs_context.c                        |    1 
> >  fs/mount.h                             |   26 +
> >  fs/mount_notify.c                      |  178 +++++++
> >  fs/namespace.c                         |   18 +
> >  fs/super.c                             |  116 ++++
> >  include/linux/dcache.h                 |    1 
> >  include/linux/fs.h                     |   77 +++
> >  include/linux/key.h                    |    4 
> >  include/linux/syscalls.h               |    4 
> >  include/linux/watch_queue.h            |   87 +++
> >  include/uapi/linux/keyctl.h            |    1 
> >  include/uapi/linux/watch_queue.h       |  156 ++++++
> >  kernel/sys_ni.c                        |    6 
> >  mm/interval_tree.c                     |    2 
> >  mm/memory.c                            |    1 
> >  samples/Kconfig                        |    6 
> >  samples/Makefile                       |    2 
> >  samples/watch_queue/Makefile           |    9 
> >  samples/watch_queue/watch_test.c       |  232 +++++++++
> >  security/keys/Kconfig                  |   10 
> >  security/keys/compat.c                 |    3 
> >  security/keys/gc.c                     |    5 
> >  security/keys/internal.h               |   29 +
> >  security/keys/key.c                    |   37 +
> >  security/keys/keyctl.c                 |   90 +++
> >  security/keys/keyring.c                |   17 -
> >  security/keys/request_key.c            |    4 
> >  36 files changed, 2332 insertions(+), 25 deletions(-)
> >  create mode 100644 Documentation/watch_queue.rst
> >  create mode 100644 drivers/misc/watch_queue.c
> >  create mode 100644 fs/mount_notify.c
> >  create mode 100644 include/linux/watch_queue.h
> >  create mode 100644 include/uapi/linux/watch_queue.h
> >  create mode 100644 samples/watch_queue/Makefile
> >  create mode 100644 samples/watch_queue/watch_test.c
> > 
> > 
> 
> 

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications
  2018-07-23 15:25 [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications David Howells
                   ` (5 preceding siblings ...)
  2018-07-23 16:31 ` [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications Casey Schaufler
@ 2018-07-24 16:00 ` David Howells
  2018-07-24 18:57   ` Casey Schaufler
  2018-07-24 19:22   ` David Howells
  2018-08-01 21:04 ` LSM hook for mount, superblock and keys watch notifications David Howells
  7 siblings, 2 replies; 18+ messages in thread
From: David Howells @ 2018-07-24 16:00 UTC (permalink / raw)
  To: Casey Schaufler
  Cc: dhowells, viro, linux-fsdevel, linux-kernel, raven, keyrings,
	linux-security-module

Casey Schaufler <casey@schaufler-ca.com> wrote:

> >  (1) Mount topology and reconfiguration change events.
> 
> With the possibility of unprivileged mounting you're going to have to
> address access control on events.  If root in a user namespace mounts a
> filesystem you may have a case where the "real" user wouldn't want the
> listener to receive a notification.

Can you clarify who the listener is in this case?

Note that mount topology events don't leak outside of the mount namespace
they're generated in.

That said, if you, a random user, put a watchpoint on "/" you can see the
mount events triggered by another random user in the same mount namespace.  I
don't see a way to control this except by resorting to the LSM since UNIX
doesn't have 'notify' permission bits.

But for each event, I can associate an object label, derived from the source,
and use f_cred on the notification queue to provide a subject label.

> >  (2) Superblocks EIO, ENOSPC and EDQUOT events (not complete yet).
> 
> Here, too. If SELinux (for example) policy says you can't see
> anything on a filesystem you shouldn't get notifications about
> things that happen to that filesystem.

Yep.  Sounds like I need to refer that to the LSM as above.

It's a bit easier for specifically nominated sb sources since you might only
need to do the check once at sb_notify() time.  If there's a general queue
that all sbs contribute to, however, then things become more complicated as
the checks have to be done at do-we-write-into-this-queue? time.

> >  (3) Key/keyring changes events
> 
> And again, I should only get notifications about keys and
> keyrings I have access to.

Currently, you can only watch keys that grant you View permission, which might
suffice.

> I expect that you intentionally left off
> 
>    (4) User injected events
> 
> at this point, but it's an obvious extension. That is going
> to require access controls (remember kdbus) so I think you'd
> do well to design them in now rather than have some security
> module hack like me come along later and "fix" it. 

Yeah - the thought had occurred to me, but there needs to be some way to
define a 'source' and a way to connect them.  Also, would you want a general
source that anyone can contribute through, specific sources where you have to
directly connect or namespace-restricted sources?

David

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications
  2018-07-24 16:00 ` David Howells
@ 2018-07-24 18:57   ` Casey Schaufler
  2018-07-25  5:39     ` Ian Kent
  2018-07-24 19:22   ` David Howells
  1 sibling, 1 reply; 18+ messages in thread
From: Casey Schaufler @ 2018-07-24 18:57 UTC (permalink / raw)
  To: David Howells
  Cc: viro, linux-fsdevel, linux-kernel, raven, keyrings,
	linux-security-module

On 7/24/2018 9:00 AM, David Howells wrote:
> Casey Schaufler <casey@schaufler-ca.com> wrote:
>
>>>  (1) Mount topology and reconfiguration change events.
>> With the possibility of unprivileged mounting you're going to have to
>> address access control on events.  If root in a user namespace mounts a
>> filesystem you may have a case where the "real" user wouldn't want the
>> listener to receive a notification.
> Can you clarify who the listener is in this case?

That would be anyone with a watchpoint set.

> Note that mount topology events don't leak outside of the mount namespace
> they're generated in.
>
> That said, if you, a random user, put a watchpoint on "/" you can see the
> mount events triggered by another random user in the same mount namespace.  I
> don't see a way to control this except by resorting to the LSM since UNIX
> doesn't have 'notify' permission bits.

I would call that a write operation from the process that triggered
the watchpoint to the one watching it. Like a signal. Signals have a
rudimentary DAC policy (write only to the same UID) that could be
your model.

> But for each event, I can associate an object label, derived from the source,
> and use f_cred on the notification queue to provide a subject label.

... or UID or groups.

>
>>>  (2) Superblocks EIO, ENOSPC and EDQUOT events (not complete yet).
>> Here, too. If SELinux (for example) policy says you can't see
>> anything on a filesystem you shouldn't get notifications about
>> things that happen to that filesystem.
> Yep.  Sounds like I need to refer that to the LSM as above.
>
> It's a bit easier for specifically nominated sb sources since you might only
> need to do the check once at sb_notify() time.  If there's a general queue
> that all sbs contribute to, however, then things become more complicated as
> the checks have to be done at do-we-write-into-this-queue? time.
>
>>>  (3) Key/keyring changes events
>> And again, I should only get notifications about keys and
>> keyrings I have access to.
> Currently, you can only watch keys that grant you View permission, which might
> suffice.

That seems appropriate.

>> I expect that you intentionally left off
>>
>>    (4) User injected events
>>
>> at this point, but it's an obvious extension. That is going
>> to require access controls (remember kdbus) so I think you'd
>> do well to design them in now rather than have some security
>> module hack like me come along later and "fix" it. 
> Yeah - the thought had occurred to me, but there needs to be some way to
> define a 'source' and a way to connect them.  Also, would you want a general
> source that anyone can contribute through, specific sources where you have to
> directly connect or namespace-restricted sources?

My guess is that the consensus would be "Yes" to all the above.

>
> David
>


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications
  2018-07-24 16:00 ` David Howells
  2018-07-24 18:57   ` Casey Schaufler
@ 2018-07-24 19:22   ` David Howells
  1 sibling, 0 replies; 18+ messages in thread
From: David Howells @ 2018-07-24 19:22 UTC (permalink / raw)
  To: Casey Schaufler
  Cc: dhowells, viro, ebiederm, linux-fsdevel, linux-kernel, raven,
	keyrings, linux-security-module

Casey Schaufler <casey@schaufler-ca.com> wrote:

> >>>  (1) Mount topology and reconfiguration change events.
> >> With the possibility of unprivileged mounting you're going to have to
> >> address access control on events.  If root in a user namespace mounts a
> >> filesystem you may have a case where the "real" user wouldn't want the
> >> listener to receive a notification.
> > Can you clarify who the listener is in this case?
> 
> That would be anyone with a watchpoint set.

I was wanting clarification on how you viewed events being generated inside
the namespace being seen by an external listener, vs events being generated
outside the namespace being seen by an internal listener.

Hmmm...  OTOH, maybe it's not a problem - can a mount namespace intersect with
two different user namespaces, given it has its own user_ns pointer?

> > But for each event, I can associate an object label, derived from the
> > source, and use f_cred on the notification queue to provide a subject
> > label.
> 
> ... or UID or groups.

Might not be useful if the watched object doesn't have UID or GID - a
superblock say.

Also, that raises an additional question: if someone triggers an event - say a
mount - there is an additional set of creds (that of the triggering process).
Do I need to consider that?

> >>    (4) User injected events
> >>
> >> at this point, but it's an obvious extension. That is going
> >> to require access controls (remember kdbus) so I think you'd
> >> do well to design them in now rather than have some security
> >> module hack like me come along later and "fix" it. 
> > Yeah - the thought had occurred to me, but there needs to be some way to
> > define a 'source' and a way to connect them.  Also, would you want a general
> > source that anyone can contribute through, specific sources where you have to
> > directly connect or namespace-restricted sources?
> 
> My guess is that the consensus would be "Yes" to all the above.

I thought you might say that.

David

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications
  2018-07-24 18:57   ` Casey Schaufler
@ 2018-07-25  5:39     ` Ian Kent
  2018-07-25 15:48       ` Casey Schaufler
  0 siblings, 1 reply; 18+ messages in thread
From: Ian Kent @ 2018-07-25  5:39 UTC (permalink / raw)
  To: Casey Schaufler, David Howells
  Cc: viro, linux-fsdevel, linux-kernel, keyrings, linux-security-module

On Tue, 2018-07-24 at 11:57 -0700, Casey Schaufler wrote:
> On 7/24/2018 9:00 AM, David Howells wrote:
> > Casey Schaufler <casey@schaufler-ca.com> wrote:
> > 
> > > >  (1) Mount topology and reconfiguration change events.
> > > 
> > > With the possibility of unprivileged mounting you're going to have to
> > > address access control on events.  If root in a user namespace mounts a
> > > filesystem you may have a case where the "real" user wouldn't want the
> > > listener to receive a notification.
> > 
> > Can you clarify who the listener is in this case?
> 
> That would be anyone with a watchpoint set.

And that process would have had the privilege to do so ...

> 
> > Note that mount topology events don't leak outside of the mount namespace
> > they're generated in.
> > 
> > That said, if you, a random user, put a watchpoint on "/" you can see the
> > mount events triggered by another random user in the same mount
> > namespace.  I
> > don't see a way to control this except by resorting to the LSM since UNIX
> > doesn't have 'notify' permission bits.
> 
> I would call that a write operation from the process that triggered
> the watchpoint to the one watching it. Like a signal. Signals have a
> rudimentary DAC policy (write only to the same UID) that could be
> your model.

I'm not sure signals are a good comparison.

They can affect a process in significant ways whereas triggering
a notification is less invasive so the security requirements
should take that into consideration.

But there is a problem here I think.

How about the case where a user name space is created or entered
without a newly created mount name space and mounts and umounts
are done, the user name space necessarily expects the table of
mounts it sees to be up to date.

But, if the methods here are used by user space, say libmount
was updated to use it, to gain the efficiency of not constantly
re-reading the proc mount table then restrictions on notifications
would mean the mount table seen in the user name space might not
be updated and would no longer be correct.

The converse is more interesting, where the user name space does
create or enter a new mount name space, then libmount would see ???,
probably not the updated mount information ... unless it opens a
new file handle to get mount update information ... a long running
daemon that uses libmount and dispenses or uses mount information
would very likely have a problem ...

The current proc file system method or providing the mount table
forces a new file handle to be opened whenever getting the mount
table so it always sees only the current mount name space mount
table.

At the very least I need to think more about this ...

> 
> > But for each event, I can associate an object label, derived from the
> > source,
> > and use f_cred on the notification queue to provide a subject label.
> 
> ... or UID or groups.
> 
> > 
> > > >  (2) Superblocks EIO, ENOSPC and EDQUOT events (not complete yet).
> > > 
> > > Here, too. If SELinux (for example) policy says you can't see
> > > anything on a filesystem you shouldn't get notifications about
> > > things that happen to that filesystem.
> > 
> > Yep.  Sounds like I need to refer that to the LSM as above.
> > 
> > It's a bit easier for specifically nominated sb sources since you might only
> > need to do the check once at sb_notify() time.  If there's a general queue
> > that all sbs contribute to, however, then things become more complicated as
> > the checks have to be done at do-we-write-into-this-queue? time.
> > 
> > > >  (3) Key/keyring changes events
> > > 
> > > And again, I should only get notifications about keys and
> > > keyrings I have access to.
> > 
> > Currently, you can only watch keys that grant you View permission, which
> > might
> > suffice.
> 
> That seems appropriate.
> 
> > > I expect that you intentionally left off
> > > 
> > >    (4) User injected events
> > > 
> > > at this point, but it's an obvious extension. That is going
> > > to require access controls (remember kdbus) so I think you'd
> > > do well to design them in now rather than have some security
> > > module hack like me come along later and "fix" it. 
> > 
> > Yeah - the thought had occurred to me, but there needs to be some way to
> > define a 'source' and a way to connect them.  Also, would you want a general
> > source that anyone can contribute through, specific sources where you have
> > to
> > directly connect or namespace-restricted sources?
> 
> My guess is that the consensus would be "Yes" to all the above.
> 
> > 
> > David
> > 
> 
> 

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications
  2018-07-25  5:39     ` Ian Kent
@ 2018-07-25 15:48       ` Casey Schaufler
  2018-07-26  1:18         ` Ian Kent
  0 siblings, 1 reply; 18+ messages in thread
From: Casey Schaufler @ 2018-07-25 15:48 UTC (permalink / raw)
  To: Ian Kent, David Howells
  Cc: viro, linux-fsdevel, linux-kernel, keyrings, linux-security-module

On 7/24/2018 10:39 PM, Ian Kent wrote:
> On Tue, 2018-07-24 at 11:57 -0700, Casey Schaufler wrote:
>> On 7/24/2018 9:00 AM, David Howells wrote:
>>> Casey Schaufler <casey@schaufler-ca.com> wrote:
>>>
>>>>>  (1) Mount topology and reconfiguration change events.
>>>> With the possibility of unprivileged mounting you're going to have to
>>>> address access control on events.  If root in a user namespace mounts a
>>>> filesystem you may have a case where the "real" user wouldn't want the
>>>> listener to receive a notification.
>>> Can you clarify who the listener is in this case?
>> That would be anyone with a watchpoint set.
> And that process would have had the privilege to do so ...

Which isn't the point. The access control isn't on the watchpoint,
it's on delivering the event to the watchpoint. The access control
needs to be based on the process that created the event and the
process receiving the event.

>
>>> Note that mount topology events don't leak outside of the mount namespace
>>> they're generated in.
>>>
>>> That said, if you, a random user, put a watchpoint on "/" you can see the
>>> mount events triggered by another random user in the same mount
>>> namespace.  I
>>> don't see a way to control this except by resorting to the LSM since UNIX
>>> doesn't have 'notify' permission bits.
>> I would call that a write operation from the process that triggered
>> the watchpoint to the one watching it. Like a signal. Signals have a
>> rudimentary DAC policy (write only to the same UID) that could be
>> your model.
> I'm not sure signals are a good comparison.

In both cases you have one process sending information
to another. If you use killpg() instead of kill() you can
send to a number of processes without knowing them individually.
A process can chose what to do with (most) signals, including
ignore them.


> They can affect a process in significant ways whereas triggering
> a notification is less invasive so the security requirements
> should take that into consideration.

I'm looking at this from a security model viewpoint. If process
A sends information to process B that's a write operation with
A as the subject and B as the object. 

> But there is a problem here I think.
>
> How about the case where a user name space is created or entered
> without a newly created mount name space and mounts and umounts
> are done, the user name space necessarily expects the table of
> mounts it sees to be up to date.
>
> But, if the methods here are used by user space, say libmount
> was updated to use it, to gain the efficiency of not constantly
> re-reading the proc mount table then restrictions on notifications
> would mean the mount table seen in the user name space might not
> be updated and would no longer be correct.

Hey, I'm not the one saying that containers don't need/want kernel
manifestation. Of course you may have troubles with access consistency
if you go around changing the access control attributes with namespaces.
One of the problems with signals is that you can't chmod() your process
to allow signals from other specified users. If you don't design an
access control scheme into the watchpoint mechanism you aren't going to
be able to deal with situations like this.

> The converse is more interesting, where the user name space does
> create or enter a new mount name space, then libmount would see ???,
> probably not the updated mount information ... unless it opens a
> new file handle to get mount update information ... a long running
> daemon that uses libmount and dispenses or uses mount information
> would very likely have a problem ...
>
> The current proc file system method or providing the mount table
> forces a new file handle to be opened whenever getting the mount
> table so it always sees only the current mount name space mount
> table.
>
> At the very least I need to think more about this ...
>
>>> But for each event, I can associate an object label, derived from the
>>> source,
>>> and use f_cred on the notification queue to provide a subject label.
>> ... or UID or groups.
>>
>>>>>  (2) Superblocks EIO, ENOSPC and EDQUOT events (not complete yet).
>>>> Here, too. If SELinux (for example) policy says you can't see
>>>> anything on a filesystem you shouldn't get notifications about
>>>> things that happen to that filesystem.
>>> Yep.  Sounds like I need to refer that to the LSM as above.
>>>
>>> It's a bit easier for specifically nominated sb sources since you might only
>>> need to do the check once at sb_notify() time.  If there's a general queue
>>> that all sbs contribute to, however, then things become more complicated as
>>> the checks have to be done at do-we-write-into-this-queue? time.
>>>
>>>>>  (3) Key/keyring changes events
>>>> And again, I should only get notifications about keys and
>>>> keyrings I have access to.
>>> Currently, you can only watch keys that grant you View permission, which
>>> might
>>> suffice.
>> That seems appropriate.
>>
>>>> I expect that you intentionally left off
>>>>
>>>>    (4) User injected events
>>>>
>>>> at this point, but it's an obvious extension. That is going
>>>> to require access controls (remember kdbus) so I think you'd
>>>> do well to design them in now rather than have some security
>>>> module hack like me come along later and "fix" it. 
>>> Yeah - the thought had occurred to me, but there needs to be some way to
>>> define a 'source' and a way to connect them.  Also, would you want a general
>>> source that anyone can contribute through, specific sources where you have
>>> to
>>> directly connect or namespace-restricted sources?
>> My guess is that the consensus would be "Yes" to all the above.
>>
>>> David
>>>
>>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications
  2018-07-25 15:48       ` Casey Schaufler
@ 2018-07-26  1:18         ` Ian Kent
  2018-07-26 16:09           ` Casey Schaufler
  0 siblings, 1 reply; 18+ messages in thread
From: Ian Kent @ 2018-07-26  1:18 UTC (permalink / raw)
  To: Casey Schaufler, David Howells
  Cc: viro, linux-fsdevel, linux-kernel, keyrings, linux-security-module

On Wed, 2018-07-25 at 08:48 -0700, Casey Schaufler wrote:
> On 7/24/2018 10:39 PM, Ian Kent wrote:
> > On Tue, 2018-07-24 at 11:57 -0700, Casey Schaufler wrote:
> > > On 7/24/2018 9:00 AM, David Howells wrote:
> > > > Casey Schaufler <casey@schaufler-ca.com> wrote:
> > > > 
> > > > > >  (1) Mount topology and reconfiguration change events.
> > > > > 
> > > > > With the possibility of unprivileged mounting you're going to have to
> > > > > address access control on events.  If root in a user namespace mounts
> > > > > a
> > > > > filesystem you may have a case where the "real" user wouldn't want the
> > > > > listener to receive a notification.
> > > > 
> > > > Can you clarify who the listener is in this case?
> > > 
> > > That would be anyone with a watchpoint set.
> > 
> > And that process would have had the privilege to do so ...
> 
> Which isn't the point. The access control isn't on the watchpoint,
> it's on delivering the event to the watchpoint. The access control
> needs to be based on the process that created the event and the
> process receiving the event.
> 
> > 
> > > > Note that mount topology events don't leak outside of the mount
> > > > namespace
> > > > they're generated in.
> > > > 
> > > > That said, if you, a random user, put a watchpoint on "/" you can see
> > > > the
> > > > mount events triggered by another random user in the same mount
> > > > namespace.  I
> > > > don't see a way to control this except by resorting to the LSM since
> > > > UNIX
> > > > doesn't have 'notify' permission bits.
> > > 
> > > I would call that a write operation from the process that triggered
> > > the watchpoint to the one watching it. Like a signal. Signals have a
> > > rudimentary DAC policy (write only to the same UID) that could be
> > > your model.
> > 
> > I'm not sure signals are a good comparison.
> 
> In both cases you have one process sending information
> to another. If you use killpg() instead of kill() you can
> send to a number of processes without knowing them individually.
> A process can chose what to do with (most) signals, including
> ignore them.

I'm just saying that the analogy isn't quite the same.

In the this case notifications can't be just sent by a process,
it's entirely controlled by the VFS so the notion of some process
doing something out-off-band doesn't apply.

> 
> > They can affect a process in significant ways whereas triggering
> > a notification is less invasive so the security requirements
> > should take that into consideration.
> 
> I'm looking at this from a security model viewpoint. If process
> A sends information to process B that's a write operation with
> A as the subject and B as the object. 

Perhaps, but again there's no process A deciding to send something.

It's the VFS saying to itself, I see this thing has changed, someone
that had appropriate privilege asked me to tell it so ...

I may be wrong but there isn't a similar access control mechanism
on the proc file system mount table (pseudo) files, only the usual
access control on opening them and what is seen is based on the
mount name space of the opening process.

This is also not quite as what we have here but it is similar.

> 
> > But there is a problem here I think.
> > 
> > How about the case where a user name space is created or entered
> > without a newly created mount name space and mounts and umounts
> > are done, the user name space necessarily expects the table of
> > mounts it sees to be up to date.
> > 
> > But, if the methods here are used by user space, say libmount
> > was updated to use it, to gain the efficiency of not constantly
> > re-reading the proc mount table then restrictions on notifications
> > would mean the mount table seen in the user name space might not
> > be updated and would no longer be correct.
> 
> Hey, I'm not the one saying that containers don't need/want kernel
> manifestation. Of course you may have troubles with access consistency
> if you go around changing the access control attributes with namespaces.
> One of the problems with signals is that you can't chmod() your process
> to allow signals from other specified users. If you don't design an
> access control scheme into the watchpoint mechanism you aren't going to
> be able to deal with situations like this.

Don't get me wrong, I'm not criticizing your suggestion that
some sort of security model is needed.

I'm saying it's not straight forward to work out what's actually
needed and, in thinking about it, I ended up described an additional
potential problem that's not even (necessarily) security related.

> 
> > The converse is more interesting, where the user name space does
> > create or enter a new mount name space, then libmount would see ???,
> > probably not the updated mount information ... unless it opens a
> > new file handle to get mount update information ... a long running
> > daemon that uses libmount and dispenses or uses mount information
> > would very likely have a problem ...
> > 
> > The current proc file system method or providing the mount table
> > forces a new file handle to be opened whenever getting the mount
> > table so it always sees only the current mount name space mount
> > table.
> > 
> > At the very least I need to think more about this ...
> > 
> > > > But for each event, I can associate an object label, derived from the
> > > > source,
> > > > and use f_cred on the notification queue to provide a subject label.
> > > 
> > > ... or UID or groups.
> > > 
> > > > > >  (2) Superblocks EIO, ENOSPC and EDQUOT events (not complete yet).
> > > > > 
> > > > > Here, too. If SELinux (for example) policy says you can't see
> > > > > anything on a filesystem you shouldn't get notifications about
> > > > > things that happen to that filesystem.
> > > > 
> > > > Yep.  Sounds like I need to refer that to the LSM as above.
> > > > 
> > > > It's a bit easier for specifically nominated sb sources since you might
> > > > only
> > > > need to do the check once at sb_notify() time.  If there's a general
> > > > queue
> > > > that all sbs contribute to, however, then things become more complicated
> > > > as
> > > > the checks have to be done at do-we-write-into-this-queue? time.
> > > > 
> > > > > >  (3) Key/keyring changes events
> > > > > 
> > > > > And again, I should only get notifications about keys and
> > > > > keyrings I have access to.
> > > > 
> > > > Currently, you can only watch keys that grant you View permission, which
> > > > might
> > > > suffice.
> > > 
> > > That seems appropriate.
> > > 
> > > > > I expect that you intentionally left off
> > > > > 
> > > > >    (4) User injected events
> > > > > 
> > > > > at this point, but it's an obvious extension. That is going
> > > > > to require access controls (remember kdbus) so I think you'd
> > > > > do well to design them in now rather than have some security
> > > > > module hack like me come along later and "fix" it. 
> > > > 
> > > > Yeah - the thought had occurred to me, but there needs to be some way to
> > > > define a 'source' and a way to connect them.  Also, would you want a
> > > > general
> > > > source that anyone can contribute through, specific sources where you
> > > > have
> > > > to
> > > > directly connect or namespace-restricted sources?
> > > 
> > > My guess is that the consensus would be "Yes" to all the above.
> > > 
> > > > David
> > > > 
> > 
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-security-
> > module" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> > 
> 
> 

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications
  2018-07-26  1:18         ` Ian Kent
@ 2018-07-26 16:09           ` Casey Schaufler
  0 siblings, 0 replies; 18+ messages in thread
From: Casey Schaufler @ 2018-07-26 16:09 UTC (permalink / raw)
  To: Ian Kent, David Howells
  Cc: viro, linux-fsdevel, linux-kernel, keyrings, linux-security-module

On 7/25/2018 6:18 PM, Ian Kent wrote:
> On Wed, 2018-07-25 at 08:48 -0700, Casey Schaufler wrote:
>> On 7/24/2018 10:39 PM, Ian Kent wrote:
>>> On Tue, 2018-07-24 at 11:57 -0700, Casey Schaufler wrote:
>>>> On 7/24/2018 9:00 AM, David Howells wrote:
>>>>> Casey Schaufler <casey@schaufler-ca.com> wrote:
>>>>>
>>>>>>>  (1) Mount topology and reconfiguration change events.
>>>>>> With the possibility of unprivileged mounting you're going to have to
>>>>>> address access control on events.  If root in a user namespace mounts
>>>>>> a
>>>>>> filesystem you may have a case where the "real" user wouldn't want the
>>>>>> listener to receive a notification.
>>>>> Can you clarify who the listener is in this case?
>>>> That would be anyone with a watchpoint set.
>>> And that process would have had the privilege to do so ...
>> Which isn't the point. The access control isn't on the watchpoint,
>> it's on delivering the event to the watchpoint. The access control
>> needs to be based on the process that created the event and the
>> process receiving the event.
>>
>>>>> Note that mount topology events don't leak outside of the mount
>>>>> namespace
>>>>> they're generated in.
>>>>>
>>>>> That said, if you, a random user, put a watchpoint on "/" you can see
>>>>> the
>>>>> mount events triggered by another random user in the same mount
>>>>> namespace.  I
>>>>> don't see a way to control this except by resorting to the LSM since
>>>>> UNIX
>>>>> doesn't have 'notify' permission bits.
>>>> I would call that a write operation from the process that triggered
>>>> the watchpoint to the one watching it. Like a signal. Signals have a
>>>> rudimentary DAC policy (write only to the same UID) that could be
>>>> your model.
>>> I'm not sure signals are a good comparison.
>> In both cases you have one process sending information
>> to another. If you use killpg() instead of kill() you can
>> send to a number of processes without knowing them individually.
>> A process can chose what to do with (most) signals, including
>> ignore them.
> I'm just saying that the analogy isn't quite the same.
>
> In the this case notifications can't be just sent by a process,
> it's entirely controlled by the VFS so the notion of some process
> doing something out-off-band doesn't apply.

If process A does something that sends information to process B
it doesn't matter whether it's explicit (e.g. kill) or implicit
(e.g. exit), you need to have control over the delivery. I can
send Morse code to a listener of mount watchpoints using the
mechanism described. If I'm not supposed to be sending information
to that other user (for whatever reason) that's bad.


>
>>> They can affect a process in significant ways whereas triggering
>>> a notification is less invasive so the security requirements
>>> should take that into consideration.
>> I'm looking at this from a security model viewpoint. If process
>> A sends information to process B that's a write operation with
>> A as the subject and B as the object. 
> Perhaps, but again there's no process A deciding to send something.

It doesn't matter whether it's intentional. It's information flow.

> It's the VFS saying to itself, I see this thing has changed, someone
> that had appropriate privilege asked me to tell it so ...

The VFS doesn't do things on it's own. It isn't a subject. It's a
hunk of code that does what it's told *by processes*.

> I may be wrong but there isn't a similar access control mechanism
> on the proc file system mount table (pseudo) files, only the usual
> access control on opening them and what is seen is based on the
> mount name space of the opening process.

You need access to file entry in /proc. That's my point,
there has to be some mechanism for control. The watchpoint
mechanism proposed offers no way to do chmod 600 on events
that a process can generate.

> This is also not quite as what we have here but it is similar.

The proposed mechanism offers no way for a process to say
"I don't want events about what I do sent to Ian" or for
SELinux to say "Don't send this event to processes marked
unconfined_t. They're all a bunch of squares".

>>> But there is a problem here I think.
>>>
>>> How about the case where a user name space is created or entered
>>> without a newly created mount name space and mounts and umounts
>>> are done, the user name space necessarily expects the table of
>>> mounts it sees to be up to date.
>>>
>>> But, if the methods here are used by user space, say libmount
>>> was updated to use it, to gain the efficiency of not constantly
>>> re-reading the proc mount table then restrictions on notifications
>>> would mean the mount table seen in the user name space might not
>>> be updated and would no longer be correct.
>> Hey, I'm not the one saying that containers don't need/want kernel
>> manifestation. Of course you may have troubles with access consistency
>> if you go around changing the access control attributes with namespaces.
>> One of the problems with signals is that you can't chmod() your process
>> to allow signals from other specified users. If you don't design an
>> access control scheme into the watchpoint mechanism you aren't going to
>> be able to deal with situations like this.
> Don't get me wrong, I'm not criticizing your suggestion that
> some sort of security model is needed.

OK, that's good. :)

> I'm saying it's not straight forward to work out what's actually
> needed and, in thinking about it, I ended up described an additional
> potential problem that's not even (necessarily) security related.

Let me propose a model that shouldn't be too hard to implement
and that is somewhat consistent with existing Linux policies.

When an event is generated the current cred is included in the
message. A filter type is supported that uses the cred information.
The default value for this filter (or if it is absent) is to
deny access to everyone except the owner of the watching process,
just like signals. A process with CAP_DAC_OVERRIDE (or similar),
can change the filter to accept events from other users. The filter
code includes a call

	rc = security_watchpoint_event(struct cred *sender);

so that security modules can decide if the sender is allowed to
write the event to this watcher.

If we want event generators to be in control of who sees what they're
doing it's a bit more complicated. You'll have to add the access
information to the cred so that it gets passed along to the filtering
code. You could pass it in addition to the cred if that's cleaner.
In that case the filter is hard coded and gets the permission from
the sender.

Neither of these should be especially difficult to implement or explain.

>>> The converse is more interesting, where the user name space does
>>> create or enter a new mount name space, then libmount would see ???,
>>> probably not the updated mount information ... unless it opens a
>>> new file handle to get mount update information ... a long running
>>> daemon that uses libmount and dispenses or uses mount information
>>> would very likely have a problem ...
>>>
>>> The current proc file system method or providing the mount table
>>> forces a new file handle to be opened whenever getting the mount
>>> table so it always sees only the current mount name space mount
>>> table.
>>>
>>> At the very least I need to think more about this ...
>>>
>>>>> But for each event, I can associate an object label, derived from the
>>>>> source,
>>>>> and use f_cred on the notification queue to provide a subject label.
>>>> ... or UID or groups.
>>>>
>>>>>>>  (2) Superblocks EIO, ENOSPC and EDQUOT events (not complete yet).
>>>>>> Here, too. If SELinux (for example) policy says you can't see
>>>>>> anything on a filesystem you shouldn't get notifications about
>>>>>> things that happen to that filesystem.
>>>>> Yep.  Sounds like I need to refer that to the LSM as above.
>>>>>
>>>>> It's a bit easier for specifically nominated sb sources since you might
>>>>> only
>>>>> need to do the check once at sb_notify() time.  If there's a general
>>>>> queue
>>>>> that all sbs contribute to, however, then things become more complicated
>>>>> as
>>>>> the checks have to be done at do-we-write-into-this-queue? time.
>>>>>
>>>>>>>  (3) Key/keyring changes events
>>>>>> And again, I should only get notifications about keys and
>>>>>> keyrings I have access to.
>>>>> Currently, you can only watch keys that grant you View permission, which
>>>>> might
>>>>> suffice.
>>>> That seems appropriate.
>>>>
>>>>>> I expect that you intentionally left off
>>>>>>
>>>>>>    (4) User injected events
>>>>>>
>>>>>> at this point, but it's an obvious extension. That is going
>>>>>> to require access controls (remember kdbus) so I think you'd
>>>>>> do well to design them in now rather than have some security
>>>>>> module hack like me come along later and "fix" it. 
>>>>> Yeah - the thought had occurred to me, but there needs to be some way to
>>>>> define a 'source' and a way to connect them.  Also, would you want a
>>>>> general
>>>>> source that anyone can contribute through, specific sources where you
>>>>> have
>>>>> to
>>>>> directly connect or namespace-restricted sources?
>>>> My guess is that the consensus would be "Yes" to all the above.
>>>>
>>>>> David
>>>>>
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-security-
>>> module" in
>>> the body of a message to majordomo@vger.kernel.org
>>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>>>
>>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>


^ permalink raw reply	[flat|nested] 18+ messages in thread

* LSM hook for mount, superblock and keys watch notifications
  2018-07-23 15:25 [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications David Howells
                   ` (6 preceding siblings ...)
  2018-07-24 16:00 ` David Howells
@ 2018-08-01 21:04 ` David Howells
  2018-08-01 21:49   ` Casey Schaufler
  2018-08-01 22:50   ` David Howells
  7 siblings, 2 replies; 18+ messages in thread
From: David Howells @ 2018-08-01 21:04 UTC (permalink / raw)
  To: Casey Schaufler
  Cc: dhowells, viro, linux-fsdevel, linux-kernel, raven, keyrings,
	linux-security-module

How about if I apply the attached patch?  It creates a new LSM hook that gets
called each time a notification is about to be posted on a queue.  It is given
the creds from the /dev/watch_queue opener, the creds of whoever triggered the
watchpoint and a pointer to the notification message.

The notification message includes metadata describing the source/type of the
message and the subtype within that, plus subtype-specific flags.

David
---
diff --git a/drivers/misc/watch_queue.c b/drivers/misc/watch_queue.c
index 13c9f88e6953..3e7bb032ec17 100644
--- a/drivers/misc/watch_queue.c
+++ b/drivers/misc/watch_queue.c
@@ -23,6 +23,8 @@
 #include <linux/uaccess.h>
 #include <linux/vmalloc.h>
 #include <linux/file.h>
+#include <linux/security.h>
+#include <linux/cred.h>
 #include <linux/watch_queue.h>
 
 #define DEBUG_WITH_WRITE /* Allow use of write() to record notifications */
@@ -50,6 +52,7 @@ struct watch_filter {
 struct watch_queue {
 	struct rcu_head		rcu;
 	struct address_space	mapping;
+	const struct cred	*cred;		/* Creds of the owner of the queue */
 	struct watch_filter	*filter;
 	wait_queue_head_t	waiters;
 	struct hlist_head	watches;	/* Contributory watches */
@@ -82,7 +85,8 @@ struct watch_queue {
  * should be in units of sizeof(*n).
  */
 static bool post_one_notification(struct watch_queue *wqueue,
-				  struct watch_notification *n)
+				  struct watch_notification *n,
+				  const struct cred *cred)
 {
 	struct watch_queue_buffer *buf = wqueue->buffer;
 	unsigned int metalen = sizeof(buf->meta) / sizeof(buf->slots[0]);
@@ -99,7 +103,8 @@ static bool post_one_notification(struct watch_queue *wqueue,
 
 	spin_lock(&wqueue->lock); /* Protect head pointer */
 
-	if (wqueue->defunct)
+	if (wqueue->defunct ||
+	    security_post_notification(wqueue->cred, cred, n) < 0)
 		goto out;
 
 	ring_tail = READ_ONCE(buf->meta.tail);
@@ -205,6 +210,7 @@ static bool filter_watch_notification(const struct watch_filter *wf,
  * __post_watch_notification - Post an event notification
  * @wlist: The watch list to post the event to.
  * @n: The notification record to post.
+ * @cred: The creds of the process that triggered the notification.
  * @id: The ID to match on the watch.
  *
  * Post a notification of an event into a set of watch queues and let the users
@@ -217,6 +223,7 @@ static bool filter_watch_notification(const struct watch_filter *wf,
  */
 void __post_watch_notification(struct watch_list *wlist,
 			       struct watch_notification *n,
+			       const struct cred *cred,
 			       u64 id)
 {
 	const struct watch_filter *wf;
@@ -236,7 +243,7 @@ void __post_watch_notification(struct watch_list *wlist,
 		if (wf && !filter_watch_notification(wf, n))
 			continue;
 
-		post_one_notification(wqueue, n);
+		post_one_notification(wqueue, n, cred);
 	}
 
 	rcu_read_unlock();
@@ -517,6 +524,7 @@ static int watch_queue_open(struct inode *inode, struct file *file)
 	refcount_set(&wqueue->usage, 1);
 	spin_lock_init(&wqueue->lock);
 	init_waitqueue_head(&wqueue->waiters);
+	wqueue->cred = get_cred(file->f_cred);
 
 	file->private_data = wqueue;
 	return 0;
@@ -615,7 +623,7 @@ int remove_watch_from_object(struct watch_list *wlist, struct watch_queue *wq,
 	n.subtype = watch_meta_removal_notification;
 	n.info = watch->info_id | sizeof(n);
 
-	post_one_notification(watch->queue, &n);
+	post_one_notification(watch->queue, &n, wq->cred);
 
 	/* We don't need the watch list lock for the next bit as RCU is
 	 * protecting everything from being deallocated.
@@ -733,6 +741,7 @@ static int watch_queue_release(struct inode *inode, struct file *file)
 	if (wqueue->filter)
 		kfree_rcu(wqueue->filter, rcu);
 	kfree(wqueue->pages);
+	put_cred(wqueue->cred);
 	put_watch_queue(wqueue);
 	return 0;
 }
@@ -760,7 +769,7 @@ static ssize_t watch_queue_write(struct file *file,
 		goto error;
 	n->info &= (WATCH_INFO_LENGTH | WATCH_INFO_TYPE_FLAGS | WATCH_INFO_ID);
 
-	if (post_one_notification(wqueue, n))
+	if (post_one_notification(wqueue, n, current_cred()))
 		wqueue->debug = 0;
 	else
 		wqueue->debug++;
diff --git a/fs/mount_notify.c b/fs/mount_notify.c
index b4905c363136..6de6e701bf20 100644
--- a/fs/mount_notify.c
+++ b/fs/mount_notify.c
@@ -22,6 +22,7 @@
 void post_mount_notification(struct mount *changed,
 			     struct mount_notification *notify)
 {
+	const struct cred *cred = current_cred();
 	struct path cursor;
 	struct mount *mnt;
 	unsigned seq;
@@ -40,7 +41,7 @@ void post_mount_notification(struct mount *changed,
 		    !hlist_empty(&mnt->mnt_watchers->watchers)) {
 			if (cursor.dentry->d_flags & DCACHE_MOUNT_WATCH)
 				post_watch_notification(mnt->mnt_watchers,
-							&notify->watch,
+							&notify->watch, cred,
 							(unsigned long)cursor.dentry);
 		} else {
 			cursor.dentry = mnt->mnt.mnt_root;
diff --git a/fs/super.c b/fs/super.c
index 1a1cf517dbd8..939ed08b87e9 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -1825,7 +1825,8 @@ EXPORT_SYMBOL(vfs_get_tree);
  */
 void post_sb_notification(struct super_block *s, struct superblock_notification *n)
 {
-	post_watch_notification(s->s_watchers, &n->watch, s->s_watch_id);
+	post_watch_notification(s->s_watchers, &n->watch, current_cred(),
+				s->s_watch_id);
 }
 
 static void release_sb_watch(struct watch_list *wlist, struct watch *watch)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index b1a62dc0b8d9..08a61a5fecd4 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1439,6 +1439,13 @@
  *	@ctx is a pointer in which to place the allocated security context.
  *	@ctxlen points to the place to put the length of @ctx.
  *
+ * @post_notification:
+ *	Check to see if a watch notification can be posted to a particular
+ *	queue.
+ *	@q_cred: The credentials of the target watch queue.
+ *	@cred: The event-triggerer's credentials
+ *	@n: The notification being posted
+ *
  * Security hooks for using the eBPF maps and programs functionalities through
  * eBPF syscalls.
  *
@@ -1713,6 +1720,11 @@ union security_list_options {
 	int (*inode_notifysecctx)(struct inode *inode, void *ctx, u32 ctxlen);
 	int (*inode_setsecctx)(struct dentry *dentry, void *ctx, u32 ctxlen);
 	int (*inode_getsecctx)(struct inode *inode, void **ctx, u32 *ctxlen);
+#ifdef CONFIG_WATCH_QUEUE
+	int (*post_notification)(const struct cred *q_cred,
+				 const struct cred *cred,
+				 struct watch_notification *n);
+#endif
 
 #ifdef CONFIG_SECURITY_NETWORK
 	int (*unix_stream_connect)(struct sock *sock, struct sock *other,
@@ -1992,6 +2004,9 @@ struct security_hook_heads {
 	struct hlist_head inode_notifysecctx;
 	struct hlist_head inode_setsecctx;
 	struct hlist_head inode_getsecctx;
+#ifdef CONFIG_WATCH_QUEUE
+	struct hlist_head post_notification;
+#endif
 #ifdef CONFIG_SECURITY_NETWORK
 	struct hlist_head unix_stream_connect;
 	struct hlist_head unix_may_send;
diff --git a/include/linux/security.h b/include/linux/security.h
index c73d9ba863bc..946ab7455a3f 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -392,6 +392,11 @@ void security_inode_invalidate_secctx(struct inode *inode);
 int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen);
 int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen);
 int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen);
+#ifdef CONFIG_WATCH_QUEUE
+int security_post_notification(const struct cred *q_cred,
+			       const struct cred *cred,
+			       struct watch_notification *n);
+#endif
 #else /* CONFIG_SECURITY */
 struct security_mnt_opts {
 };
@@ -1216,6 +1221,14 @@ static inline int security_inode_getsecctx(struct inode *inode, void **ctx, u32
 {
 	return -EOPNOTSUPP;
 }
+#ifdef CONFIG_WATCH_QUEUE
+static inline int security_post_notification(const struct cred *q_cred,
+					     const struct cred *cred,
+					     struct watch_notification *n)
+{
+	return 0;
+}
+#endif
 #endif	/* CONFIG_SECURITY */
 
 #ifdef CONFIG_SECURITY_NETWORK
diff --git a/include/linux/watch_queue.h b/include/linux/watch_queue.h
index 465b14f3ad42..a1d20c917998 100644
--- a/include/linux/watch_queue.h
+++ b/include/linux/watch_queue.h
@@ -46,7 +46,9 @@ struct watch_list {
 };
 
 extern void __post_watch_notification(struct watch_list *,
-				      struct watch_notification *, u64);
+				      struct watch_notification *,
+				      const struct cred *,
+				      u64);
 extern struct watch_queue *get_watch_queue(int);
 extern void put_watch_queue(struct watch_queue *);
 extern void put_watch_list(struct watch_list *);
@@ -68,10 +70,11 @@ static inline void init_watch(struct watch *watch)
 
 static inline void post_watch_notification(struct watch_list *wlist,
 					   struct watch_notification *n,
+					   const struct cred *cred,
 					   u64 id)
 {
 	if (unlikely(wlist))
-		__post_watch_notification(wlist, n, id);
+		__post_watch_notification(wlist, n, cred, id);
 }
 
 static inline void remove_watch_list(struct watch_list *wlist)
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 66b424e8c104..8394e42fe5ae 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -194,7 +194,8 @@ static inline void notify_key(struct key *key,
 		.aux		= aux,
 	};
 
-	post_watch_notification(key->watchers, &n.watch, n.key_id);
+	post_watch_notification(key->watchers, &n.watch, current_cred(),
+				n.key_id);
 #endif
 }
 
diff --git a/security/security.c b/security/security.c
index 2439a5613813..2e9c74ca3eac 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1375,6 +1375,15 @@ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen)
 }
 EXPORT_SYMBOL(security_inode_getsecctx);
 
+#ifdef CONFIG_WATCH_QUEUE
+int security_post_notification(const struct cred *q_cred,
+			       const struct cred *cred,
+			       struct watch_notification *n)
+{
+	return call_int_hook(post_notification, 0, q_cred, cred, n);
+}
+#endif
+
 #ifdef CONFIG_SECURITY_NETWORK
 
 int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk)

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: LSM hook for mount, superblock and keys watch notifications
  2018-08-01 21:04 ` LSM hook for mount, superblock and keys watch notifications David Howells
@ 2018-08-01 21:49   ` Casey Schaufler
  2018-08-01 22:50   ` David Howells
  1 sibling, 0 replies; 18+ messages in thread
From: Casey Schaufler @ 2018-08-01 21:49 UTC (permalink / raw)
  To: David Howells
  Cc: viro, linux-fsdevel, linux-kernel, raven, keyrings,
	linux-security-module

On 8/1/2018 2:04 PM, David Howells wrote:
> How about if I apply the attached patch?  It creates a new LSM hook that gets
> called each time a notification is about to be posted on a queue.  It is given
> the creds from the /dev/watch_queue opener, the creds of whoever triggered the
> watchpoint and a pointer to the notification message.
>
> The notification message includes metadata describing the source/type of the
> message and the subtype within that, plus subtype-specific flags.

This looks like it will solve the problem for security modules.
I still think there should be some sort of default controls.
The idea that the default is that anyone can listen for everyone's
events, and there's no way to control it does not sit well.
You could implement that as a security module with the one hook
that does UID based controls, but I don't see a lot of advantages
to doing it that way. At the least a process should be able to hide
the events it would generate from everyone. Better would be to have
a specification (e.g. mod bits, an ACL) for who should receive them.


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: LSM hook for mount, superblock and keys watch notifications
  2018-08-01 21:04 ` LSM hook for mount, superblock and keys watch notifications David Howells
  2018-08-01 21:49   ` Casey Schaufler
@ 2018-08-01 22:50   ` David Howells
  1 sibling, 0 replies; 18+ messages in thread
From: David Howells @ 2018-08-01 22:50 UTC (permalink / raw)
  To: Casey Schaufler
  Cc: dhowells, viro, linux-fsdevel, linux-kernel, raven, keyrings,
	linux-security-module

Casey Schaufler <casey@schaufler-ca.com> wrote:

> This looks like it will solve the problem for security modules.
> I still think there should be some sort of default controls.

The control for keyrings is that you have to have View permission on the thing
you want to set a watchpoint on.

For mount topology changes, everyone can see everything in their namespace
anyway just by repeatedly parsing /proc/mounts or similar.  Whether or not
this is a good idea...

Superblocks - some of the things generated here *shouldn't* be hidden - EIO
for example.

> The idea that the default is that anyone can listen for everyone's
> events, and there's no way to control it does not sit well.
> You could implement that as a security module with the one hook
> that does UID based controls, but I don't see a lot of advantages
> to doing it that way. At the least a process should be able to hide
> the events it would generate from everyone.

That's tricky.  Some of the events aren't actually generated directly by a
process stepping on a trigger - EIO for example.  Further, none of the
interfaces available have a "don't notify" button.  Now I can add one to
move_mount() and fsmount(), but not write() (which might cause EDQUOT or
ENOSPC errors that we might want to know about irrespective of what the user
wants).

> Better would be to have a specification (e.g. mod bits, an ACL) for who
> should receive them.

How does one attach this, and to where?  It doesn't belong to the watch queue,
it belongs to the watched object.  It would also need attaching before the
object gets published.

If you're using fsopen(), you could attach it before doing fsmount() to set an
ACL on a superblock, I suppose:

	fd = fsopen("ext4", 0);
	fsconfig(fd, FSCONFIG_SET_NAMESPACE, "user", NULL, userns_fd);
	fsconfig(fd, FSCONFIG_SET_SB_ACL, NULL, "0:pw,1000:w", 0);
	fsconfig(fd, FSCONFIG_SET_PATH, "source", "/dev/sda1", AT_FDCWD);
	fsconfig(fd, FSCONFIG_SET_FLAG, "acl", NULL, 0);
	mfd = fsmount(0, 0);

where 0 and 1000 are uids and [pw] indicate pickability and watchability, with
the former allowing the use of fspick() with it:

	fd = fspick(AT_FDCWD, "/mnt", 0);

or, even:

	fd = fspick(0, "6f9553ab-7329-4998-bd5b-a5b4add9ad79",
		    FSPICK_OPEN_BY_UUID);

I'm not sure how you'd attach an ACL to mount objects, though.  I guess
move_mount() would have to take a 6th parameter that's the ACL.  You wouldn't
necessarily be able to use the same ACL for both mount and superblock because
of namespaces - it should be possible to have a superblock that's in multiple
user namespace (though it currently isn't).

David

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2018-08-01 22:50 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-07-23 15:25 [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications David Howells
2018-07-23 15:25 ` [PATCH 1/5] General notification queue with user mmap()'able ring buffer David Howells
2018-07-23 15:25 ` [PATCH 2/5] KEYS: Add a notification facility David Howells
2018-07-23 15:26 ` [PATCH 3/5] vfs: Add a mount-notification facility David Howells
2018-07-23 15:26 ` [PATCH 4/5] vfs: Add superblock notifications David Howells
2018-07-23 15:26 ` [PATCH 5/5] Add sample notification program David Howells
2018-07-23 16:31 ` [RFC][PATCH 0/5] Mount, Filesystem and Keyrings notifications Casey Schaufler
2018-07-24  0:37   ` Ian Kent
2018-07-24 16:00 ` David Howells
2018-07-24 18:57   ` Casey Schaufler
2018-07-25  5:39     ` Ian Kent
2018-07-25 15:48       ` Casey Schaufler
2018-07-26  1:18         ` Ian Kent
2018-07-26 16:09           ` Casey Schaufler
2018-07-24 19:22   ` David Howells
2018-08-01 21:04 ` LSM hook for mount, superblock and keys watch notifications David Howells
2018-08-01 21:49   ` Casey Schaufler
2018-08-01 22:50   ` David Howells

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).