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 events, such as mounting, unmounting, mount expiry,
mount reconfiguration.
(2) Superblock events, such as R/W<->R/O changes, quota overrun and I/O
errors (not complete yet).
(3) Key/keyring events, such as creating, linking and removal of keys.
(4) General device events (single common queue) including:
- Block layer events, such as device errors
- USB subsystem events, such as device/bus attach/remove, device
reset, device errors.
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. To further aid this, the fsinfo() syscall
on which this patch series depends, provides a way to access superblock and
mount information in binary form without the need to parse /proc/mounts.
LSM support is included, but controversial:
(1) The creds of the process that did the fput() that reduced the refcount
to zero are cached in the file struct.
(2) __fput() overrides the current creds with the creds from (1) whilst
doing the cleanup, thereby making sure that the creds seen by the
destruction notification generated by mntput() appears to come from
the last fputter.
(3) security_post_notification() is called for each queue that we might
want to post a notification into, thereby allowing the LSM to prevent
covert communications.
(?) Do I need to add security_set_watch(), say, to rule on whether a watch
may be set in the first place? I might need to add a variant per
watch-type.
(?) Do I really need to keep track of the process creds in which an
implicit object destruction happened? For example, imagine you create
an fd with fsopen()/fsmount(). It is marked to dissolve the mount it
refers to on close unless move_mount() clears that flag. Now, imagine
someone looking at that fd through procfs at the same time as you exit
due to an error. The LSM sees the destruction notification come from
the looker if they happen to do their fput() after yours.
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 could be considered:
(1) Adding a keyctl call 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.
(2) Adding global superblock event queue.
(3) 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
Changes:
v4: Split the basic UAPI bits out into their own patch and then split the
LSM hooks out into an intermediate patch. Add LSM hooks for setting
watches.
Rename the *_notify() system calls to watch_*() for consistency.
v3: I've added a USB notification source and reformulated the block
notification source so that there's now a common watch list, for which
the system call is now device_notify().
I've assigned a pair of unused ioctl numbers in the 'W' series to the
ioctls added by this series.
I've also added a description of the kernel API to the documentation.
v2: I've fixed various issues raised by Jann Horn and GregKH and moved to
krefs for refcounting. I've added some security features to try and
give Casey Schaufler the LSM control he wants.
David
---
David Howells (13):
security: Override creds in __fput() with last fputter's creds
uapi: General notification ring definitions
security: Add hooks to rule on setting a watch
security: Add a hook for the point of notification insertion
General notification queue with user mmap()'able ring buffer
keys: Add a notification facility
vfs: Add a mount-notification facility
vfs: Add superblock notifications
fsinfo: Export superblock notification counter
Add a general, global device notification watch list
block: Add block layer notifications
usb: Add USB subsystem notifications
Add sample notification program
Documentation/ioctl/ioctl-number.txt | 1
Documentation/security/keys/core.rst | 58 ++
Documentation/watch_queue.rst | 492 ++++++++++++++++++
arch/x86/entry/syscalls/syscall_32.tbl | 3
arch/x86/entry/syscalls/syscall_64.tbl | 3
block/Kconfig | 9
block/blk-core.c | 29 +
drivers/base/Kconfig | 9
drivers/base/Makefile | 1
drivers/base/watch.c | 89 +++
drivers/misc/Kconfig | 13
drivers/misc/Makefile | 1
drivers/misc/watch_queue.c | 889 ++++++++++++++++++++++++++++++++
drivers/usb/core/Kconfig | 10
drivers/usb/core/devio.c | 55 ++
drivers/usb/core/hub.c | 3
fs/Kconfig | 21 +
fs/Makefile | 1
fs/file_table.c | 12
fs/fsinfo.c | 12
fs/mount.h | 33 +
fs/mount_notify.c | 187 +++++++
fs/namespace.c | 9
fs/super.c | 122 ++++
include/linux/blkdev.h | 15 +
include/linux/dcache.h | 1
include/linux/device.h | 7
include/linux/fs.h | 79 +++
include/linux/key.h | 4
include/linux/lsm_hooks.h | 48 ++
include/linux/security.h | 35 +
include/linux/syscalls.h | 5
include/linux/usb.h | 19 +
include/linux/watch_queue.h | 87 +++
include/uapi/linux/fsinfo.h | 10
include/uapi/linux/keyctl.h | 1
include/uapi/linux/watch_queue.h | 213 ++++++++
kernel/sys_ni.c | 7
samples/Kconfig | 6
samples/Makefile | 1
samples/vfs/test-fsinfo.c | 13
samples/watch_queue/Makefile | 9
samples/watch_queue/watch_test.c | 308 +++++++++++
security/keys/Kconfig | 10
security/keys/compat.c | 2
security/keys/gc.c | 5
security/keys/internal.h | 30 +
security/keys/key.c | 37 +
security/keys/keyctl.c | 95 +++
security/keys/keyring.c | 17 -
security/keys/request_key.c | 4
security/security.c | 29 +
52 files changed, 3121 insertions(+), 38 deletions(-)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 drivers/base/watch.c
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
Add security hooks that will allow an LSM to rule on whether or not a watch
may be set. More than one hook is required as the watches watch different
types of object.
Signed-off-by: David Howells <[email protected]>
cc: Casey Schaufler <[email protected]>
cc: Stephen Smalley <[email protected]>
cc: [email protected]
---
include/linux/lsm_hooks.h | 38 ++++++++++++++++++++++++++++++++++++++
include/linux/security.h | 25 +++++++++++++++++++++++++
security/security.c | 23 +++++++++++++++++++++++
3 files changed, 86 insertions(+)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 2474c3f785ca..0543449c0f6e 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1420,6 +1420,32 @@
* @ctx is a pointer in which to place the allocated security context.
* @ctxlen points to the place to put the length of @ctx.
*
+ * Security hooks for the general notification queue:
+ *
+ * @watch_mount:
+ * Check to see if a process is allowed to watch for mount topology change
+ * notifications on a mount subtree.
+ * @watch: The watch object
+ * @path: The root of the subtree to watch.
+ *
+ * @watch_sb:
+ * Check to see if a process is allowed to watch for event notifications
+ * from a superblock.
+ * @watch: The watch object
+ * @sb: The superblock to watch.
+ *
+ * @watch_key:
+ * Check to see if a process is allowed to watch for event notifications
+ * from a key or keyring.
+ * @watch: The watch object
+ * @key: The key to watch.
+ *
+ * @watch_devices:
+ * Check to see if a process is allowed to watch for event notifications
+ * from devices (as a global set).
+ * @watch: The watch object
+ *
+ *
* Security hooks for using the eBPF maps and programs functionalities through
* eBPF syscalls.
*
@@ -1698,6 +1724,12 @@ 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 (*watch_mount)(struct watch *watch, struct path *path);
+ int (*watch_sb)(struct watch *watch, struct super_block *sb);
+ int (*watch_key)(struct watch *watch, struct key *key);
+ int (*watch_devices)(struct watch *watch);
+#endif /* CONFIG_WATCH_QUEUE */
#ifdef CONFIG_SECURITY_NETWORK
int (*unix_stream_connect)(struct sock *sock, struct sock *other,
@@ -1977,6 +2009,12 @@ 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 watch_mount;
+ struct hlist_head watch_sb;
+ struct hlist_head watch_key;
+ struct hlist_head watch_devices;
+#endif /* CONFIG_WATCH_QUEUE */
#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 23c8b602c0ab..ac3bf6b5f87b 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -58,6 +58,7 @@ struct fs_context;
struct fs_parameter;
enum fs_value_type;
struct fsinfo_kparams;
+struct watch;
/* Default (no) options for the capable function */
#define CAP_OPT_NONE 0x0
@@ -396,6 +397,12 @@ 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_watch_mount(struct watch *watch, struct path *path);
+int security_watch_sb(struct watch *watch, struct super_block *sb);
+int security_watch_key(struct watch *watch, struct key *key);
+int security_watch_devices(struct watch *watch);
+#endif /* CONFIG_WATCH_QUEUE */
#else /* CONFIG_SECURITY */
static inline int call_lsm_notifier(enum lsm_event event, void *data)
@@ -1215,6 +1222,24 @@ static inline int security_inode_getsecctx(struct inode *inode, void **ctx, u32
{
return -EOPNOTSUPP;
}
+#ifdef CONFIG_WATCH_QUEUE
+static inline int security_watch_mount(struct watch *watch, struct path *path)
+{
+ return 0;
+}
+static inline int security_watch_sb(struct watch *watch, struct super_block *sb)
+{
+ return 0;
+}
+static inline int security_watch_key(struct watch *watch, struct key *key)
+{
+ return 0;
+}
+static inline int security_watch_devices(struct watch *watch)
+{
+ return 0;
+}
+#endif /* CONFIG_WATCH_QUEUE */
#endif /* CONFIG_SECURITY */
#ifdef CONFIG_SECURITY_NETWORK
diff --git a/security/security.c b/security/security.c
index 3af886e8fced..ee2f8539a0d5 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1929,6 +1929,29 @@ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen)
}
EXPORT_SYMBOL(security_inode_getsecctx);
+#ifdef CONFIG_WATCH_QUEUE
+int security_watch_mount(struct watch *watch, struct path *path)
+{
+ return call_int_hook(watch_mount, 0, watch, path);
+}
+
+int security_watch_sb(struct watch *watch, struct super_block *sb)
+{
+ return call_int_hook(watch_sb, 0, watch, sb);
+}
+
+int security_watch_key(struct watch *watch, struct key *key)
+{
+ return call_int_hook(watch_key, 0, watch, key);
+}
+
+int security_watch_devices(struct watch *watch)
+{
+ return call_int_hook(watch_devices, 0, watch);
+}
+
+#endif /* CONFIG_WATCH_QUEUE */
+
#ifdef CONFIG_SECURITY_NETWORK
int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk)
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/watch_queue", 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, block layer events) 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 <[email protected]>
---
Documentation/ioctl/ioctl-number.txt | 1
Documentation/watch_queue.rst | 462 ++++++++++++++++++
drivers/misc/Kconfig | 13
drivers/misc/Makefile | 1
drivers/misc/watch_queue.c | 889 ++++++++++++++++++++++++++++++++++
include/linux/watch_queue.h | 84 +++
include/uapi/linux/watch_queue.h | 20 +
7 files changed, 1470 insertions(+)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 drivers/misc/watch_queue.c
create mode 100644 include/linux/watch_queue.h
diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt
index c9558146ac58..e7b2e56fcfdd 100644
--- a/Documentation/ioctl/ioctl-number.txt
+++ b/Documentation/ioctl/ioctl-number.txt
@@ -194,6 +194,7 @@ Code Seq#(hex) Include File Comments
'W' 00-1F linux/wanrouter.h conflict! (pre 3.9)
'W' 00-3F sound/asound.h conflict!
'W' 40-5F drivers/pci/switch/switchtec.c
+'W' 60-61 linux/watch_queue.h
'X' all fs/xfs/xfs_fs.h conflict!
and fs/xfs/linux-2.6/xfs_ioctl32.h
and include/linux/falloc.h
diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
new file mode 100644
index 000000000000..0668c4a31710
--- /dev/null
+++ b/Documentation/watch_queue.rst
@@ -0,0 +1,462 @@
+============================
+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 can be used in conjunction with::
+
+ * Key/keyring notifications
+
+ * Mount topology change notifications
+
+ * Superblock event notifications
+
+ * Block layer 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 the 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 {
+ __u32 type:24;
+ __u32 subtype:8;
+ __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 specific to the type and
+ subtype.
+
+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 List (Notification Source) API
+====================================
+
+A "watch list" is a list of watchers that are subscribed to a source of
+notifications. A list may be attached to an object (say a key or a superblock)
+or may be global (say for device events). From a userspace perspective, a
+non-global watch list is typically referred to by reference to the object it
+belongs to (such as using KEYCTL_NOTIFY and giving it a key serial number to
+watch that specific key).
+
+To manage a watch list, the following functions are provided:
+
+ * ``void init_watch_list(struct watch_list *wlist,
+ void (*release_watch)(struct watch *wlist));``
+
+ Initialise a watch list. If ``release_watch`` is not NULL, then this
+ indicates a function that should be called when the watch_list object is
+ destroyed to discard any references the watch list holds on the watched
+ object.
+
+ * ``void remove_watch_list(struct watch_list *wlist);``
+
+ This removes all of the watches subscribed to a watch_list and frees them
+ and then destroys the watch_list object itself.
+
+
+Watch Queue (Notification Buffer) API
+=====================================
+
+A "watch queue" is the buffer allocated by or on behalf of the application that
+notification records will be written into. The workings of this are hidden
+entirely inside of the watch_queue device driver, but it is necessary to gain a
+reference to it to place a watch. These can be managed with:
+
+ * ``struct watch_queue *get_watch_queue(int fd);``
+
+ Since watch queues are indicated to the kernel by the fd of the character
+ device that implements the buffer, userspace must hand that fd through a
+ system call. This can be used to look up an opaque pointer to the watch
+ queue from the system call.
+
+ * ``void put_watch_queue(struct watch_queue *wqueue);``
+
+ This discards the reference obtained from ``get_watch_queue()``.
+
+
+Watch Subscription API
+======================
+
+A "watch" is a subscription on a watch list, indicating the watch queue, and
+thus the buffer, into which notification records should be written. The watch
+queue object may also carry filtering rules for that object, as set by
+userspace. Some parts of the watch struct can be set by the driver::
+
+ struct watch {
+ union {
+ u32 info_id; /* ID to be OR'd in to info field */
+ ...
+ };
+ void *private; /* Private data for the watched object */
+ u64 id; /* Internal identifier */
+ ...
+ };
+
+The ``info_id`` value should be an 8-bit number obtained from userspace and
+shifted by WATCH_INFO_ID__SHIFT. This is OR'd into the WATCH_INFO_ID field of
+struct watch_notification::info when and if the notification is written into
+the associated watch queue buffer.
+
+The ``private`` field is the driver's data associated with the watch_list and
+is cleaned up by the ``watch_list::release_watch()`` method.
+
+The ``id`` field is the source's ID. Notifications that are posted with a
+different ID are ignored.
+
+The following functions are provided to manage watches:
+
+ * ``void init_watch(struct watch *watch, struct watch_queue *wqueue);``
+
+ Initialise a watch object, setting its pointer to the watch queue, using
+ appropriate barriering to avoid lockdep complaints.
+
+ * ``int add_watch_to_object(struct watch *watch, struct watch_list *wlist);``
+
+ Subscribe a watch to a watch list (notification source). The
+ driver-settable fields in the watch struct must have been set before this
+ is called.
+
+ * ``int remove_watch_from_object(struct watch_list *wlist,
+ struct watch_queue *wqueue,
+ u64 id, false);``
+
+ Remove a watch from a watch list, where the watch must match the specified
+ watch queue (``wqueue``) and object identifier (``id``). A notification
+ (``WATCH_META_REMOVAL_NOTIFICATION``) is sent to the watch queue to
+ indicate that the watch got removed.
+
+ * ``int remove_watch_from_object(struct watch_list *wlist, NULL, 0, true);``
+
+ Remove all the watches from a watch list. It is expected that this will be
+ called preparatory to destruction and that the watch list will be
+ inaccessible to new watches by this point. A notification
+ (``WATCH_META_REMOVAL_NOTIFICATION``) is sent to the watch queue of each
+ subscribed watch to indicate that the watch got removed.
+
+
+Notification Posting API
+========================
+
+To post a notification to watch list so that the subscribed watches can see it,
+the following function should be used::
+
+ void post_watch_notification(struct watch_list *wlist,
+ struct watch_notification *n,
+ const struct cred *cred,
+ u64 id);
+
+The notification should be preformatted and a pointer to the header (``n``)
+should be passed in. The notification may be larger than this and the size is
+noted in ``n->info & WATCH_INFO_LENGTH``.
+
+ [!] Note that the length must be 8-byte aligned and OR'd in directly.
+ The three bits that correspond to the bottom part of the length are
+ overlain with metadata flags.
+
+The ``cred`` struct indicates the credentials of the source (subject) and is
+passed to the LSMs, such as SELinux, to allow or suppress the recording of the
+note in each individual queue according to the credentials of that queue
+(object).
+
+The ``id`` is the ID of the source object (such as the serial number on a key).
+Only watches that have the same ID set in them will see this notification.
+
+
+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.
+
+ * WATCH_TYPE_BLOCK_NOTIFY
+
+ Notifications of this type indicate block layer events, such as I/O errors
+ or temporary link loss. Watchpoints of this type are set on a global
+ queue.
+
+
+Event Filtering
+===============
+
+Once a watch queue has been created, a set of filters can be applied to limit
+the events that are received using::
+
+ struct watch_notification_filter filter = {
+ ...
+ };
+ ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter)
+
+The filter description is a variable of type::
+
+ struct watch_notification_filter {
+ __u32 nr_filters;
+ __u32 __reserved;
+ struct watch_notification_type_filter filters[];
+ };
+
+Where "nr_filters" is the number of filters in filters[] and "__reserved"
+should be 0. The "filters" array has elements of the following type::
+
+ struct watch_notification_type_filter {
+ __u32 type;
+ __u32 info_filter;
+ __u32 info_mask;
+ __u32 subtype_filter[8];
+ };
+
+Where:
+
+ * ``type`` is the event type to filter for and should be something like
+ "WATCH_TYPE_MOUNT_NOTIFY"
+
+ * ``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.
+
+ * ``subtype_filter`` is a bitmask indicating the subtypes that are of
+ interest. Bit 0 of subtype_filter[0] corresponds to subtype 0, bit 1 to
+ subtype 1, and so on.
+
+If the argument to the ioctl() is NULL, then the filters will be removed and
+all events from the watched sources will come through.
+
+
+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 6a0365b2332c..19668c0ebe03 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -4,6 +4,19 @@
menu "Misc devices"
+config WATCH_QUEUE
+ bool "Mappable notification queue"
+ default n
+ depends on MMU
+ 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.
+
+ Note that in theory this should work fine with NOMMU, but I'm not
+ sure how to make that work.
+
config SENSORS_LIS3LV02D
tristate
depends on INPUT
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index b9affcdaa3d6..bf16acd9f8cc 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..174bbd79b6cc
--- /dev/null
+++ b/drivers/misc/watch_queue.c
@@ -0,0 +1,889 @@
+// SPDX-License-Identifier: GPL-2.0
+/* User-mappable watch queue
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * See Documentation/watch_queue.rst
+ */
+
+#define pr_fmt(fmt) "watchq: " fmt
+#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/security.h>
+#include <linux/cred.h>
+#include <linux/watch_queue.h>
+
+#undef 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;
+ const struct cred *cred; /* Creds of the owner of the queue */
+ struct watch_filter __rcu *filter;
+ wait_queue_head_t waiters;
+ struct hlist_head watches; /* Contributory watches */
+ struct kref usage; /* Object usage count */
+ 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.
+ * @cred: The credentials to use in security checks.
+ *
+ * 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,
+ const struct cred *cred)
+{
+ 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_bh(&wqueue->lock); /* Protect head pointer */
+
+ if (wqueue->defunct ||
+ security_post_notification(wqueue->cred, cred, n) < 0)
+ 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_bh(&wqueue->lock);
+ if (used == 0)
+ wake_up(&wqueue->waiters);
+ return true;
+
+overrun:
+ wqueue->flag_next = WATCH_INFO_OVERRUN;
+out:
+ spin_unlock_bh(&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.
+ * @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
+ * 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,
+ const struct cred *cred,
+ 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, cred);
+ }
+
+ rcu_read_unlock();
+}
+EXPORT_SYMBOL(__post_watch_notification);
+
+/*
+ * Allow the queue to be polled.
+ */
+static __poll_t 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 head, tail;
+ __poll_t mask = 0;
+
+ if (!buf)
+ return EPOLLERR;
+
+ poll_wait(file, &wqueue->waiters, wait);
+
+ head = READ_ONCE(buf->meta.head);
+ tail = READ_ONCE(buf->meta.tail);
+ if (head != tail)
+ mask |= EPOLLIN | EPOLLRDNORM;
+ if (head - tail > wqueue->size)
+ mask |= EPOLLERR;
+ 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 vm_fault_t 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)) {
+ vm_fault_t 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;
+ struct inode *inode = file_inode(file);
+ u8 nr_pages;
+
+ inode_lock(inode);
+ nr_pages = wqueue->nr_pages;
+ inode_unlock(inode);
+
+ if (nr_pages == 0 ||
+ vma->vm_pgoff != 0 ||
+ vma->vm_end - vma->vm_start > nr_pages * PAGE_SIZE ||
+ !(pgprot_val(vma->vm_page_prot) & pgprot_val(PAGE_SHARED)))
+ return -EINVAL;
+
+ vma->vm_flags |= VM_DONTEXPAND;
+ 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 (wqueue->buffer)
+ return -EBUSY;
+
+ 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.mask = wqueue->size - 1;
+ buf->meta.head = len;
+ buf->meta.tail = 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 inode *inode,
+ 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 *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(struct_size(wfilter, filters, nr_filter), 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:
+ inode_lock(inode);
+ rcu_swap_protected(wqueue->filter, wfilter,
+ lockdep_is_held(&inode->i_rwsem));
+ inode_unlock(inode);
+ if (wfilter)
+ 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;
+ struct inode *inode = file_inode(file);
+ long ret;
+
+ switch (cmd) {
+ case IOC_WATCH_QUEUE_SET_SIZE:
+ inode_lock(inode);
+ ret = watch_queue_set_size(wqueue, arg);
+ inode_unlock(inode);
+ return ret;
+
+ case IOC_WATCH_QUEUE_SET_FILTER:
+ ret = watch_queue_set_filter(
+ inode, wqueue,
+ (struct watch_notification_filter __user *)arg);
+ return ret;
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+/*
+ * 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);
+
+ kref_init(&wqueue->usage);
+ spin_lock_init(&wqueue->lock);
+ init_waitqueue_head(&wqueue->waiters);
+ wqueue->cred = get_cred(file->f_cred);
+
+ file->private_data = wqueue;
+ return 0;
+}
+
+static void __put_watch_queue(struct kref *kref)
+{
+ struct watch_queue *wqueue =
+ container_of(kref, struct watch_queue, usage);
+
+ kfree_rcu(wqueue, rcu);
+}
+
+/**
+ * put_watch_queue - Dispose of a ref on a watchqueue.
+ * @wqueue: The watch queue to unref.
+ */
+void put_watch_queue(struct watch_queue *wqueue)
+{
+ kref_put(&wqueue->usage, __put_watch_queue);
+}
+EXPORT_SYMBOL(put_watch_queue);
+
+static void free_watch(struct rcu_head *rcu)
+{
+ struct watch *watch = container_of(rcu, struct watch, rcu);
+
+ put_watch_queue(rcu_access_pointer(watch->queue));
+}
+
+static void __put_watch(struct kref *kref)
+{
+ struct watch *watch = container_of(kref, struct watch, usage);
+
+ call_rcu(&watch->rcu, free_watch);
+}
+
+/*
+ * Discard a watch.
+ */
+static void put_watch(struct watch *watch)
+{
+ kref_put(&watch->usage, __put_watch);
+}
+
+/**
+ * init_watch_queue - Initialise a watch
+ * @watch: The watch to initialise.
+ * @wqueue: The queue to assign.
+ *
+ * Initialise a watch and set the watch queue.
+ */
+void init_watch(struct watch *watch, struct watch_queue *wqueue)
+{
+ kref_init(&watch->usage);
+ INIT_HLIST_NODE(&watch->list_node);
+ INIT_HLIST_NODE(&watch->queue_node);
+ rcu_assign_pointer(watch->queue, wqueue);
+}
+
+/**
+ * add_watch_to_object - Add a watch on an object to a watch list
+ * @watch: The watch to add
+ * @wlist: The watch list to add to
+ *
+ * @watch->queue 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_list *wlist)
+{
+ struct watch_queue *wqueue = rcu_access_pointer(watch->queue);
+ struct watch *w;
+
+ hlist_for_each_entry(w, &wlist->watchers, list_node) {
+ if (watch->id == w->id)
+ return -EBUSY;
+ }
+
+ rcu_assign_pointer(watch->watch_list, wlist);
+
+ spin_lock_bh(&wqueue->lock);
+ kref_get(&wqueue->usage);
+ hlist_add_head(&watch->queue_node, &wqueue->watches);
+ spin_unlock_bh(&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 && rcu_access_pointer(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);
+
+ /* We now own the reference on watch that used to belong to wlist. */
+
+ n.type = WATCH_TYPE_META;
+ n.subtype = WATCH_META_REMOVAL_NOTIFICATION;
+ n.info = watch->info_id | sizeof(n);
+
+ wqueue = rcu_dereference(watch->queue);
+
+ /* We don't need the watch list lock for the next bit as RCU is
+ * protecting *wqueue from deallocation.
+ */
+ if (wqueue) {
+ post_one_notification(wqueue, &n, wq ? wq->cred : NULL);
+
+ spin_lock_bh(&wqueue->lock);
+
+ if (!hlist_unhashed(&watch->queue_node)) {
+ hlist_del_init_rcu(&watch->queue_node);
+ put_watch(watch);
+ }
+
+ spin_unlock_bh(&wqueue->lock);
+ }
+
+ if (wlist->release_watch) {
+ void (*release_watch)(struct watch *);
+
+ release_watch = wlist->release_watch;
+ rcu_read_unlock();
+ (*release_watch)(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 has the
+ * potential to race with removal of the watches by the destruction of the
+ * objects being watched or with 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_bh(&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);
+ /* We now own a ref on the watch. */
+ spin_unlock_bh(&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) {
+ void (*release_watch)(struct watch *);
+
+ 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);
+
+ /* We now own a second ref on the watch. */
+ }
+
+ release_watch = wlist->release_watch;
+ spin_unlock(&wlist->lock);
+
+ if (release) {
+ if (release_watch) {
+ rcu_read_unlock();
+ /* This might need to call dput(), so
+ * we have to drop all the locks.
+ */
+ (*release_watch)(watch);
+ rcu_read_lock();
+ }
+ put_watch(watch);
+ }
+ }
+
+ put_watch(watch);
+ spin_lock_bh(&wqueue->lock);
+ }
+
+ spin_unlock_bh(&wqueue->lock);
+ rcu_read_unlock();
+}
+
+/*
+ * Release the file.
+ */
+static int watch_queue_release(struct inode *inode, struct file *file)
+{
+ struct watch_filter *wfilter;
+ struct watch_queue *wqueue = file->private_data;
+ int i;
+
+ watch_queue_clear(wqueue);
+
+ if (wqueue->buffer)
+ vunmap(wqueue->buffer);
+
+ for (i = 0; i < wqueue->nr_pages; i++) {
+ ClearPageUptodate(wqueue->pages[i]);
+ wqueue->pages[i]->mapping = NULL;
+ __free_page(wqueue->pages[i]);
+ }
+
+ wfilter = rcu_dereference_protected(wqueue->filter, true);
+ if (wfilter)
+ kfree_rcu(wfilter, rcu);
+ kfree(wqueue->pages);
+ put_cred(wqueue->cred);
+ 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, file->f_cred))
+ 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;
+ kref_get(&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,
+};
+builtin_misc_device(watch_queue_dev);
diff --git a/include/linux/watch_queue.h b/include/linux/watch_queue.h
new file mode 100644
index 000000000000..91777119db5e
--- /dev/null
+++ b/include/linux/watch_queue.h
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0
+/* User-mappable watch queue
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * See Documentation/watch_queue.rst
+ */
+
+#ifndef _LINUX_WATCH_QUEUE_H
+#define _LINUX_WATCH_QUEUE_H
+
+#include <uapi/linux/watch_queue.h>
+#include <linux/kref.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 */
+ struct kref usage; /* Object usage count */
+};
+
+/*
+ * List of watches on an object.
+ */
+struct watch_list {
+ struct rcu_head rcu;
+ struct hlist_head watchers;
+ void (*release_watch)(struct watch *);
+ spinlock_t lock;
+};
+
+extern void __post_watch_notification(struct watch_list *,
+ 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 init_watch(struct watch *, struct watch_queue *);
+extern int add_watch_to_object(struct watch *, struct watch_list *);
+extern int remove_watch_from_object(struct watch_list *, struct watch_queue *, u64, bool);
+
+static inline void init_watch_list(struct watch_list *wlist,
+ void (*release_watch)(struct watch *))
+{
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+ wlist->release_watch = release_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, cred, 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
index c3a88fa5f62a..0e3e5672aa09 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -3,6 +3,10 @@
#define _UAPI_LINUX_WATCH_QUEUE_H
#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define IOC_WATCH_QUEUE_SET_SIZE _IO('W', 0x60) /* Set the size in pages */
+#define IOC_WATCH_QUEUE_SET_FILTER _IO('W', 0x61) /* Set the filter */
enum watch_notification_type {
WATCH_TYPE_META = 0, /* Special record */
@@ -60,4 +64,20 @@ struct watch_queue_buffer {
};
};
+/*
+ * 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 */
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);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_MOUNT_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
watch_mount(AT_FDCWD, "/", 0, fd, 0x02);
In this case, it would let me monitor the mount topology subtree rooted at
"/" for events. 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.
After setting the watch, records will be placed into the queue when, for
example, as superblock switches between read-write and read-only. Records
are of the following format:
struct mount_notification {
struct watch_notification watch;
__u32 triggered_on;
__u32 changed_mount;
} *n;
Where:
n->watch.type will be WATCH_TYPE_MOUNT_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_MOUNT_NEW_MOUNT.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the fifth argument to
watch_mount(), shifted.
n->watch.info & WATCH_INFO_FLAG_0 will be used for
NOTIFY_MOUNT_READONLY, being set if the superblock becomes R/O, and
being cleared otherwise, and for NOTIFY_MOUNT_NEW_MOUNT, being set
if the new mount is a submount (e.g. an automount).
n->triggered_on indicates the ID of the mount on which the watch
was installed.
n->changed_mount indicates the ID of the mount that was affected.
The mount IDs can be retrieved with the fsinfo() syscall, using the
fsinfo_mount_info and fsinfo_mount_child attributes. There are
notification counters there too for when a buffer overrun occurs, thereby
allowing the mount tree to be quickly rescanned.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also that
the queue can be shared between multiple notifications of various types.
Signed-off-by: David Howells <[email protected]>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 9 ++
fs/Makefile | 1
fs/mount.h | 33 ++++--
fs/mount_notify.c | 187 ++++++++++++++++++++++++++++++++
fs/namespace.c | 9 +-
include/linux/dcache.h | 1
include/linux/syscalls.h | 2
include/uapi/linux/watch_queue.h | 24 ++++
kernel/sys_ni.c | 3 +
11 files changed, 257 insertions(+), 14 deletions(-)
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 03decae51513..91164735efe7 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -439,3 +439,4 @@
432 i386 fsmount sys_fsmount __ia32_sys_fsmount
433 i386 fspick sys_fspick __ia32_sys_fspick
434 i386 fsinfo sys_fsinfo __ia32_sys_fsinfo
+435 i386 watch_mount sys_watch_mount __ia32_sys_watch_mount
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index ea63df9a1020..858aff7a1239 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -356,6 +356,7 @@
432 common fsmount __x64_sys_fsmount
433 common fspick __x64_sys_fspick
434 common fsinfo __x64_sys_fsinfo
+435 common watch_mount __x64_sys_watch_mount
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/Kconfig b/fs/Kconfig
index 9e7d2f2c0111..a26bbe27a791 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -121,6 +121,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 26eaeae4b9a1..c6a71daf2464 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -131,3 +131,4 @@ obj-$(CONFIG_F2FS_FS) += f2fs/
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/mount.h b/fs/mount.h
index 47795802f78e..a95b805d00d8 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
atomic_t mnt_notify_counter; /* Number of notifications generated */
} __randomize_layout;
@@ -153,18 +158,8 @@ static inline bool is_anon_ns(struct mnt_namespace *ns)
return ns->seq == 0;
}
-/*
- * 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) */
-};
+extern void post_mount_notification(struct mount *changed,
+ struct mount_notification *notify);
static inline void notify_mount(struct mount *changed,
struct mount *aux,
@@ -172,4 +167,18 @@ static inline void notify_mount(struct mount *changed,
u32 info_flags)
{
atomic_inc(&changed->mnt_notify_counter);
+
+#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..2ba81f17cc20
--- /dev/null
+++ b/fs/mount_notify.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Provide mount topology/attribute change notifications.
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/syscalls.h>
+#include <linux/slab.h>
+#include <linux/security.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)
+{
+ const struct cred *cred = current_cred();
+ 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,
+ ¬ify->watch, cred,
+ (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)
+ break;
+
+ cursor.dentry = READ_ONCE(mnt->mnt_mountpoint);
+ mnt = parent;
+ cursor.mnt = &mnt->mnt;
+ } else {
+ 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 *watch)
+{
+ struct vfsmount *mnt = watch->private;
+ struct dentry *dentry = (struct dentry *)(unsigned long)watch->id;
+
+ dput(dentry);
+ mntput(mnt);
+}
+
+/**
+ * sys_watch_mount - 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(watch_mount,
+ 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;
+ unsigned int lookup_flags =
+ LOOKUP_DIRECTORY | LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
+ int ret;
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+ if ((at_flags & ~(AT_NO_AUTOMOUNT | AT_EMPTY_PATH)) != 0)
+ return -EINVAL;
+ if (at_flags & AT_NO_AUTOMOUNT)
+ lookup_flags &= ~LOOKUP_AUTOMOUNT;
+ if (at_flags & AT_EMPTY_PATH)
+ lookup_flags |= LOOKUP_EMPTY;
+
+ ret = user_path_at(dfd, filename, lookup_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) {
+ ret = -ENOMEM;
+ if (!m->mnt_watchers) {
+ wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+ if (!wlist)
+ goto err_wqueue;
+ init_watch_list(wlist, release_mount_watch);
+ }
+
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wlist;
+
+ init_watch(watch, wqueue);
+ watch->id = (unsigned long)path.dentry;
+ watch->private = path.mnt;
+ watch->info_id = (u32)watch_id << 24;
+
+ ret = security_watch_mount(watch, &path);
+ if (ret < 0)
+ goto err_watch;
+
+ down_write(&m->mnt.mnt_sb->s_umount);
+ if (!m->mnt_watchers) {
+ m->mnt_watchers = wlist;
+ wlist = NULL;
+ }
+
+ ret = add_watch_to_object(watch, m->mnt_watchers);
+ 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);
+ watch = NULL;
+ }
+ up_write(&m->mnt.mnt_sb->s_umount);
+ } else {
+ ret = -EBADSLT;
+ 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);
+ }
+ }
+
+err_watch:
+ kfree(watch);
+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 2ec7d9d1905a..b4758486ee33 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -515,7 +515,8 @@ static int mnt_make_readonly(struct mount *mnt)
mnt->mnt.mnt_flags &= ~MNT_WRITE_HOLD;
unlock_mount_hash();
if (ret == 0)
- notify_mount(mnt, NULL, NOTIFY_MOUNT_READONLY, 0x10000);
+ notify_mount(mnt, NULL, NOTIFY_MOUNT_READONLY,
+ WATCH_INFO_FLAG_0);
return ret;
}
@@ -1478,6 +1479,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
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--;
@@ -2115,7 +2120,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,
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 ?
- 0x10000 : 0);
+ WATCH_INFO_FLAG_0 : 0);
commit_tree(source_mnt);
}
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 361305ddd75e..5db8e244d9a0 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_NORCU 0x40000000 /* No RCU delay for freeing */
+#define DCACHE_MOUNT_WATCH 0x80000000 /* There's a mount watch here */
extern seqlock_t rename_lock;
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 217d25b62b4f..f2e7f18b323a 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1001,6 +1001,8 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,
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_watch_mount(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 32177bcf85f1..cf3c0c03a747 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -105,4 +105,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 d1d9d76cae1e..97b025e7863c 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -88,6 +88,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 */
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);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_KEY_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01);
After that, records will be placed into the queue when events occur in
which keys are changed in some way. Records are of the following format:
struct key_notification {
struct watch_notification watch;
__u32 key_id;
__u32 aux;
} *n;
Where:
n->watch.type will be WATCH_TYPE_KEY_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_KEY_REVOKED.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the second argument to
keyctl_watch_key(), shifted.
n->key will be the ID of the affected key.
n->aux will hold subtype-dependent information, such as the key
being linked into the keyring specified by n->key in the case of
NOTIFY_KEY_LINKED.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also that
the queue can be shared between multiple notifications of various types.
Signed-off-by: David Howells <[email protected]>
---
Documentation/security/keys/core.rst | 58 +++++++++++++++++++++
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 | 2 +
security/keys/gc.c | 5 ++
security/keys/internal.h | 30 ++++++++++-
security/keys/key.c | 37 ++++++++-----
security/keys/keyctl.c | 95 +++++++++++++++++++++++++++++++++-
security/keys/keyring.c | 17 ++++--
security/keys/request_key.c | 4 +
12 files changed, 264 insertions(+), 24 deletions(-)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 9521c4207f01..05ef58c753f3 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,
@@ -1001,6 +1002,63 @@ The keyctl syscall functions are:
written into the output buffer. Verification returns 0 on success.
+ * 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 7099985e35a9..f1c43852c0c6 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 f45ee0f69c0c..e9e7da849619 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -67,6 +67,7 @@
#define KEYCTL_PKEY_SIGN 27 /* Create a public key signature */
#define KEYCTL_PKEY_VERIFY 28 /* Verify a public key signature */
#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 0e3e5672aa09..32177bcf85f1 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -80,4 +80,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 9482df601dc3..021d8e1c9233 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -158,6 +158,8 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
case KEYCTL_PKEY_VERIFY:
return keyctl_pkey_verify(compat_ptr(arg2), compat_ptr(arg3),
compat_ptr(arg4), compat_ptr(arg5));
+ 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 634e96b380e8..b685b9a85a9e 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -135,6 +135,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 8f533c81aa8d..a7ac0f823ade 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);
@@ -178,6 +180,23 @@ 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, current_cred(),
+ n.key_id);
+#endif
+}
+
/*
* Check to see whether permission is granted to use a key in the desired way.
*/
@@ -324,6 +343,15 @@ static inline long keyctl_pkey_e_d_s(int op,
}
#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 696f1c092c50..9d9f94992470 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -412,6 +412,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);
}
/*
@@ -454,7 +455,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 */
@@ -603,7 +604,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)
@@ -756,9 +757,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);
@@ -999,9 +1002,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);
@@ -1033,15 +1038,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);
@@ -1063,8 +1070,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 3e4053a217c3..f3b71efd76c5 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,90 @@ 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;
+
+ 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) {
+ ret = -ENOMEM;
+ if (!key->watchers) {
+ wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+ if (!wlist)
+ goto err_wqueue;
+ init_watch_list(wlist, NULL);
+ }
+
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wlist;
+
+ init_watch(watch, wqueue);
+ watch->id = key->serial;
+ watch->info_id = (u32)watch_id << 24;
+
+ ret = security_watch_key(watch, key);
+ if (ret < 0)
+ goto err_watch;
+
+ down_write(&key->sem);
+ if (!key->watchers) {
+ key->watchers = wlist;
+ wlist = NULL;
+ }
+
+ ret = add_watch_to_object(watch, key->watchers);
+ up_write(&key->sem);
+
+ if (ret == 0)
+ watch = NULL;
+ } else {
+ ret = -EBADSLT;
+ if (key->watchers) {
+ down_write(&key->sem);
+ ret = remove_watch_from_object(key->watchers,
+ wqueue, key_serial(key),
+ false);
+ up_write(&key->sem);
+ }
+ }
+
+err_watch:
+ kfree(watch);
+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
*/
@@ -1771,6 +1859,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
(const void __user *)arg4,
(const void __user *)arg5);
+ 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 e14f09e3a4b0..f0f9ab3c5587 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -1018,12 +1018,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);
@@ -1286,12 +1288,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));
}
/*
@@ -1369,7 +1373,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);
}
@@ -1398,6 +1402,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);
@@ -1419,6 +1424,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;
@@ -1452,6 +1458,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 75d87f9e0f49..5f474d0e8620 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -387,7 +387,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)
@@ -406,7 +406,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;
Add UAPI definitions for the general notification ring, including the
following pieces:
(1) struct watch_notification.
This is the metadata header for each entry in the ring. It includes a
type and subtype that indicate the source of the message
(eg. WATCH_TYPE_MOUNT_NOTIFY) and the kind of the message
(eg. NOTIFY_MOUNT_NEW_MOUNT).
The header also contains an information field that conveys the
following information:
- WATCH_INFO_LENGTH. The size of the entry (entries are variable
length).
- WATCH_INFO_OVERRUN. If preceding messages were lost due to ring
overrun or lack of memory.
- WATCH_INFO_ENOMEM. If preceding messages were lost due to lack
of memory.
- WATCH_INFO_RECURSIVE. If the event detected was applied to
multiple objects (eg. a recursive change to mount attributes).
- WATCH_INFO_IN_SUBTREE. If the event didn't happen at the watched
object, but rather to some related object (eg. a subtree mount
watch saw a mount happen somewhere within the subtree).
- WATCH_INFO_TYPE_FLAGS. Eight flags whose meanings depend on the
message type.
- WATCH_INFO_ID. The watch ID specified when the watchpoint was
set.
All the information in the header can be used in filtering messages at
the point of writing into the buffer.
(2) struct watch_queue_buffer.
This describes the layout of the ring. Note that the first slots in
the ring contain a special metadata entry that contains the ring
pointers. The producer in the kernel knows to skip this and it has a
proper header (WATCH_TYPE_META, WATCH_META_SKIP_NOTIFICATION) that
indicates the size so that the ring consumer can handle it the same as
any other record and just skip it.
Note that this means that ring entries can never be split over the end
of the ring, so if an entry would need to be split, a skip record is
inserted to wrap the ring first; this is also WATCH_TYPE_META,
WATCH_META_SKIP_NOTIFICATION.
Signed-off-by: David Howells <[email protected]>
---
include/uapi/linux/watch_queue.h | 63 ++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
create mode 100644 include/uapi/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..c3a88fa5f62a
--- /dev/null
+++ b/include/uapi/linux/watch_queue.h
@@ -0,0 +1,63 @@
+/* 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>
+
+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 */
+ WATCH_TYPE_BLOCK_NOTIFY = 4, /* Block layer notifications */
+#define WATCH_TYPE___NR 5
+};
+
+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_INFO_ID__SHIFT 24
+};
+
+#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_META */
+ __u32 head; /* Ring head index */
+ __u32 tail; /* Ring tail index */
+ __u32 mask; /* Ring index mask */
+ } meta;
+ struct watch_notification slots[0];
+ };
+};
+
+#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
Add a security hook that allows an LSM to rule on whether a notification
message is allowed to be inserted into a particular watch queue.
The hook is given the following information:
(1) The credentials of the triggerer (which may be init_cred for a system
notification, eg. a hardware error).
(2) The credentials of the creator of the watch buffer.
(3) The notification message.
Signed-off-by: David Howells <[email protected]>
cc: Casey Schaufler <[email protected]>
cc: Stephen Smalley <[email protected]>
cc: [email protected]
---
include/linux/lsm_hooks.h | 10 ++++++++++
include/linux/security.h | 10 ++++++++++
security/security.c | 6 ++++++
3 files changed, 26 insertions(+)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 0543449c0f6e..94fb6067fed5 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1445,6 +1445,12 @@
* from devices (as a global set).
* @watch: The watch object
*
+ * @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.
@@ -1729,6 +1735,9 @@ union security_list_options {
int (*watch_sb)(struct watch *watch, struct super_block *sb);
int (*watch_key)(struct watch *watch, struct key *key);
int (*watch_devices)(struct watch *watch);
+ int (*post_notification)(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n);
#endif /* CONFIG_WATCH_QUEUE */
#ifdef CONFIG_SECURITY_NETWORK
@@ -2014,6 +2023,7 @@ struct security_hook_heads {
struct hlist_head watch_sb;
struct hlist_head watch_key;
struct hlist_head watch_devices;
+ struct hlist_head post_notification;
#endif /* CONFIG_WATCH_QUEUE */
#ifdef CONFIG_SECURITY_NETWORK
struct hlist_head unix_stream_connect;
diff --git a/include/linux/security.h b/include/linux/security.h
index ac3bf6b5f87b..ed301edeaa19 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -59,6 +59,7 @@ struct fs_parameter;
enum fs_value_type;
struct fsinfo_kparams;
struct watch;
+struct watch_notification;
/* Default (no) options for the capable function */
#define CAP_OPT_NONE 0x0
@@ -402,6 +403,9 @@ int security_watch_mount(struct watch *watch, struct path *path);
int security_watch_sb(struct watch *watch, struct super_block *sb);
int security_watch_key(struct watch *watch, struct key *key);
int security_watch_devices(struct watch *watch);
+int security_post_notification(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n);
#endif /* CONFIG_WATCH_QUEUE */
#else /* CONFIG_SECURITY */
@@ -1239,6 +1243,12 @@ static inline int security_watch_devices(struct watch *watch)
{
return 0;
}
+static inline int security_post_notification(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n)
+{
+ return 0;
+}
#endif /* CONFIG_WATCH_QUEUE */
#endif /* CONFIG_SECURITY */
diff --git a/security/security.c b/security/security.c
index ee2f8539a0d5..8a04ac74b4fe 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1950,6 +1950,12 @@ int security_watch_devices(struct watch *watch)
return call_int_hook(watch_devices, 0, watch);
}
+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 /* CONFIG_WATCH_QUEUE */
#ifdef CONFIG_SECURITY_NETWORK
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
Other events may be produced, such as with a failing disk:
ptrs h=5 t=2 m=6000004
NOTIFY[00000005-00000002] ty=0004 sy=0006 i=04000018
BLOCK 00800050 e=6[critical medium] s=5be8
This corresponds to:
print_req_error: critical medium error, dev sdf, sector 23528 flags 0
in dmesg.
Signed-off-by: David Howells <[email protected]>
---
samples/Kconfig | 6 +
samples/Makefile | 1
samples/watch_queue/Makefile | 9 +
samples/watch_queue/watch_test.c | 308 ++++++++++++++++++++++++++++++++++++++
4 files changed, 324 insertions(+)
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 0561a94f6fdb..a2b7a7babee5 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -160,4 +160,10 @@ config SAMPLE_VFS
as mount API and statx(). Note that this is restricted to the x86
arch whilst it accesses system calls that aren't yet in all arches.
+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 debf8925f06f..ed3b8bab6e9b 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -20,3 +20,4 @@ obj-$(CONFIG_SAMPLE_TRACE_PRINTK) += trace_printk/
obj-$(CONFIG_VIDEO_PCI_SKELETON) += v4l/
obj-y += vfio-mdev/
subdir-$(CONFIG_SAMPLE_VFS) += vfs
+subdir-$(CONFIG_SAMPLE_WATCH_QUEUE) += watch_queue
diff --git a/samples/watch_queue/Makefile b/samples/watch_queue/Makefile
new file mode 100644
index 000000000000..42b694430d0f
--- /dev/null
+++ b/samples/watch_queue/Makefile
@@ -0,0 +1,9 @@
+# List of programs to build
+hostprogs-y := 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..73e70e7dfc36
--- /dev/null
+++ b/samples/watch_queue/watch_test.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Use /dev/watch_queue to watch for notifications.
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#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>
+
+#ifndef __NR_watch_mount
+#define __NR_watch_mount -1
+#endif
+#ifndef __NR_watch_sb
+#define __NR_watch_sb -1
+#endif
+#ifndef __NR_watch_devices
+#define __NR_watch_devices -1
+#endif
+#ifndef KEYCTL_WATCH_KEY
+#define KEYCTL_WATCH_KEY -1
+#endif
+
+#define BUF_SIZE 4
+
+static const char *key_subtypes[256] = {
+ [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[256] = {
+ [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[256] = {
+ [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]);
+}
+
+static const char *block_subtypes[256] = {
+ [NOTIFY_BLOCK_ERROR_TIMEOUT] = "timeout",
+ [NOTIFY_BLOCK_ERROR_NO_SPACE] = "critical space allocation",
+ [NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT] = "recoverable transport",
+ [NOTIFY_BLOCK_ERROR_CRITICAL_TARGET] = "critical target",
+ [NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS] = "critical nexus",
+ [NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM] = "critical medium",
+ [NOTIFY_BLOCK_ERROR_PROTECTION] = "protection",
+ [NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE] = "kernel resource",
+ [NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE] = "device resource",
+ [NOTIFY_BLOCK_ERROR_IO] = "I/O",
+};
+
+static void saw_block_change(struct watch_notification *n)
+{
+ struct block_notification *b = (struct block_notification *)n;
+ unsigned int len = n->info & WATCH_INFO_LENGTH;
+
+ if (len < sizeof(struct block_notification))
+ return;
+
+ printf("BLOCK %08llx e=%u[%s] s=%llx\n",
+ (unsigned long long)b->dev,
+ n->subtype, block_subtypes[n->subtype],
+ (unsigned long long)b->sector);
+}
+
+static const char *usb_subtypes[256] = {
+ [NOTIFY_USB_DEVICE_ADD] = "dev-add",
+ [NOTIFY_USB_DEVICE_REMOVE] = "dev-remove",
+ [NOTIFY_USB_BUS_ADD] = "bus-add",
+ [NOTIFY_USB_BUS_REMOVE] = "bus-remove",
+ [NOTIFY_USB_DEVICE_RESET] = "dev-reset",
+ [NOTIFY_USB_DEVICE_ERROR] = "dev-error",
+};
+
+static void saw_usb_event(struct watch_notification *n)
+{
+ struct usb_notification *u = (struct usb_notification *)n;
+ unsigned int len = n->info & WATCH_INFO_LENGTH;
+
+ if (len < sizeof(struct usb_notification))
+ return;
+
+ printf("USB %*.*s %s e=%x r=%x\n",
+ u->name_len, u->name_len, u->name,
+ usb_subtypes[n->subtype],
+ u->error, u->reserved);
+}
+
+/*
+ * 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 = __atomic_load_n(&buf->meta.head, __ATOMIC_ACQUIRE),
+ tail = buf->meta.tail,
+ tail != head
+ ) {
+ 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;
+ case WATCH_TYPE_BLOCK_NOTIFY:
+ saw_block_change(n);
+ break;
+ case WATCH_TYPE_USB_NOTIFY:
+ saw_usb_event(n);
+ break;
+ }
+
+ tail += (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+ __atomic_store_n(&buf->meta.tail, tail, __ATOMIC_RELEASE);
+ }
+ }
+
+out:
+ return 0;
+}
+
+static struct watch_notification_filter filter = {
+ .nr_filters = 5,
+ .__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,
+ },
+ [3] = {
+ .type = WATCH_TYPE_BLOCK_NOTIFY,
+ .subtype_filter[0] = UINT_MAX,
+ },
+ [4] = {
+ .type = WATCH_TYPE_USB_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_watch_mount, AT_FDCWD, "/", 0, fd, 0x02) == -1) {
+ perror("watch_mount");
+ exit(1);
+ }
+
+ if (syscall(__NR_watch_sb, AT_FDCWD, "/mnt", 0, fd, 0x03) == -1) {
+ perror("watch_sb");
+ exit(1);
+ }
+
+ if (syscall(__NR_watch_devices, fd, 0x04) == -1) {
+ perror("watch_devices");
+ exit(1);
+ }
+
+ return consumer(fd, buf);
+}
Add a USB subsystem notification mechanism whereby notifications about
hardware events such as device connection, disconnection, reset and I/O
errors, can be reported to a monitoring process asynchronously.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report USB notifications via that
queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_USB_NOTIFY,
.subtype_filter[0] = UINT_MAX;
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
notify_devices(fd, 12);
After that, records will be placed into the queue when events occur on a
USB device or bus. Records are of the following format:
struct usb_notification {
struct watch_notification watch;
__u32 error;
__u32 reserved;
__u8 name_len;
__u8 name[0];
} *n;
Where:
n->watch.type will be WATCH_TYPE_USB_NOTIFY
n->watch.subtype will be the type of notification, such as
NOTIFY_USB_DEVICE_ADD.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the second argument to
device_notify(), shifted.
n->error and n->reserved are intended to convey information such as
error codes, but are currently not used
n->name_len and n->name convey the USB device name as an
unterminated string. This may be truncated - it is currently
limited to a maximum 63 chars.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype.
Signed-off-by: David Howells <[email protected]>
cc: Greg Kroah-Hartman <[email protected]>
cc: [email protected]
---
Documentation/watch_queue.rst | 9 ++++++
drivers/usb/core/Kconfig | 10 +++++++
drivers/usb/core/devio.c | 55 ++++++++++++++++++++++++++++++++++++++
drivers/usb/core/hub.c | 3 ++
include/linux/usb.h | 19 +++++++++++++
include/uapi/linux/watch_queue.h | 30 ++++++++++++++++++++-
6 files changed, 125 insertions(+), 1 deletion(-)
diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
index c2954e191989..7ce5d4147fa9 100644
--- a/Documentation/watch_queue.rst
+++ b/Documentation/watch_queue.rst
@@ -15,6 +15,8 @@ receive notifications from the kernel. This can be used in conjunction with::
* Block layer event notifications
+ * USB subsystem event notifications
+
The notifications buffers can be enabled by:
@@ -344,6 +346,13 @@ Any particular buffer can be fed from multiple sources. Sources include:
or temporary link loss. Watchpoints of this type are set on the global
device watch list.
+ * WATCH_TYPE_USB_NOTIFY
+
+ Notifications of this type indicate USB subsystem events, such as
+ attachment, removal, reset and I/O errors. Separate events are generated
+ for buses and devices. Watchpoints of this type are set on the global
+ device watch list.
+
Event Filtering
===============
diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig
index bdb6bd0b63a6..4be88368ab6b 100644
--- a/drivers/usb/core/Kconfig
+++ b/drivers/usb/core/Kconfig
@@ -103,3 +103,13 @@ config USB_AUTOSUSPEND_DELAY
The default value Linux has always had is 2 seconds. Change
this value if you want a different delay and cannot modify
the command line or module parameter.
+
+config USB_NOTIFICATIONS
+ bool "Provide USB hardware event notifications"
+ depends on USB
+ select DEVICE_NOTIFICATIONS
+ help
+ This option provides support for getting hardware event notifications
+ on USB devices and interfaces. This makes use of the
+ /dev/watch_queue misc device to handle the notification buffer.
+ device_notify(2) is used to set/remove watches.
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index fa783531ee88..af7f339c35c5 100644
--- a/drivers/usb/core/devio.c
+++ b/drivers/usb/core/devio.c
@@ -41,6 +41,7 @@
#include <linux/dma-mapping.h>
#include <asm/byteorder.h>
#include <linux/moduleparam.h>
+#include <linux/watch_queue.h>
#include "usb.h"
@@ -2633,13 +2634,67 @@ static void usbdev_remove(struct usb_device *udev)
}
}
+#ifdef CONFIG_USB_NOTIFICATIONS
+static noinline void post_usb_notification(const char *devname,
+ enum usb_notification_type subtype,
+ u32 error)
+{
+ unsigned int name_len, n_len;
+ u64 id = 0; /* Might want to put a dev# here. */
+
+ struct {
+ struct usb_notification n;
+ char more_name[USB_NOTIFICATION_MAX_NAME_LEN -
+ (sizeof(struct usb_notification) -
+ offsetof(struct usb_notification, name))];
+ } n;
+
+ name_len = strlen(devname);
+ name_len = min_t(size_t, name_len, USB_NOTIFICATION_MAX_NAME_LEN);
+ n_len = round_up(offsetof(struct usb_notification, name) + name_len,
+ sizeof(__u64));
+
+ memset(&n, 0, sizeof(n));
+ memcpy(n.n.name, devname, n_len);
+
+ n.n.watch.type = WATCH_TYPE_USB_NOTIFY;
+ n.n.watch.subtype = subtype;
+ n.n.watch.info = n_len;
+ n.n.error = error;
+ n.n.name_len = name_len;
+
+ post_device_notification(&n.n.watch, id);
+}
+
+void post_usb_device_notification(const struct usb_device *udev,
+ enum usb_notification_type subtype, u32 error)
+{
+ post_usb_notification(dev_name(&udev->dev), subtype, error);
+}
+
+void post_usb_bus_notification(const struct usb_bus *ubus,
+ enum usb_notification_type subtype, u32 error)
+{
+ post_usb_notification(ubus->bus_name, subtype, error);
+}
+#endif
+
static int usbdev_notify(struct notifier_block *self,
unsigned long action, void *dev)
{
switch (action) {
case USB_DEVICE_ADD:
+ post_usb_device_notification(dev, NOTIFY_USB_DEVICE_ADD, 0);
break;
case USB_DEVICE_REMOVE:
+ post_usb_device_notification(dev, NOTIFY_USB_DEVICE_REMOVE, 0);
+ usbdev_remove(dev);
+ break;
+ case USB_BUS_ADD:
+ post_usb_bus_notification(dev, NOTIFY_USB_BUS_ADD, 0);
+ break;
+ case USB_BUS_REMOVE:
+ post_usb_bus_notification(dev, NOTIFY_USB_BUS_REMOVE, 0);
usbdev_remove(dev);
break;
}
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 2f94568ba385..722013d8142c 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -4596,6 +4596,9 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
(udev->config) ? "reset" : "new", speed,
devnum, driver_name);
+ if (udev->config)
+ post_usb_device_notification(udev, NOTIFY_USB_DEVICE_RESET, 0);
+
/* Set up TT records, if needed */
if (hdev->tt) {
udev->tt = hdev->tt;
diff --git a/include/linux/usb.h b/include/linux/usb.h
index ae82d9d1112b..12687b55811d 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -2008,6 +2008,25 @@ extern void usb_led_activity(enum usb_led_event ev);
static inline void usb_led_activity(enum usb_led_event ev) {}
#endif
+/*
+ * Notification functions.
+ */
+#ifdef CONFIG_USB_NOTIFICATIONS
+extern void post_usb_device_notification(const struct usb_device *udev,
+ enum usb_notification_type subtype,
+ u32 error);
+extern void post_usb_bus_notification(const struct usb_bus *ubus,
+ enum usb_notification_type subtype,
+ u32 error);
+#else
+static inline void post_usb_device_notification(const struct usb_device *udev,
+ enum usb_notification_type subtype,
+ u32 error) {}
+static inline void post_usb_bus_notification(const struct usb_bus *ubus,
+ enum usb_notification_type subtype,
+ u32 error) {}
+#endif
+
#endif /* __KERNEL__ */
#endif
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 22e3326b83a6..d596ac5a61e4 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -14,7 +14,8 @@ enum watch_notification_type {
WATCH_TYPE_SB_NOTIFY = 2, /* Superblock notification */
WATCH_TYPE_KEY_NOTIFY = 3, /* Key/keyring change notification */
WATCH_TYPE_BLOCK_NOTIFY = 4, /* Block layer notifications */
-#define WATCH_TYPE___NR 5
+ WATCH_TYPE_USB_NOTIFY = 5, /* USB subsystem notifications */
+#define WATCH_TYPE___NR 6
};
enum watch_meta_notification_subtype {
@@ -182,4 +183,31 @@ struct block_notification {
__u64 sector; /* Affected sector */
};
+/*
+ * Type of USB layer notification.
+ */
+enum usb_notification_type {
+ NOTIFY_USB_DEVICE_ADD = 0, /* USB device added */
+ NOTIFY_USB_DEVICE_REMOVE = 1, /* USB device removed */
+ NOTIFY_USB_BUS_ADD = 2, /* USB bus added */
+ NOTIFY_USB_BUS_REMOVE = 3, /* USB bus removed */
+ NOTIFY_USB_DEVICE_RESET = 4, /* USB device reset */
+ NOTIFY_USB_DEVICE_ERROR = 5, /* USB device error */
+};
+
+/*
+ * USB subsystem notification record.
+ * - watch.type = WATCH_TYPE_USB_NOTIFY
+ * - watch.subtype = enum usb_notification_type
+ */
+struct usb_notification {
+ struct watch_notification watch; /* WATCH_TYPE_USB_NOTIFY */
+ __u32 error;
+ __u32 reserved;
+ __u8 name_len; /* Length of device name */
+ __u8 name[0]; /* Device name (padded to __u64, truncated at 63 chars) */
+};
+
+#define USB_NOTIFICATION_MAX_NAME_LEN 63
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
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. watch_mount() is used for that.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_SB_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
watch_sb(AT_FDCWD, "/home/dhowells", 0, fd, 0x03);
In this case, it would let me monitor my own homedir for events. After
setting the watch, records will be placed into the queue when, for example,
as superblock switches between read-write and read-only. Records are of
the following format:
struct superblock_notification {
struct watch_notification watch;
__u64 sb_id;
} *n;
Where:
n->watch.type will be WATCH_TYPE_SB_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_SUPERBLOCK_READONLY.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the fifth argument to
watch_sb(), shifted.
n->watch.info & WATCH_INFO_FLAG_0 will be used for
NOTIFY_SUPERBLOCK_READONLY, being set if the superblock becomes
R/O, and being cleared otherwise.
n->sb_id will be the ID of the superblock, as can be retrieved with
the fsinfo() syscall, as part of the fsinfo_sb_notifications
attribute in the the watch_id field.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also 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 <[email protected]>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 12 +++
fs/super.c | 121 ++++++++++++++++++++++++++++++++
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, 243 insertions(+)
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index 91164735efe7..ef6035f71db4 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -440,3 +440,4 @@
433 i386 fspick sys_fspick __ia32_sys_fspick
434 i386 fsinfo sys_fsinfo __ia32_sys_fsinfo
435 i386 watch_mount sys_watch_mount __ia32_sys_watch_mount
+436 i386 watch_sb sys_watch_sb __ia32_sys_watch_sb
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 858aff7a1239..072f5dbac6a3 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -357,6 +357,7 @@
433 common fspick __x64_sys_fspick
434 common fsinfo __x64_sys_fsinfo
435 common watch_mount __x64_sys_watch_mount
+436 common watch_sb __x64_sys_watch_sb
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/Kconfig b/fs/Kconfig
index a26bbe27a791..fc0fa4b35f3c 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -130,6 +130,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 61819e8e5469..5bfe409a4fbe 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -36,6 +36,8 @@
#include <linux/lockdep.h>
#include <linux/user_namespace.h>
#include <linux/fs_context.h>
+#include <linux/syscalls.h>
+#include <linux/namei.h>
#include <uapi/linux/mount.h>
#include "internal.h"
@@ -350,6 +352,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);
@@ -990,6 +996,8 @@ int reconfigure_super(struct fs_context *fc)
/* 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
@@ -1808,3 +1816,116 @@ int thaw_super(struct super_block *sb)
return thaw_super_locked(sb);
}
EXPORT_SYMBOL(thaw_super);
+
+#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, current_cred(),
+ s->s_unique_id);
+}
+
+/**
+ * sys_watch_sb - 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(watch_sb,
+ 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;
+ unsigned int lookup_flags =
+ LOOKUP_DIRECTORY | LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
+ int ret;
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+ if ((at_flags & ~(AT_NO_AUTOMOUNT | AT_EMPTY_PATH)) != 0)
+ return -EINVAL;
+ if (at_flags & AT_NO_AUTOMOUNT)
+ lookup_flags &= ~LOOKUP_AUTOMOUNT;
+ if (at_flags & AT_EMPTY_PATH)
+ lookup_flags |= LOOKUP_EMPTY;
+
+ 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) {
+ ret = -ENOMEM;
+ if (!s->s_watchers) {
+ wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+ if (!wlist)
+ goto err_wqueue;
+ init_watch_list(wlist, NULL);
+ }
+
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wlist;
+
+ init_watch(watch, wqueue);
+ watch->id = s->s_unique_id;
+ watch->private = s;
+ watch->info_id = (u32)watch_id << 24;
+
+ ret = security_watch_sb(watch, s);
+ if (ret < 0)
+ goto err_watch;
+
+ down_write(&s->s_umount);
+ ret = -EIO;
+ if (atomic_read(&s->s_active)) {
+ if (!s->s_watchers) {
+ s->s_watchers = wlist;
+ wlist = NULL;
+ }
+
+ ret = add_watch_to_object(watch, s->s_watchers);
+ if (ret == 0) {
+ spin_lock(&sb_lock);
+ s->s_count++;
+ spin_unlock(&sb_lock);
+ watch = NULL;
+ }
+ }
+ up_write(&s->s_umount);
+ } else {
+ ret = -EBADSLT;
+ if (READ_ONCE(s->s_watchers)) {
+ down_write(&s->s_umount);
+ ret = remove_watch_from_object(s->s_watchers, wqueue,
+ s->s_unique_id, false);
+ up_write(&s->s_umount);
+ }
+ }
+
+err_watch:
+ kfree(watch);
+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 db05738b1951..02ba4bfb9cc3 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -40,6 +40,7 @@
#include <linux/fs_types.h>
#include <linux/build_bug.h>
#include <linux/stddef.h>
+#include <linux/watch_queue.h>
#include <asm/byteorder.h>
#include <uapi/linux/fs.h>
@@ -1531,6 +1532,10 @@ struct super_block {
/* Superblock event notifications */
u64 s_unique_id;
+
+#ifdef CONFIG_SB_NOTIFICATIONS
+ struct watch_list *s_watchers;
+#endif
} __randomize_layout;
/* Helper functions so that in most cases filesystems will
@@ -3531,4 +3536,76 @@ static inline struct sock *io_uring_get_socket(struct file *file)
}
#endif
+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.
+ * @info: WATCH_INFO_FLAG_* flags to be set in the record.
+ */
+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_unique_id,
+ };
+
+ post_sb_notification(s, &n);
+ }
+
+#endif
+}
+
+/**
+ * notify_sb_error: Post superblock error notification.
+ * @s: The superblock the notification is about.
+ * @error: The error number to be recorded.
+ */
+static inline int notify_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_unique_id,
+ .error_number = error,
+ .error_cookie = 0,
+ };
+
+ post_sb_notification(s, &n.s);
+ }
+#endif
+ return error;
+}
+
+/**
+ * notify_sb_EDQUOT: Post superblock quota overrun notification.
+ * @s: The superblock the notification is about.
+ */
+static inline int notify_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_unique_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 f2e7f18b323a..308141bc3f69 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1003,6 +1003,8 @@ asmlinkage long sys_fsinfo(int dfd, const char __user *path,
void __user *buffer, size_t buf_size);
asmlinkage long sys_watch_mount(int dfd, const char __user *path,
unsigned int at_flags, int watch_fd, int watch_id);
+asmlinkage long sys_watch_sb(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 cf3c0c03a747..66b0da7cf888 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -129,4 +129,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 97b025e7863c..565d1e3d1bed 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -108,6 +108,9 @@ COND_SYSCALL(quotactl);
/* fs/read_write.c */
+/* fs/sb_notify.c */
+COND_SYSCALL(sb_notify);
+
/* fs/sendfile.c */
/* fs/select.c */
Provide an fsinfo attribute to export the superblock notification counter
so that it can be polled in the case of a notification buffer overrun.
This is accessed with:
struct fsinfo_params params = {
.request = FSINFO_ATTR_SB_NOTIFICATIONS,
};
and returns a structure that looks like:
struct fsinfo_sb_notifications {
__u64 watch_id;
__u32 notify_counter;
__u32 __reserved[1];
};
Where watch_id is a number uniquely identifying the superblock in
notification records and notify_counter is incremented for each
superblock notification posted.
Signed-off-by: David Howells <[email protected]>
---
fs/fsinfo.c | 12 ++++++++++++
fs/super.c | 1 +
include/linux/fs.h | 1 +
include/uapi/linux/fsinfo.h | 10 ++++++++++
include/uapi/linux/watch_queue.h | 2 +-
samples/vfs/test-fsinfo.c | 13 +++++++++++++
6 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index 3ec64d3cba08..1456e26d2f7c 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -284,6 +284,16 @@ static int fsinfo_generic_param_enum(struct file_system_type *f,
return sizeof(*p);
}
+static int fsinfo_generic_sb_notifications(struct path *path,
+ struct fsinfo_sb_notifications *p)
+{
+ struct super_block *sb = path->dentry->d_sb;
+
+ p->watch_id = sb->s_unique_id;
+ p->notify_counter = atomic_read(&sb->s_notify_counter);
+ return sizeof(*p);
+}
+
static void fsinfo_insert_sb_flag_parameters(struct path *path,
struct fsinfo_kparams *params)
{
@@ -331,6 +341,7 @@ int generic_fsinfo(struct path *path, struct fsinfo_kparams *params)
case _genp(MOUNT_DEVNAME, mount_devname);
case _genp(MOUNT_CHILDREN, mount_children);
case _genp(MOUNT_SUBMOUNT, mount_submount);
+ case _gen(SB_NOTIFICATIONS, sb_notifications);
default:
return -EOPNOTSUPP;
}
@@ -606,6 +617,7 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRING_N (SERVER_NAME, server_name),
FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
FSINFO_STRING (CELL_NAME, cell_name),
+ FSINFO_STRUCT (SB_NOTIFICATIONS, sb_notifications),
};
/**
diff --git a/fs/super.c b/fs/super.c
index 5bfe409a4fbe..b9bd4a849b69 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -1823,6 +1823,7 @@ EXPORT_SYMBOL(thaw_super);
*/
void post_sb_notification(struct super_block *s, struct superblock_notification *n)
{
+ atomic_inc(&s->s_notify_counter);
post_watch_notification(s->s_watchers, &n->watch, current_cred(),
s->s_unique_id);
}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 02ba4bfb9cc3..06e272a25ed7 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1536,6 +1536,7 @@ struct super_block {
#ifdef CONFIG_SB_NOTIFICATIONS
struct watch_list *s_watchers;
#endif
+ atomic_t s_notify_counter;
} __randomize_layout;
/* Helper functions so that in most cases filesystems will
diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
index bb38f9c41772..31e9354170c5 100644
--- a/include/uapi/linux/fsinfo.h
+++ b/include/uapi/linux/fsinfo.h
@@ -39,6 +39,7 @@ enum fsinfo_attribute {
FSINFO_ATTR_SERVER_NAME = 21, /* Name of the Nth server (string) */
FSINFO_ATTR_SERVER_ADDRESS = 22, /* Mth address of the Nth server */
FSINFO_ATTR_CELL_NAME = 23, /* Cell name (string) */
+ FSINFO_ATTR_SB_NOTIFICATIONS = 24, /* sb_notify() information */
FSINFO_ATTR__NR
};
@@ -308,4 +309,13 @@ struct fsinfo_server_address {
struct __kernel_sockaddr_storage address;
};
+/*
+ * Information struct for fsinfo(FSINFO_ATTR_SB_NOTIFICATIONS).
+ */
+struct fsinfo_sb_notifications {
+ __u64 watch_id; /* Watch ID for superblock. */
+ __u32 notify_counter; /* Number of notifications. */
+ __u32 __reserved[1];
+};
+
#endif /* _UAPI_LINUX_FSINFO_H */
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 66b0da7cf888..aeffcfd7a742 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -146,7 +146,7 @@ enum superblock_notification_type {
*/
struct superblock_notification {
struct watch_notification watch; /* WATCH_TYPE_SB_NOTIFY */
- __u64 sb_id; /* 64-bit superblock ID [fsinfo_ids::f_sb_id] */
+ __u64 sb_id; /* 64-bit superblock ID [fsinfo_sb_notifications::watch_id] */
};
struct superblock_error_notification {
diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c
index af29da74559e..0f8f9ded0925 100644
--- a/samples/vfs/test-fsinfo.c
+++ b/samples/vfs/test-fsinfo.c
@@ -90,6 +90,7 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRING_N (SERVER_NAME, server_name),
FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
FSINFO_STRING (CELL_NAME, cell_name),
+ FSINFO_STRUCT (SB_NOTIFICATIONS, sb_notifications),
};
#define FSINFO_NAME(X,Y) [FSINFO_ATTR_##X] = #Y
@@ -118,6 +119,7 @@ static const char *fsinfo_attr_names[FSINFO_ATTR__NR] = {
FSINFO_NAME (SERVER_NAME, server_name),
FSINFO_NAME (SERVER_ADDRESS, server_address),
FSINFO_NAME (CELL_NAME, cell_name),
+ FSINFO_NAME (SB_NOTIFICATIONS, sb_notifications),
};
union reply {
@@ -133,6 +135,7 @@ union reply {
struct fsinfo_mount_info mount_info;
struct fsinfo_mount_child mount_children[1];
struct fsinfo_server_address srv_addr;
+ struct fsinfo_sb_notifications sb_notifications;
};
static void dump_hex(unsigned int *data, int from, int to)
@@ -377,6 +380,15 @@ static void dump_attr_MOUNT_CHILDREN(union reply *r, int size)
printf("\t[%u] %8x %8x\n", i++, f->mnt_id, f->notify_counter);
}
+static void dump_attr_SB_NOTIFICATIONS(union reply *r, int size)
+{
+ struct fsinfo_sb_notifications *f = &r->sb_notifications;
+
+ printf("\n");
+ printf("\twatch_id: %llx\n", (unsigned long long)f->watch_id);
+ printf("\tnotifs : %llx\n", (unsigned long long)f->notify_counter);
+}
+
/*
*
*/
@@ -395,6 +407,7 @@ static const dumper_t fsinfo_attr_dumper[FSINFO_ATTR__NR] = {
FSINFO_DUMPER(MOUNT_INFO),
FSINFO_DUMPER(MOUNT_CHILDREN),
FSINFO_DUMPER(SERVER_ADDRESS),
+ FSINFO_DUMPER(SB_NOTIFICATIONS),
};
static void dump_fsinfo(enum fsinfo_attribute attr,
Create a general, global watch list that can be used for the posting of
device notification events, for such things as device attachment,
detachment and errors on sources such as block devices and USB devices.
This can be enabled with:
CONFIG_DEVICE_NOTIFICATIONS
To add a watch on this list, an event queue must be created and configured:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
and then a watch can be placed upon it using a system call:
watch_devices(fd, 12);
Unless the application wants to receive all events, it should employ
appropriate filters.
Signed-off-by: David Howells <[email protected]>
---
Documentation/watch_queue.rst | 27 +++++++---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
drivers/base/Kconfig | 9 +++
drivers/base/Makefile | 1
drivers/base/watch.c | 89 ++++++++++++++++++++++++++++++++
include/linux/device.h | 7 +++
include/linux/syscalls.h | 1
include/linux/watch_queue.h | 3 +
kernel/sys_ni.c | 1
10 files changed, 133 insertions(+), 7 deletions(-)
create mode 100644 drivers/base/watch.c
diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
index 0668c4a31710..e4b8233d5aa8 100644
--- a/Documentation/watch_queue.rst
+++ b/Documentation/watch_queue.rst
@@ -11,7 +11,7 @@ receive notifications from the kernel. This can be used in conjunction with::
* Superblock event notifications
- * Block layer event notifications
+ * General device event notifications
The notifications buffers can be enabled by:
@@ -292,6 +292,25 @@ The ``id`` is the ID of the source object (such as the serial number on a key).
Only watches that have the same ID set in them will see this notification.
+Global Device Watch List
+========================
+
+There is a global watch list that hardware generated events, such as device
+connection, disconnection, failure and error can be posted upon. It must be
+enabled using::
+
+ CONFIG_DEVICE_NOTIFICATIONS
+
+Watchpoints are set in userspace using the device_notify(2) system call.
+Within the kernel events are posted upon it using::
+
+ void post_device_notification(struct watch_notification *n, u64 id);
+
+where ``n`` is the formatted notification record to post. ``id`` is an
+identifier that can be used to direct to specific watches, but it should be 0
+for general use on this queue.
+
+
Watch Sources
=============
@@ -317,12 +336,6 @@ Any particular buffer can be fed from multiple sources. Sources include:
See Documentation/security/keys/core.rst for more information.
- * WATCH_TYPE_BLOCK_NOTIFY
-
- Notifications of this type indicate block layer events, such as I/O errors
- or temporary link loss. Watchpoints of this type are set on a global
- queue.
-
Event Filtering
===============
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index ef6035f71db4..5e69653cde96 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -441,3 +441,4 @@
434 i386 fsinfo sys_fsinfo __ia32_sys_fsinfo
435 i386 watch_mount sys_watch_mount __ia32_sys_watch_mount
436 i386 watch_sb sys_watch_sb __ia32_sys_watch_sb
+437 i386 watch_devices sys_watch_devices __ia32_sys_watch_devices
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 072f5dbac6a3..19afaecef042 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -358,6 +358,7 @@
434 common fsinfo __x64_sys_fsinfo
435 common watch_mount __x64_sys_watch_mount
436 common watch_sb __x64_sys_watch_sb
+437 common watch_devices __x64_sys_watch_devices
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index dc404492381d..63db34efb23b 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -1,6 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
menu "Generic Driver Options"
+config DEVICE_NOTIFICATIONS
+ bool "Provide device event notifications"
+ select WATCH_QUEUE
+ help
+ This option provides support for getting hardware event notifications
+ on devices, buses and interfaces. This makes use of the
+ /dev/watch_queue misc device to handle the notification buffer.
+ device_notify(2) is used to set/remove watches.
+
config UEVENT_HELPER
bool "Support for uevent helper"
help
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 157452080f3d..4db2e8f1a1f4 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -7,6 +7,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \
attribute_container.o transport_class.o \
topology.o container.o property.o cacheinfo.o \
devcon.o swnode.o
+obj-$(CONFIG_DEVICE_NOTIFICATIONS) += watch.o
obj-$(CONFIG_DEVTMPFS) += devtmpfs.o
obj-y += power/
obj-$(CONFIG_ISA_BUS_API) += isa.o
diff --git a/drivers/base/watch.c b/drivers/base/watch.c
new file mode 100644
index 000000000000..af89eca12097
--- /dev/null
+++ b/drivers/base/watch.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Event notifications.
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#include <linux/watch_queue.h>
+#include <linux/syscalls.h>
+#include <linux/init_task.h>
+#include <linux/security.h>
+
+/*
+ * Global queue for watching for device layer events.
+ */
+static struct watch_list device_watchers = {
+ .watchers = HLIST_HEAD_INIT,
+ .lock = __SPIN_LOCK_UNLOCKED(&device_watchers.lock),
+};
+
+static DEFINE_SPINLOCK(device_watchers_lock);
+
+/**
+ * post_device_notification - Post notification of a device event
+ * @n - The notification to post
+ * @id - The device ID
+ *
+ * Note that there's only a global queue to which all events are posted. Might
+ * want to provide per-dev queues also.
+ */
+void post_device_notification(struct watch_notification *n, u64 id)
+{
+ post_watch_notification(&device_watchers, n, &init_cred, id);
+}
+
+/**
+ * sys_watch_devices - Watch for device events.
+ * @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_DEFINE2(watch_devices, int, watch_fd, int, watch_id)
+{
+ struct watch_queue *wqueue;
+ struct watch_list *wlist = &device_watchers;
+ struct watch *watch;
+ long ret = -ENOMEM;
+ u64 id = 0; /* Might want to allow dev# here. */
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+
+ wqueue = get_watch_queue(watch_fd);
+ if (IS_ERR(wqueue)) {
+ ret = PTR_ERR(wqueue);
+ goto err;
+ }
+
+ if (watch_id >= 0) {
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wqueue;
+
+ init_watch(watch, wqueue);
+ watch->id = id;
+ watch->info_id = (u32)watch_id << WATCH_INFO_ID__SHIFT;
+
+ ret = security_watch_devices(watch);
+ if (ret < 0)
+ goto err_watch;
+
+ spin_lock(&device_watchers_lock);
+ ret = add_watch_to_object(watch, wlist);
+ spin_unlock(&device_watchers_lock);
+ if (ret == 0)
+ watch = NULL;
+ } else {
+ spin_lock(&device_watchers_lock);
+ ret = remove_watch_from_object(wlist, wqueue, id, false);
+ spin_unlock(&device_watchers_lock);
+ }
+
+err_watch:
+ kfree(watch);
+err_wqueue:
+ put_watch_queue(wqueue);
+err:
+ return ret;
+}
diff --git a/include/linux/device.h b/include/linux/device.h
index e85264fb6616..c947c078b1be 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -26,6 +26,7 @@
#include <linux/uidgid.h>
#include <linux/gfp.h>
#include <linux/overflow.h>
+#include <linux/watch_queue.h>
#include <asm/device.h>
struct device;
@@ -1396,6 +1397,12 @@ struct device_link *device_link_add(struct device *consumer,
void device_link_del(struct device_link *link);
void device_link_remove(void *consumer, struct device *supplier);
+#ifdef CONFIG_DEVICE_NOTIFICATIONS
+extern void post_device_notification(struct watch_notification *n, u64 id);
+#else
+static inline void post_device_notification(struct watch_notification *n, u64 id) {}
+#endif
+
#ifndef dev_fmt
#define dev_fmt(fmt) fmt
#endif
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 308141bc3f69..470028aef886 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1005,6 +1005,7 @@ asmlinkage long sys_watch_mount(int dfd, const char __user *path,
unsigned int at_flags, int watch_fd, int watch_id);
asmlinkage long sys_watch_sb(int dfd, const char __user *path,
unsigned int at_flags, int watch_fd, int watch_id);
+asmlinkage long sys_watch_devices(int watch_fd, int watch_id, unsigned int flags);
/*
* Architecture-specific system calls
diff --git a/include/linux/watch_queue.h b/include/linux/watch_queue.h
index 91777119db5e..8a5d586dfdf8 100644
--- a/include/linux/watch_queue.h
+++ b/include/linux/watch_queue.h
@@ -12,10 +12,12 @@
#include <uapi/linux/watch_queue.h>
#include <linux/kref.h>
+#include <linux/rcupdate.h>
#ifdef CONFIG_WATCH_QUEUE
struct watch_queue;
+struct cred;
/*
* Representation of a watch on an object.
@@ -53,6 +55,7 @@ extern void put_watch_queue(struct watch_queue *);
extern void init_watch(struct watch *, struct watch_queue *);
extern int add_watch_to_object(struct watch *, struct watch_list *);
extern int remove_watch_from_object(struct watch_list *, struct watch_queue *, u64, bool);
+extern void post_device_notification(struct watch_notification *, u64);
static inline void init_watch_list(struct watch_list *wlist,
void (*release_watch)(struct watch *))
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 565d1e3d1bed..580374089f8d 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -51,6 +51,7 @@ COND_SYSCALL_COMPAT(io_pgetevents);
COND_SYSCALL(io_uring_setup);
COND_SYSCALL(io_uring_enter);
COND_SYSCALL(io_uring_register);
+COND_SYSCALL(device_notify);
/* fs/xattr.c */
On Fri, Jun 07, 2019 at 03:17:40PM +0100, David Howells wrote:
> Add UAPI definitions for the general notification ring, including the
> following pieces:
>
> (1) struct watch_notification.
>
> This is the metadata header for each entry in the ring. It includes a
> type and subtype that indicate the source of the message
> (eg. WATCH_TYPE_MOUNT_NOTIFY) and the kind of the message
> (eg. NOTIFY_MOUNT_NEW_MOUNT).
>
> The header also contains an information field that conveys the
> following information:
>
> - WATCH_INFO_LENGTH. The size of the entry (entries are variable
> length).
>
> - WATCH_INFO_OVERRUN. If preceding messages were lost due to ring
> overrun or lack of memory.
>
> - WATCH_INFO_ENOMEM. If preceding messages were lost due to lack
> of memory.
>
> - WATCH_INFO_RECURSIVE. If the event detected was applied to
> multiple objects (eg. a recursive change to mount attributes).
>
> - WATCH_INFO_IN_SUBTREE. If the event didn't happen at the watched
> object, but rather to some related object (eg. a subtree mount
> watch saw a mount happen somewhere within the subtree).
>
> - WATCH_INFO_TYPE_FLAGS. Eight flags whose meanings depend on the
> message type.
>
> - WATCH_INFO_ID. The watch ID specified when the watchpoint was
> set.
>
> All the information in the header can be used in filtering messages at
> the point of writing into the buffer.
>
> (2) struct watch_queue_buffer.
>
> This describes the layout of the ring. Note that the first slots in
> the ring contain a special metadata entry that contains the ring
> pointers. The producer in the kernel knows to skip this and it has a
> proper header (WATCH_TYPE_META, WATCH_META_SKIP_NOTIFICATION) that
> indicates the size so that the ring consumer can handle it the same as
> any other record and just skip it.
>
> Note that this means that ring entries can never be split over the end
> of the ring, so if an entry would need to be split, a skip record is
> inserted to wrap the ring first; this is also WATCH_TYPE_META,
> WATCH_META_SKIP_NOTIFICATION.
>
> Signed-off-by: David Howells <[email protected]>
I'm starting by reading the uapi changes and the sample program...
> ---
>
> include/uapi/linux/watch_queue.h | 63 ++++++++++++++++++++++++++++++++++++++
> 1 file changed, 63 insertions(+)
> create mode 100644 include/uapi/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..c3a88fa5f62a
> --- /dev/null
> +++ b/include/uapi/linux/watch_queue.h
> @@ -0,0 +1,63 @@
> +/* 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>
> +
> +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 */
> + WATCH_TYPE_BLOCK_NOTIFY = 4, /* Block layer notifications */
> +#define WATCH_TYPE___NR 5
Given the way enums work I think you can just make WATCH_TYPE___NR the
last element in the enum?
> +};
> +
> +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 {
Kind of a long name...
> + __u32 type:24; /* enum watch_notification_type */
> + __u32 subtype:8; /* Type-specific subtype (filterable) */
16777216 diferent types and 256 different subtypes? My gut instinct
wants a better balance, though I don't know where I'd draw the line.
Probably 12 bits for type and 10 for subtype? OTOH I don't have a good
sense of how many distinct notification types an XFS would want to send
back to userspace, and maybe 256 subtypes is fine. We could always
reserve another watch_notification_type if we need > 256.
Ok, no objections. :)
> + __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) */
This is a mask, isn't it? Could we perhaps have some helpers here?
Something along the lines of...
#define WATCH_INFO_LENGTH_MASK 0x000001f8
#define WATCH_INFO_LENGTH_SHIFT 3
static inline size_t watch_notification_length(struct watch_notification *wn)
{
return (wn->info & WATCH_INFO_LENGTH_MASK) >> WATCH_INFO_LENGTH_SHIFT *
sizeof(struct watch_notification);
}
static inline struct watch_notification *watch_notification_next(
struct watch_notification *wn)
{
return wn + ((wn->info & WATCH_INFO_LENGTH_MASK) >>
WATCH_INFO_LENGTH_SHIFT);
}
...so that we don't have to opencode all of the ring buffer walking
magic and stuff?
(I might also shorten the namespace from WATCH_INFO_ to WNI_ ...)
Hmm so the length field is 6 bits and therefore the maximum size of a
notification record is ... 63 * (sizeof(u32) * 2) = 504 bytes? Which
means that kernel users can send back a maximum payload of 496 bytes?
That's probably big enough for random fs notifications (bad metadata
detected, media errors, etc.)
Judging from the sample program I guess all that userspace does is
allocate a memory buffer and toss it into the kernel, which then
initializes the ring management variables, and from there we just scan
around the ring buffer every time poll(watch_fd) says there's something
to do? How does userspace tell the kernel the size of the ring buffer?
Does (watch_notification->info & WATCH_INFO_LENGTH) == 0 have any
meaning besides apparently "stop looking at me"?
> +#define WATCH_INFO_IN_SUBTREE 0x00000200 /* Change was not at watched root */
> +#define WATCH_INFO_TYPE_FLAGS 0x00ff0000 /* Type-specific flags */
WATCH_INFO_FLAG_MASK ?
> +#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 */
WATCH_INFO_ID_MASK ?
> +#define WATCH_INFO_ID__SHIFT 24
Why double underscore here?
> +};
> +
> +#define WATCH_LENGTH_SHIFT 3
> +
> +struct watch_queue_buffer {
> + union {
> + /* The first few entries are special, containing the
> + * ring management variables.
The first /two/ entries, correct?
Also, weird multiline comment style.
> + */
> + struct {
> + struct watch_notification watch; /* WATCH_TYPE_META */
Do these structures have to be cache-aligned for the atomic reads and
writes to work?
--D
> + __u32 head; /* Ring head index */
> + __u32 tail; /* Ring tail index */
> + __u32 mask; /* Ring index mask */
> + } meta;
> + struct watch_notification slots[0];
> + };
> +};
> +
> +#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
>
So that the LSM can see the credentials of the last process to do an fput()
on a file object when the file object is being dismantled, do the following
steps:
(1) Cache the current credentials in file->f_fput_cred at the point the
file object's reference count reaches zero.
(2) In __fput(), use override_creds() to apply those credentials to the
dismantling process. This is necessary so that if we're dismantling a
unix socket that has semi-passed fds still in it, their fputs will
pick up the same credentials if they're reduced to zero at that point.
Note that it's probably not strictly necessary to take an extra ref on
the creds here (which override_creds() does).
(3) Destroy the fput creds in file_free_rcu().
This additionally makes the creds available to:
fsnotify
eventpoll
file locking
->fasync, ->release file ops
superblock destruction
mountpoint destruction
This allows various notifications about object cleanups/destructions to
carry appropriate credentials for the LSM to approve/disapprove them based
on the process that caused them, even if indirectly.
Note that this means that someone looking at /proc/<pid>/fd/<n> may end up
being inadvertently noted as the subject of a cleanup message if the
process they're looking at croaks whilst they're looking at it.
Further, kernel services like nfsd and cachefiles may be seen as the
fputter and may not have a system credential. In cachefiles's case, it may
appear that cachefilesd caused the notification.
Suggested-by: Casey Schaufler <[email protected]>
Signed-off-by: David Howells <[email protected]>
cc: Casey Schaufler <[email protected]>
---
fs/file_table.c | 12 ++++++++++++
include/linux/fs.h | 1 +
2 files changed, 13 insertions(+)
diff --git a/fs/file_table.c b/fs/file_table.c
index 3f9c1b452c1d..9bf2be45b7f9 100644
--- a/fs/file_table.c
+++ b/fs/file_table.c
@@ -46,6 +46,7 @@ static void file_free_rcu(struct rcu_head *head)
struct file *f = container_of(head, struct file, f_u.fu_rcuhead);
put_cred(f->f_cred);
+ put_cred(f->f_fput_cred);
kmem_cache_free(filp_cachep, f);
}
@@ -252,6 +253,7 @@ struct file *alloc_file_clone(struct file *base, int flags,
*/
static void __fput(struct file *file)
{
+ const struct cred *saved_cred;
struct dentry *dentry = file->f_path.dentry;
struct vfsmount *mnt = file->f_path.mnt;
struct inode *inode = file->f_inode;
@@ -262,6 +264,12 @@ static void __fput(struct file *file)
might_sleep();
+ /* Set the creds of whoever triggered the last fput for the LSM. Note
+ * that this has to be made available to further fputs, say on fds
+ * trapped in a unix socket.
+ */
+ saved_cred = override_creds(file->f_fput_cred);
+
fsnotify_close(file);
/*
* The function eventpoll_release() should be the first called
@@ -293,6 +301,8 @@ static void __fput(struct file *file)
if (unlikely(mode & FMODE_NEED_UNMOUNT))
dissolve_on_fput(mnt);
mntput(mnt);
+
+ revert_creds(saved_cred);
out:
file_free(file);
}
@@ -334,6 +344,7 @@ void fput_many(struct file *file, unsigned int refs)
if (atomic_long_sub_and_test(refs, &file->f_count)) {
struct task_struct *task = current;
+ file->f_fput_cred = get_current_cred();
if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD))) {
init_task_work(&file->f_u.fu_rcuhead, ____fput);
if (!task_work_add(task, &file->f_u.fu_rcuhead, true))
@@ -368,6 +379,7 @@ void __fput_sync(struct file *file)
if (atomic_long_dec_and_test(&file->f_count)) {
struct task_struct *task = current;
BUG_ON(!(task->flags & PF_KTHREAD));
+ file->f_fput_cred = get_current_cred();
__fput(file);
}
}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f1c74596cd77..db05738b1951 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -943,6 +943,7 @@ struct file {
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
+ const struct cred *f_fput_cred; /* Who did the last fput() (for LSM) */
struct file_ra_state f_ra;
u64 f_version;
Add a block layer notification mechanism whereby notifications about
block-layer events such as I/O errors, can be reported to a monitoring
process asynchronously.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report block notifications via that
queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_BLOCK_NOTIFY,
.subtype_filter[0] = UINT_MAX;
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
watch_devices(fd, 12);
After that, records will be placed into the queue when, for example, errors
occur on a block device. Records are of the following format:
struct block_notification {
struct watch_notification watch;
__u64 dev;
__u64 sector;
} *n;
Where:
n->watch.type will be WATCH_TYPE_BLOCK_NOTIFY
n->watch.subtype will be the type of notification, such as
NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the second argument to
watch_devices(), shifted.
n->dev will be the device numbers munged together.
n->sector will indicate the affected sector (if appropriate for the
event).
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype.
Signed-off-by: David Howells <[email protected]>
---
Documentation/watch_queue.rst | 10 +++++++++-
block/Kconfig | 9 +++++++++
block/blk-core.c | 29 +++++++++++++++++++++++++++++
include/linux/blkdev.h | 15 +++++++++++++++
include/uapi/linux/watch_queue.h | 27 +++++++++++++++++++++++++++
5 files changed, 89 insertions(+), 1 deletion(-)
diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
index e4b8233d5aa8..c2954e191989 100644
--- a/Documentation/watch_queue.rst
+++ b/Documentation/watch_queue.rst
@@ -11,7 +11,9 @@ receive notifications from the kernel. This can be used in conjunction with::
* Superblock event notifications
- * General device event notifications
+ * General device event notifications, including::
+
+ * Block layer event notifications
The notifications buffers can be enabled by:
@@ -336,6 +338,12 @@ Any particular buffer can be fed from multiple sources. Sources include:
See Documentation/security/keys/core.rst for more information.
+ * WATCH_TYPE_BLOCK_NOTIFY
+
+ Notifications of this type indicate block layer events, such as I/O errors
+ or temporary link loss. Watchpoints of this type are set on the global
+ device watch list.
+
Event Filtering
===============
diff --git a/block/Kconfig b/block/Kconfig
index 1b220101a9cb..4ff4a56ba9f9 100644
--- a/block/Kconfig
+++ b/block/Kconfig
@@ -163,6 +163,15 @@ config BLK_SED_OPAL
Enabling this option enables users to setup/unlock/lock
Locking ranges for SED devices using the Opal protocol.
+config BLK_NOTIFICATIONS
+ bool "Block layer event notifications"
+ select DEVICE_NOTIFICATIONS
+ help
+ This option provides support for getting block layer event
+ notifications. This makes use of the /dev/watch_queue misc device to
+ handle the notification buffer and provides the device_notify() system
+ call to enable/disable watches.
+
menu "Partition Types"
source "block/partitions/Kconfig"
diff --git a/block/blk-core.c b/block/blk-core.c
index 419d600e6637..edad86172d47 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -144,6 +144,22 @@ static const struct {
[BLK_STS_IOERR] = { -EIO, "I/O" },
};
+#ifdef CONFIG_BLK_NOTIFICATIONS
+static const
+enum block_notification_type blk_notifications[ARRAY_SIZE(blk_errors)] = {
+ [BLK_STS_TIMEOUT] = NOTIFY_BLOCK_ERROR_TIMEOUT,
+ [BLK_STS_NOSPC] = NOTIFY_BLOCK_ERROR_NO_SPACE,
+ [BLK_STS_TRANSPORT] = NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT,
+ [BLK_STS_TARGET] = NOTIFY_BLOCK_ERROR_CRITICAL_TARGET,
+ [BLK_STS_NEXUS] = NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS,
+ [BLK_STS_MEDIUM] = NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM,
+ [BLK_STS_PROTECTION] = NOTIFY_BLOCK_ERROR_PROTECTION,
+ [BLK_STS_RESOURCE] = NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE,
+ [BLK_STS_DEV_RESOURCE] = NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE,
+ [BLK_STS_IOERR] = NOTIFY_BLOCK_ERROR_IO,
+};
+#endif
+
blk_status_t errno_to_blk_status(int errno)
{
int i;
@@ -179,6 +195,19 @@ static void print_req_error(struct request *req, blk_status_t status)
req->rq_disk ? req->rq_disk->disk_name : "?",
(unsigned long long)blk_rq_pos(req),
req->cmd_flags);
+
+#ifdef CONFIG_BLK_NOTIFICATIONS
+ if (blk_notifications[idx]) {
+ struct block_notification n = {
+ .watch.type = WATCH_TYPE_BLOCK_NOTIFY,
+ .watch.subtype = blk_notifications[idx],
+ .watch.info = sizeof(n),
+ .dev = req->rq_disk ? disk_devt(req->rq_disk) : 0,
+ .sector = blk_rq_pos(req),
+ };
+ post_block_notification(&n);
+ }
+#endif
}
static void req_bio_endio(struct request *rq, struct bio *bio,
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 1aafeb923e7b..8b8e235f47c9 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -43,6 +43,7 @@ struct pr_ops;
struct rq_qos;
struct blk_queue_stats;
struct blk_stat_callback;
+struct block_notification;
#define BLKDEV_MIN_RQ 4
#define BLKDEV_MAX_RQ 128 /* Default maximum */
@@ -1744,6 +1745,20 @@ static inline bool blk_req_can_dispatch_to_zone(struct request *rq)
}
#endif /* CONFIG_BLK_DEV_ZONED */
+#ifdef CONFIG_BLK_NOTIFICATIONS
+static inline void post_block_notification(struct block_notification *n)
+{
+ u64 id = 0; /* Might want to allow dev# here. */
+
+ post_device_notification(&n->watch, id);
+}
+#else
+static inline void post_block_notification(struct block_notification *n)
+{
+}
+#endif
+
+
#else /* CONFIG_BLOCK */
struct block_device;
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index aeffcfd7a742..22e3326b83a6 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -155,4 +155,31 @@ struct superblock_error_notification {
__u32 error_cookie;
};
+/*
+ * Type of block layer notification.
+ */
+enum block_notification_type {
+ NOTIFY_BLOCK_ERROR_TIMEOUT = 1, /* Timeout error */
+ NOTIFY_BLOCK_ERROR_NO_SPACE = 2, /* Critical space allocation error */
+ NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT = 3, /* Recoverable transport error */
+ NOTIFY_BLOCK_ERROR_CRITICAL_TARGET = 4, /* Critical target error */
+ NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS = 5, /* Critical nexus error */
+ NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM = 6, /* Critical medium error */
+ NOTIFY_BLOCK_ERROR_PROTECTION = 7, /* Protection error */
+ NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE = 8, /* Kernel resource error */
+ NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE = 9, /* Device resource error */
+ NOTIFY_BLOCK_ERROR_IO = 10, /* Other I/O error */
+};
+
+/*
+ * Block layer notification record.
+ * - watch.type = WATCH_TYPE_BLOCK_NOTIFY
+ * - watch.subtype = enum block_notification_type
+ */
+struct block_notification {
+ struct watch_notification watch; /* WATCH_TYPE_BLOCK_NOTIFY */
+ __u64 dev; /* Device number */
+ __u64 sector; /* Affected sector */
+};
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
> I'm starting by reading the uapi changes and the sample program...
If you look in:
[PATCH 05/13] General notification queue with user mmap()'able ring buffer
you'll find the API document that goes in Documentation/
David
Darrick J. Wong <[email protected]> wrote:
> > +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 */
> > + WATCH_TYPE_BLOCK_NOTIFY = 4, /* Block layer notifications */
> > +#define WATCH_TYPE___NR 5
>
> Given the way enums work I think you can just make WATCH_TYPE___NR the
> last element in the enum?
Yeah. I've a feeling I'd been asked not to do that, but I don't remember who
by.
> > +struct watch_notification {
>
> Kind of a long name...
*shrug*. Try to avoid conflicts with userspace symbols.
> > + __u32 type:24; /* enum watch_notification_type */
> > + __u32 subtype:8; /* Type-specific subtype (filterable) */
>
> 16777216 diferent types and 256 different subtypes? My gut instinct
> wants a better balance, though I don't know where I'd draw the line.
> Probably 12 bits for type and 10 for subtype? OTOH I don't have a good
> sense of how many distinct notification types an XFS would want to send
> back to userspace, and maybe 256 subtypes is fine. We could always
> reserve another watch_notification_type if we need > 256.
>
> Ok, no objections. :)
If a source wants more than 256 subtypes, it can always allocate an additional
type. Note that the greater the number of subtypes, the larger the filter
structure (added in patch 5).
> > + __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) */
>
> This is a mask, isn't it? Could we perhaps have some helpers here?
> Something along the lines of...
>
> #define WATCH_INFO_LENGTH_MASK 0x000001f8
> #define WATCH_INFO_LENGTH_SHIFT 3
>
> static inline size_t watch_notification_length(struct watch_notification *wn)
> {
> return (wn->info & WATCH_INFO_LENGTH_MASK) >> WATCH_INFO_LENGTH_SHIFT *
> sizeof(struct watch_notification);
> }
>
> static inline struct watch_notification *watch_notification_next(
> struct watch_notification *wn)
> {
> return wn + ((wn->info & WATCH_INFO_LENGTH_MASK) >>
> WATCH_INFO_LENGTH_SHIFT);
> }
No inline functions in UAPI headers, please. I'd love to kill off the ones
that we have, but that would break things.
> ...so that we don't have to opencode all of the ring buffer walking
> magic and stuff?
There'll end up being a small userspace library, I think.
> (I might also shorten the namespace from WATCH_INFO_ to WNI_ ...)
I'd rather not do that. WNI_ could mean all sorts of things - at a first
glance, it sounds like something to do with Windows or windowing.
> Hmm so the length field is 6 bits and therefore the maximum size of a
> notification record is ... 63 * (sizeof(u32) * 2) = 504 bytes? Which
> means that kernel users can send back a maximum payload of 496 bytes?
> That's probably big enough for random fs notifications (bad metadata
> detected, media errors, etc.)
Yep. The ring buffer is limited in capacity since it has to be unswappable so
that notifications can be written into it from softirq context or under lock.
> Judging from the sample program I guess all that userspace does is
> allocate a memory buffer and toss it into the kernel, which then
> initializes the ring management variables, and from there we just scan
> around the ring buffer every time poll(watch_fd) says there's something
> to do?
Yes. Further, if head == tail, then it's empty. Note that since head and
tail will go up to 4G, but the buffer is limited to less than that, there's no
need for a requisite unusable slot in the ring as head != tail when the ring
is full.
> How does userspace tell the kernel the size of the ring buffer?
If you looked in the sample program, you might have noticed this in the main()
function:
if (ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE) == -1) {
The chardev allocates the buffer and then userspace mmaps it.
I need to steal the locked-page accounting from the epoll patches to stop
someone from using this to lock away all memory.
> Does (watch_notification->info & WATCH_INFO_LENGTH) == 0 have any
> meaning besides apparently "stop looking at me"?
That's an illegal value, indicating a kernel bug.
> > +#define WATCH_INFO_IN_SUBTREE 0x00000200 /* Change was not at watched root */
> > +#define WATCH_INFO_TYPE_FLAGS 0x00ff0000 /* Type-specific flags */
>
> WATCH_INFO_FLAG_MASK ?
Yeah.
> > +#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 */
>
> WATCH_INFO_ID_MASK ?
Sure.
> > +#define WATCH_INFO_ID__SHIFT 24
>
> Why double underscore here?
Because it's related to WATCH_INFO_ID, but isn't a mask on ->info.
> > +};
> > +
> > +#define WATCH_LENGTH_SHIFT 3
> > +
> > +struct watch_queue_buffer {
> > + union {
> > + /* The first few entries are special, containing the
> > + * ring management variables.
>
> The first /two/ entries, correct?
Currently two.
> Also, weird multiline comment style.
Not really.
> > + */
> > + struct {
> > + struct watch_notification watch; /* WATCH_TYPE_META */
>
> Do these structures have to be cache-aligned for the atomic reads and
> writes to work?
I hope not, otherwise we're screwed all over the kernel, particularly with
structure randomisation.
Further, what alignment is "cache aligned"? It can vary by CPU for a single
arch.
David
On 6/7/19 8:51 AM, David Howells wrote:
> Darrick J. Wong <[email protected]> wrote:
>
>>> + __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) */
>>
>> This is a mask, isn't it? Could we perhaps have some helpers here?
>> Something along the lines of...
>>
>> #define WATCH_INFO_LENGTH_MASK 0x000001f8
>> #define WATCH_INFO_LENGTH_SHIFT 3
>>
>> static inline size_t watch_notification_length(struct watch_notification *wn)
>> {
>> return (wn->info & WATCH_INFO_LENGTH_MASK) >> WATCH_INFO_LENGTH_SHIFT *
>> sizeof(struct watch_notification);
>> }
>>
>> static inline struct watch_notification *watch_notification_next(
>> struct watch_notification *wn)
>> {
>> return wn + ((wn->info & WATCH_INFO_LENGTH_MASK) >>
>> WATCH_INFO_LENGTH_SHIFT);
>> }
>
> No inline functions in UAPI headers, please. I'd love to kill off the ones
> that we have, but that would break things.
Hi David,
What is the problem with inline functions in UAPI headers?
>> ...so that we don't have to opencode all of the ring buffer walking
>> magic and stuff?
>
> There'll end up being a small userspace library, I think.
>>> +};
>>> +
>>> +#define WATCH_LENGTH_SHIFT 3
>>> +
>>> +struct watch_queue_buffer {
>>> + union {
>>> + /* The first few entries are special, containing the
>>> + * ring management variables.
>>
>> The first /two/ entries, correct?
>
> Currently two.
>
>> Also, weird multiline comment style.
>
> Not really.
Yes really.
>>> + */
It does not match the preferred coding style for multi-line comments
according to coding-style.rst.
thanks.
--
~Randy
On 6/7/19 10:17 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:
>
> (1) Mount topology events, such as mounting, unmounting, mount expiry,
> mount reconfiguration.
>
> (2) Superblock events, such as R/W<->R/O changes, quota overrun and I/O
> errors (not complete yet).
>
> (3) Key/keyring events, such as creating, linking and removal of keys.
>
> (4) General device events (single common queue) including:
>
> - Block layer events, such as device errors
>
> - USB subsystem events, such as device/bus attach/remove, device
> reset, device errors.
>
> 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. To further aid this, the fsinfo() syscall
> on which this patch series depends, provides a way to access superblock and
> mount information in binary form without the need to parse /proc/mounts.
>
>
> LSM support is included, but controversial:
>
> (1) The creds of the process that did the fput() that reduced the refcount
> to zero are cached in the file struct.
>
> (2) __fput() overrides the current creds with the creds from (1) whilst
> doing the cleanup, thereby making sure that the creds seen by the
> destruction notification generated by mntput() appears to come from
> the last fputter.
>
> (3) security_post_notification() is called for each queue that we might
> want to post a notification into, thereby allowing the LSM to prevent
> covert communications.
>
> (?) Do I need to add security_set_watch(), say, to rule on whether a watch
> may be set in the first place? I might need to add a variant per
> watch-type.
>
> (?) Do I really need to keep track of the process creds in which an
> implicit object destruction happened? For example, imagine you create
> an fd with fsopen()/fsmount(). It is marked to dissolve the mount it
> refers to on close unless move_mount() clears that flag. Now, imagine
> someone looking at that fd through procfs at the same time as you exit
> due to an error. The LSM sees the destruction notification come from
> the looker if they happen to do their fput() after yours.
I remain unconvinced that (1), (2), (3), and the final (?) above are a
good idea.
For SELinux, I would expect that one would implement a collection of per
watch-type WATCH permission checks on the target object (or to some
well-defined object label like the kernel SID if there is no object)
that allow receipt of all notifications of that watch-type for objects
related to the target object, where "related to" is defined per watch-type.
I wouldn't expect SELinux to implement security_post_notification() at
all. I can't see how one can construct a meaningful, stable policy for
it. I'd argue that the triggering process is not posting the
notification; the kernel is posting the notification and the watcher has
been authorized to receive it.
>
>
> 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 could be considered:
>
> (1) Adding a keyctl call 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.
>
> (2) Adding global superblock event queue.
>
> (3) 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
>
> Changes:
>
> v4: Split the basic UAPI bits out into their own patch and then split the
> LSM hooks out into an intermediate patch. Add LSM hooks for setting
> watches.
>
> Rename the *_notify() system calls to watch_*() for consistency.
>
> v3: I've added a USB notification source and reformulated the block
> notification source so that there's now a common watch list, for which
> the system call is now device_notify().
>
> I've assigned a pair of unused ioctl numbers in the 'W' series to the
> ioctls added by this series.
>
> I've also added a description of the kernel API to the documentation.
>
> v2: I've fixed various issues raised by Jann Horn and GregKH and moved to
> krefs for refcounting. I've added some security features to try and
> give Casey Schaufler the LSM control he wants.
>
> David
> ---
> David Howells (13):
> security: Override creds in __fput() with last fputter's creds
> uapi: General notification ring definitions
> security: Add hooks to rule on setting a watch
> security: Add a hook for the point of notification insertion
> General notification queue with user mmap()'able ring buffer
> keys: Add a notification facility
> vfs: Add a mount-notification facility
> vfs: Add superblock notifications
> fsinfo: Export superblock notification counter
> Add a general, global device notification watch list
> block: Add block layer notifications
> usb: Add USB subsystem notifications
> Add sample notification program
>
>
> Documentation/ioctl/ioctl-number.txt | 1
> Documentation/security/keys/core.rst | 58 ++
> Documentation/watch_queue.rst | 492 ++++++++++++++++++
> arch/x86/entry/syscalls/syscall_32.tbl | 3
> arch/x86/entry/syscalls/syscall_64.tbl | 3
> block/Kconfig | 9
> block/blk-core.c | 29 +
> drivers/base/Kconfig | 9
> drivers/base/Makefile | 1
> drivers/base/watch.c | 89 +++
> drivers/misc/Kconfig | 13
> drivers/misc/Makefile | 1
> drivers/misc/watch_queue.c | 889 ++++++++++++++++++++++++++++++++
> drivers/usb/core/Kconfig | 10
> drivers/usb/core/devio.c | 55 ++
> drivers/usb/core/hub.c | 3
> fs/Kconfig | 21 +
> fs/Makefile | 1
> fs/file_table.c | 12
> fs/fsinfo.c | 12
> fs/mount.h | 33 +
> fs/mount_notify.c | 187 +++++++
> fs/namespace.c | 9
> fs/super.c | 122 ++++
> include/linux/blkdev.h | 15 +
> include/linux/dcache.h | 1
> include/linux/device.h | 7
> include/linux/fs.h | 79 +++
> include/linux/key.h | 4
> include/linux/lsm_hooks.h | 48 ++
> include/linux/security.h | 35 +
> include/linux/syscalls.h | 5
> include/linux/usb.h | 19 +
> include/linux/watch_queue.h | 87 +++
> include/uapi/linux/fsinfo.h | 10
> include/uapi/linux/keyctl.h | 1
> include/uapi/linux/watch_queue.h | 213 ++++++++
> kernel/sys_ni.c | 7
> samples/Kconfig | 6
> samples/Makefile | 1
> samples/vfs/test-fsinfo.c | 13
> samples/watch_queue/Makefile | 9
> samples/watch_queue/watch_test.c | 308 +++++++++++
> security/keys/Kconfig | 10
> security/keys/compat.c | 2
> security/keys/gc.c | 5
> security/keys/internal.h | 30 +
> security/keys/key.c | 37 +
> security/keys/keyctl.c | 95 +++
> security/keys/keyring.c | 17 -
> security/keys/request_key.c | 4
> security/security.c | 29 +
> 52 files changed, 3121 insertions(+), 38 deletions(-)
> create mode 100644 Documentation/watch_queue.rst
> create mode 100644 drivers/base/watch.c
> 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
>
On 6/10/2019 8:21 AM, Stephen Smalley wrote:
> On 6/7/19 10:17 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:
>>
>> (1) Mount topology events, such as mounting, unmounting, mount expiry,
>> mount reconfiguration.
>>
>> (2) Superblock events, such as R/W<->R/O changes, quota overrun and I/O
>> errors (not complete yet).
>>
>> (3) Key/keyring events, such as creating, linking and removal of keys.
>>
>> (4) General device events (single common queue) including:
>>
>> - Block layer events, such as device errors
>>
>> - USB subsystem events, such as device/bus attach/remove, device
>> reset, device errors.
>>
>> 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. To further aid this, the fsinfo() syscall
>> on which this patch series depends, provides a way to access superblock and
>> mount information in binary form without the need to parse /proc/mounts.
>>
>>
>> LSM support is included, but controversial:
>>
>> (1) The creds of the process that did the fput() that reduced the refcount
>> to zero are cached in the file struct.
>>
>> (2) __fput() overrides the current creds with the creds from (1) whilst
>> doing the cleanup, thereby making sure that the creds seen by the
>> destruction notification generated by mntput() appears to come from
>> the last fputter.
>>
>> (3) security_post_notification() is called for each queue that we might
>> want to post a notification into, thereby allowing the LSM to prevent
>> covert communications.
>>
>> (?) Do I need to add security_set_watch(), say, to rule on whether a watch
>> may be set in the first place? I might need to add a variant per
>> watch-type.
>>
>> (?) Do I really need to keep track of the process creds in which an
>> implicit object destruction happened? For example, imagine you create
>> an fd with fsopen()/fsmount(). It is marked to dissolve the mount it
>> refers to on close unless move_mount() clears that flag. Now, imagine
>> someone looking at that fd through procfs at the same time as you exit
>> due to an error. The LSM sees the destruction notification come from
>> the looker if they happen to do their fput() after yours.
>
> I remain unconvinced that (1), (2), (3), and the final (?) above are a good idea.
>
> For SELinux, I would expect that one would implement a collection of per watch-type WATCH permission checks on the target object (or to some well-defined object label like the kernel SID if there is no object) that allow receipt of all notifications of that watch-type for objects related to the target object, where "related to" is defined per watch-type.
>
> I wouldn't expect SELinux to implement security_post_notification() at all. I can't see how one can construct a meaningful, stable policy for it. I'd argue that the triggering process is not posting the notification; the kernel is posting the notification and the watcher has been authorized to receive it.
I cannot agree. There is an explicit action by a subject that results
in information being delivered to an object. Just like a signal or a
UDP packet delivery. Smack handles this kind of thing just fine. The
internal mechanism that results in the access is irrelevant from
this viewpoint. I can understand how a mechanism like SELinux that
works on finer granularity might view it differently.
>
>>
>>
>> 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 could be considered:
>>
>> (1) Adding a keyctl call 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.
>>
>> (2) Adding global superblock event queue.
>>
>> (3) 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
>>
>> Changes:
>>
>> v4: Split the basic UAPI bits out into their own patch and then split the
>> LSM hooks out into an intermediate patch. Add LSM hooks for setting
>> watches.
>>
>> Rename the *_notify() system calls to watch_*() for consistency.
>>
>> v3: I've added a USB notification source and reformulated the block
>> notification source so that there's now a common watch list, for which
>> the system call is now device_notify().
>>
>> I've assigned a pair of unused ioctl numbers in the 'W' series to the
>> ioctls added by this series.
>>
>> I've also added a description of the kernel API to the documentation.
>>
>> v2: I've fixed various issues raised by Jann Horn and GregKH and moved to
>> krefs for refcounting. I've added some security features to try and
>> give Casey Schaufler the LSM control he wants.
>>
>> David
>> ---
>> David Howells (13):
>> security: Override creds in __fput() with last fputter's creds
>> uapi: General notification ring definitions
>> security: Add hooks to rule on setting a watch
>> security: Add a hook for the point of notification insertion
>> General notification queue with user mmap()'able ring buffer
>> keys: Add a notification facility
>> vfs: Add a mount-notification facility
>> vfs: Add superblock notifications
>> fsinfo: Export superblock notification counter
>> Add a general, global device notification watch list
>> block: Add block layer notifications
>> usb: Add USB subsystem notifications
>> Add sample notification program
>>
>>
>> Documentation/ioctl/ioctl-number.txt | 1
>> Documentation/security/keys/core.rst | 58 ++
>> Documentation/watch_queue.rst | 492 ++++++++++++++++++
>> arch/x86/entry/syscalls/syscall_32.tbl | 3
>> arch/x86/entry/syscalls/syscall_64.tbl | 3
>> block/Kconfig | 9
>> block/blk-core.c | 29 +
>> drivers/base/Kconfig | 9
>> drivers/base/Makefile | 1
>> drivers/base/watch.c | 89 +++
>> drivers/misc/Kconfig | 13
>> drivers/misc/Makefile | 1
>> drivers/misc/watch_queue.c | 889 ++++++++++++++++++++++++++++++++
>> drivers/usb/core/Kconfig | 10
>> drivers/usb/core/devio.c | 55 ++
>> drivers/usb/core/hub.c | 3
>> fs/Kconfig | 21 +
>> fs/Makefile | 1
>> fs/file_table.c | 12
>> fs/fsinfo.c | 12
>> fs/mount.h | 33 +
>> fs/mount_notify.c | 187 +++++++
>> fs/namespace.c | 9
>> fs/super.c | 122 ++++
>> include/linux/blkdev.h | 15 +
>> include/linux/dcache.h | 1
>> include/linux/device.h | 7
>> include/linux/fs.h | 79 +++
>> include/linux/key.h | 4
>> include/linux/lsm_hooks.h | 48 ++
>> include/linux/security.h | 35 +
>> include/linux/syscalls.h | 5
>> include/linux/usb.h | 19 +
>> include/linux/watch_queue.h | 87 +++
>> include/uapi/linux/fsinfo.h | 10
>> include/uapi/linux/keyctl.h | 1
>> include/uapi/linux/watch_queue.h | 213 ++++++++
>> kernel/sys_ni.c | 7
>> samples/Kconfig | 6
>> samples/Makefile | 1
>> samples/vfs/test-fsinfo.c | 13
>> samples/watch_queue/Makefile | 9
>> samples/watch_queue/watch_test.c | 308 +++++++++++
>> security/keys/Kconfig | 10
>> security/keys/compat.c | 2
>> security/keys/gc.c | 5
>> security/keys/internal.h | 30 +
>> security/keys/key.c | 37 +
>> security/keys/keyctl.c | 95 +++
>> security/keys/keyring.c | 17 -
>> security/keys/request_key.c | 4
>> security/security.c | 29 +
>> 52 files changed, 3121 insertions(+), 38 deletions(-)
>> create mode 100644 Documentation/watch_queue.rst
>> create mode 100644 drivers/base/watch.c
>> 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
>>
>
On Mon, Jun 10, 2019 at 9:34 AM Casey Schaufler <[email protected]> wrote:
>
> On 6/10/2019 8:21 AM, Stephen Smalley wrote:
> > On 6/7/19 10:17 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:
> >>
> >> (1) Mount topology events, such as mounting, unmounting, mount expiry,
> >> mount reconfiguration.
> >>
> >> (2) Superblock events, such as R/W<->R/O changes, quota overrun and I/O
> >> errors (not complete yet).
> >>
> >> (3) Key/keyring events, such as creating, linking and removal of keys.
> >>
> >> (4) General device events (single common queue) including:
> >>
> >> - Block layer events, such as device errors
> >>
> >> - USB subsystem events, such as device/bus attach/remove, device
> >> reset, device errors.
> >>
> >> 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. To further aid this, the fsinfo() syscall
> >> on which this patch series depends, provides a way to access superblock and
> >> mount information in binary form without the need to parse /proc/mounts.
> >>
> >>
> >> LSM support is included, but controversial:
> >>
> >> (1) The creds of the process that did the fput() that reduced the refcount
> >> to zero are cached in the file struct.
> >>
> >> (2) __fput() overrides the current creds with the creds from (1) whilst
> >> doing the cleanup, thereby making sure that the creds seen by the
> >> destruction notification generated by mntput() appears to come from
> >> the last fputter.
> >>
> >> (3) security_post_notification() is called for each queue that we might
> >> want to post a notification into, thereby allowing the LSM to prevent
> >> covert communications.
> >>
> >> (?) Do I need to add security_set_watch(), say, to rule on whether a watch
> >> may be set in the first place? I might need to add a variant per
> >> watch-type.
> >>
> >> (?) Do I really need to keep track of the process creds in which an
> >> implicit object destruction happened? For example, imagine you create
> >> an fd with fsopen()/fsmount(). It is marked to dissolve the mount it
> >> refers to on close unless move_mount() clears that flag. Now, imagine
> >> someone looking at that fd through procfs at the same time as you exit
> >> due to an error. The LSM sees the destruction notification come from
> >> the looker if they happen to do their fput() after yours.
> >
> > I remain unconvinced that (1), (2), (3), and the final (?) above are a good idea.
> >
> > For SELinux, I would expect that one would implement a collection of per watch-type WATCH permission checks on the target object (or to some well-defined object label like the kernel SID if there is no object) that allow receipt of all notifications of that watch-type for objects related to the target object, where "related to" is defined per watch-type.
> >
> > I wouldn't expect SELinux to implement security_post_notification() at all. I can't see how one can construct a meaningful, stable policy for it. I'd argue that the triggering process is not posting the notification; the kernel is posting the notification and the watcher has been authorized to receive it.
>
> I cannot agree. There is an explicit action by a subject that results
> in information being delivered to an object. Just like a signal or a
> UDP packet delivery. Smack handles this kind of thing just fine. The
> internal mechanism that results in the access is irrelevant from
> this viewpoint. I can understand how a mechanism like SELinux that
> works on finer granularity might view it differently.
I think you really need to give an example of a coherent policy that
needs this. As it stands, your analogy seems confusing. If someone
changes the system clock, we don't restrict who is allowed to be
notified (via, for example, TFD_TIMER_CANCEL_ON_SET) that the clock
was changed based on who changed the clock. Similarly, if someone
tries to receive a packet on a socket, we check whether they have the
right to receive on that socket (from the endpoint in question) and,
if the sender is local, whether the sender can send to that socket.
We do not check whether the sender can send to the receiver.
The signal example is inapplicable. Sending a signal to a process is
an explicit action done to that process, and it can easily adversely
affect the target. Of course it requires permission.
--Andy
On Fri, 07 Jun 2019 15:18:19 +0100
David Howells <[email protected]> wrote:
> 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);
> ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
>
> then a notification can be set up to report notifications via that queue:
>
> struct watch_notification_filter filter = {
> .nr_filters = 1,
> .filters = {
> [0] = {
> .type = WATCH_TYPE_KEY_NOTIFY,
> .subtype_filter[0] = UINT_MAX,
> },
> },
> };
> ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
> keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01);
One little nit: it seems that keyctl_watch_key is actually spelled
keyctl(KEYCTL_WATCH_KEY, ...).
jon
Jonathan Corbet <[email protected]> wrote:
> > keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01);
>
> One little nit: it seems that keyctl_watch_key is actually spelled
> keyctl(KEYCTL_WATCH_KEY, ...).
Yeah - but there'll be a wrapper for it in -lkeyutils. The syscalls I added
in other patches are, technically, referred to syscall(__NR_xxx, ...) at the
moment too.
David
On 6/10/2019 9:42 AM, Andy Lutomirski wrote:
> On Mon, Jun 10, 2019 at 9:34 AM Casey Schaufler <[email protected]> wrote:
>> On 6/10/2019 8:21 AM, Stephen Smalley wrote:
>>> On 6/7/19 10:17 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:
>>>>
>>>> (1) Mount topology events, such as mounting, unmounting, mount expiry,
>>>> mount reconfiguration.
>>>>
>>>> (2) Superblock events, such as R/W<->R/O changes, quota overrun and I/O
>>>> errors (not complete yet).
>>>>
>>>> (3) Key/keyring events, such as creating, linking and removal of keys.
>>>>
>>>> (4) General device events (single common queue) including:
>>>>
>>>> - Block layer events, such as device errors
>>>>
>>>> - USB subsystem events, such as device/bus attach/remove, device
>>>> reset, device errors.
>>>>
>>>> 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. To further aid this, the fsinfo() syscall
>>>> on which this patch series depends, provides a way to access superblock and
>>>> mount information in binary form without the need to parse /proc/mounts.
>>>>
>>>>
>>>> LSM support is included, but controversial:
>>>>
>>>> (1) The creds of the process that did the fput() that reduced the refcount
>>>> to zero are cached in the file struct.
>>>>
>>>> (2) __fput() overrides the current creds with the creds from (1) whilst
>>>> doing the cleanup, thereby making sure that the creds seen by the
>>>> destruction notification generated by mntput() appears to come from
>>>> the last fputter.
>>>>
>>>> (3) security_post_notification() is called for each queue that we might
>>>> want to post a notification into, thereby allowing the LSM to prevent
>>>> covert communications.
>>>>
>>>> (?) Do I need to add security_set_watch(), say, to rule on whether a watch
>>>> may be set in the first place? I might need to add a variant per
>>>> watch-type.
>>>>
>>>> (?) Do I really need to keep track of the process creds in which an
>>>> implicit object destruction happened? For example, imagine you create
>>>> an fd with fsopen()/fsmount(). It is marked to dissolve the mount it
>>>> refers to on close unless move_mount() clears that flag. Now, imagine
>>>> someone looking at that fd through procfs at the same time as you exit
>>>> due to an error. The LSM sees the destruction notification come from
>>>> the looker if they happen to do their fput() after yours.
>>> I remain unconvinced that (1), (2), (3), and the final (?) above are a good idea.
>>>
>>> For SELinux, I would expect that one would implement a collection of per watch-type WATCH permission checks on the target object (or to some well-defined object label like the kernel SID if there is no object) that allow receipt of all notifications of that watch-type for objects related to the target object, where "related to" is defined per watch-type.
>>>
>>> I wouldn't expect SELinux to implement security_post_notification() at all. I can't see how one can construct a meaningful, stable policy for it. I'd argue that the triggering process is not posting the notification; the kernel is posting the notification and the watcher has been authorized to receive it.
>> I cannot agree. There is an explicit action by a subject that results
>> in information being delivered to an object. Just like a signal or a
>> UDP packet delivery. Smack handles this kind of thing just fine. The
>> internal mechanism that results in the access is irrelevant from
>> this viewpoint. I can understand how a mechanism like SELinux that
>> works on finer granularity might view it differently.
> I think you really need to give an example of a coherent policy that
> needs this.
I keep telling you, and you keep ignoring what I say.
> As it stands, your analogy seems confusing.
It's pretty simple. I have given both the abstract
and examples.
> If someone
> changes the system clock, we don't restrict who is allowed to be
> notified (via, for example, TFD_TIMER_CANCEL_ON_SET) that the clock
> was changed based on who changed the clock.
That's right. The system clock is not an object that
unprivileged processes can modify. In fact, it is not
an object at all. If you care to look, you will see that
Smack does nothing with the clock.
> Similarly, if someone
> tries to receive a packet on a socket, we check whether they have the
> right to receive on that socket (from the endpoint in question) and,
> if the sender is local, whether the sender can send to that socket.
> We do not check whether the sender can send to the receiver.
Bzzzt! Smack sure does.
> The signal example is inapplicable.
From a modeling viewpoint the actions are identical.
> Sending a signal to a process is
> an explicit action done to that process, and it can easily adversely
> affect the target. Of course it requires permission.
>
> --Andy
> On Jun 10, 2019, at 11:01 AM, Casey Schaufler <[email protected]> wrote:
>
>> On 6/10/2019 9:42 AM, Andy Lutomirski wrote:
>>> On Mon, Jun 10, 2019 at 9:34 AM Casey Schaufler <[email protected]> wrote:
>>>> On 6/10/2019 8:21 AM, Stephen Smalley wrote:
>>>>> On 6/7/19 10:17 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:
>>>>>
>>>>> (1) Mount topology events, such as mounting, unmounting, mount expiry,
>>>>> mount reconfiguration.
>>>>>
>>>>> (2) Superblock events, such as R/W<->R/O changes, quota overrun and I/O
>>>>> errors (not complete yet).
>>>>>
>>>>> (3) Key/keyring events, such as creating, linking and removal of keys.
>>>>>
>>>>> (4) General device events (single common queue) including:
>>>>>
>>>>> - Block layer events, such as device errors
>>>>>
>>>>> - USB subsystem events, such as device/bus attach/remove, device
>>>>> reset, device errors.
>>>>>
>>>>> 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. To further aid this, the fsinfo() syscall
>>>>> on which this patch series depends, provides a way to access superblock and
>>>>> mount information in binary form without the need to parse /proc/mounts.
>>>>>
>>>>>
>>>>> LSM support is included, but controversial:
>>>>>
>>>>> (1) The creds of the process that did the fput() that reduced the refcount
>>>>> to zero are cached in the file struct.
>>>>>
>>>>> (2) __fput() overrides the current creds with the creds from (1) whilst
>>>>> doing the cleanup, thereby making sure that the creds seen by the
>>>>> destruction notification generated by mntput() appears to come from
>>>>> the last fputter.
>>>>>
>>>>> (3) security_post_notification() is called for each queue that we might
>>>>> want to post a notification into, thereby allowing the LSM to prevent
>>>>> covert communications.
>>>>>
>>>>> (?) Do I need to add security_set_watch(), say, to rule on whether a watch
>>>>> may be set in the first place? I might need to add a variant per
>>>>> watch-type.
>>>>>
>>>>> (?) Do I really need to keep track of the process creds in which an
>>>>> implicit object destruction happened? For example, imagine you create
>>>>> an fd with fsopen()/fsmount(). It is marked to dissolve the mount it
>>>>> refers to on close unless move_mount() clears that flag. Now, imagine
>>>>> someone looking at that fd through procfs at the same time as you exit
>>>>> due to an error. The LSM sees the destruction notification come from
>>>>> the looker if they happen to do their fput() after yours.
>>>> I remain unconvinced that (1), (2), (3), and the final (?) above are a good idea.
>>>>
>>>> For SELinux, I would expect that one would implement a collection of per watch-type WATCH permission checks on the target object (or to some well-defined object label like the kernel SID if there is no object) that allow receipt of all notifications of that watch-type for objects related to the target object, where "related to" is defined per watch-type.
>>>>
>>>> I wouldn't expect SELinux to implement security_post_notification() at all. I can't see how one can construct a meaningful, stable policy for it. I'd argue that the triggering process is not posting the notification; the kernel is posting the notification and the watcher has been authorized to receive it.
>>> I cannot agree. There is an explicit action by a subject that results
>>> in information being delivered to an object. Just like a signal or a
>>> UDP packet delivery. Smack handles this kind of thing just fine. The
>>> internal mechanism that results in the access is irrelevant from
>>> this viewpoint. I can understand how a mechanism like SELinux that
>>> works on finer granularity might view it differently.
>> I think you really need to give an example of a coherent policy that
>> needs this.
>
> I keep telling you, and you keep ignoring what I say.
>
>> As it stands, your analogy seems confusing.
>
> It's pretty simple. I have given both the abstract
> and examples.
You gave the /dev/null example, which is inapplicable to this patchset.
>
>> If someone
>> changes the system clock, we don't restrict who is allowed to be
>> notified (via, for example, TFD_TIMER_CANCEL_ON_SET) that the clock
>> was changed based on who changed the clock.
>
> That's right. The system clock is not an object that
> unprivileged processes can modify. In fact, it is not
> an object at all. If you care to look, you will see that
> Smack does nothing with the clock.
And this is different from the mount tree how?
>
>> Similarly, if someone
>> tries to receive a packet on a socket, we check whether they have the
>> right to receive on that socket (from the endpoint in question) and,
>> if the sender is local, whether the sender can send to that socket.
>> We do not check whether the sender can send to the receiver.
>
> Bzzzt! Smack sure does.
This seems dubious. I’m still trying to get you to explain to a non-Smack person why this makes sense.
>
>> The signal example is inapplicable.
>
> From a modeling viewpoint the actions are identical.
This seems incorrect to me and, I think, to most everyone else reading this. Can you explain?
In SELinux-ese, when you write to a file, the subject is the writer and the object is the file. When you send a signal to a process, the object is the target process.
On 6/10/2019 11:22 AM, Andy Lutomirski wrote:
>> On Jun 10, 2019, at 11:01 AM, Casey Schaufler <[email protected]> wrote:
>>
>>> On 6/10/2019 9:42 AM, Andy Lutomirski wrote:
>>>> On Mon, Jun 10, 2019 at 9:34 AM Casey Schaufler <[email protected]> wrote:
>>>>> On 6/10/2019 8:21 AM, Stephen Smalley wrote:
>>>>>> On 6/7/19 10:17 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:
>>>>>>
>>>>>> (1) Mount topology events, such as mounting, unmounting, mount expiry,
>>>>>> mount reconfiguration.
>>>>>>
>>>>>> (2) Superblock events, such as R/W<->R/O changes, quota overrun and I/O
>>>>>> errors (not complete yet).
>>>>>>
>>>>>> (3) Key/keyring events, such as creating, linking and removal of keys.
>>>>>>
>>>>>> (4) General device events (single common queue) including:
>>>>>>
>>>>>> - Block layer events, such as device errors
>>>>>>
>>>>>> - USB subsystem events, such as device/bus attach/remove, device
>>>>>> reset, device errors.
>>>>>>
>>>>>> 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. To further aid this, the fsinfo() syscall
>>>>>> on which this patch series depends, provides a way to access superblock and
>>>>>> mount information in binary form without the need to parse /proc/mounts.
>>>>>>
>>>>>>
>>>>>> LSM support is included, but controversial:
>>>>>>
>>>>>> (1) The creds of the process that did the fput() that reduced the refcount
>>>>>> to zero are cached in the file struct.
>>>>>>
>>>>>> (2) __fput() overrides the current creds with the creds from (1) whilst
>>>>>> doing the cleanup, thereby making sure that the creds seen by the
>>>>>> destruction notification generated by mntput() appears to come from
>>>>>> the last fputter.
>>>>>>
>>>>>> (3) security_post_notification() is called for each queue that we might
>>>>>> want to post a notification into, thereby allowing the LSM to prevent
>>>>>> covert communications.
>>>>>>
>>>>>> (?) Do I need to add security_set_watch(), say, to rule on whether a watch
>>>>>> may be set in the first place? I might need to add a variant per
>>>>>> watch-type.
>>>>>>
>>>>>> (?) Do I really need to keep track of the process creds in which an
>>>>>> implicit object destruction happened? For example, imagine you create
>>>>>> an fd with fsopen()/fsmount(). It is marked to dissolve the mount it
>>>>>> refers to on close unless move_mount() clears that flag. Now, imagine
>>>>>> someone looking at that fd through procfs at the same time as you exit
>>>>>> due to an error. The LSM sees the destruction notification come from
>>>>>> the looker if they happen to do their fput() after yours.
>>>>> I remain unconvinced that (1), (2), (3), and the final (?) above are a good idea.
>>>>>
>>>>> For SELinux, I would expect that one would implement a collection of per watch-type WATCH permission checks on the target object (or to some well-defined object label like the kernel SID if there is no object) that allow receipt of all notifications of that watch-type for objects related to the target object, where "related to" is defined per watch-type.
>>>>>
>>>>> I wouldn't expect SELinux to implement security_post_notification() at all. I can't see how one can construct a meaningful, stable policy for it. I'd argue that the triggering process is not posting the notification; the kernel is posting the notification and the watcher has been authorized to receive it.
>>>> I cannot agree. There is an explicit action by a subject that results
>>>> in information being delivered to an object. Just like a signal or a
>>>> UDP packet delivery. Smack handles this kind of thing just fine. The
>>>> internal mechanism that results in the access is irrelevant from
>>>> this viewpoint. I can understand how a mechanism like SELinux that
>>>> works on finer granularity might view it differently.
>>> I think you really need to give an example of a coherent policy that
>>> needs this.
>> I keep telling you, and you keep ignoring what I say.
>>
>>> As it stands, your analogy seems confusing.
>> It's pretty simple. I have given both the abstract
>> and examples.
> You gave the /dev/null example, which is inapplicable to this patchset.
That addressed an explicit objection, and pointed out
an exception to a generality you had asserted, which was
not true. It's also a red herring regarding the current
discussion.
>>> If someone
>>> changes the system clock, we don't restrict who is allowed to be
>>> notified (via, for example, TFD_TIMER_CANCEL_ON_SET) that the clock
>>> was changed based on who changed the clock.
>> That's right. The system clock is not an object that
>> unprivileged processes can modify. In fact, it is not
>> an object at all. If you care to look, you will see that
>> Smack does nothing with the clock.
> And this is different from the mount tree how?
The mount tree can be modified by unprivileged users.
If nothing that unprivileged users can do to the mount
tree can trigger a notification you are correct, the
mount tree is very like the system clock. Is that the
case?
>>> Similarly, if someone
>>> tries to receive a packet on a socket, we check whether they have the
>>> right to receive on that socket (from the endpoint in question) and,
>>> if the sender is local, whether the sender can send to that socket.
>>> We do not check whether the sender can send to the receiver.
>> Bzzzt! Smack sure does.
> This seems dubious. I’m still trying to get you to explain to a non-Smack person why this makes sense.
Process A sends a packet to process B.
If A has access to TopSecret data and B is not
allowed to see TopSecret data, the delivery should
be prevented. Is that nonsensical?
>>> The signal example is inapplicable.
>> From a modeling viewpoint the actions are identical.
> This seems incorrect to me
What would be correct then? Some convoluted combination
of system entities that aren't owned or controlled by
any mechanism?
> and, I think, to most everyone else reading this.
That's quite the assertion. You may even be correct.
> Can you explain?
>
> In SELinux-ese, when you write to a file, the subject is the writer and the object is the file. When you send a signal to a process, the object is the target process.
YES!!!!!!!!!!!!
And when a process triggers a notification it is the subject
and the watching process is the object!
Subject == active entity
Object == passive entity
Triggering an event is, like calling kill(), an action!
On Mon, Jun 10, 2019 at 12:34 PM Casey Schaufler <[email protected]> wrote:
> >>> I think you really need to give an example of a coherent policy that
> >>> needs this.
> >> I keep telling you, and you keep ignoring what I say.
> >>
> >>> As it stands, your analogy seems confusing.
> >> It's pretty simple. I have given both the abstract
> >> and examples.
> > You gave the /dev/null example, which is inapplicable to this patchset.
>
> That addressed an explicit objection, and pointed out
> an exception to a generality you had asserted, which was
> not true. It's also a red herring regarding the current
> discussion.
This argument is pointless.
Please humor me and just give me an example. If you think you have
already done so, feel free to repeat yourself. If you have no
example, then please just say so.
>
> >>> If someone
> >>> changes the system clock, we don't restrict who is allowed to be
> >>> notified (via, for example, TFD_TIMER_CANCEL_ON_SET) that the clock
> >>> was changed based on who changed the clock.
> >> That's right. The system clock is not an object that
> >> unprivileged processes can modify. In fact, it is not
> >> an object at all. If you care to look, you will see that
> >> Smack does nothing with the clock.
> > And this is different from the mount tree how?
>
> The mount tree can be modified by unprivileged users.
> If nothing that unprivileged users can do to the mount
> tree can trigger a notification you are correct, the
> mount tree is very like the system clock. Is that the
> case?
The mount tree can't be modified by unprivileged users, unless a
privileged user very carefully configured it as such. An unprivileged
user can create a new userns and a new mount ns, but then they're
modifying a whole different mount tree.
>
> >>> Similarly, if someone
> >>> tries to receive a packet on a socket, we check whether they have the
> >>> right to receive on that socket (from the endpoint in question) and,
> >>> if the sender is local, whether the sender can send to that socket.
> >>> We do not check whether the sender can send to the receiver.
> >> Bzzzt! Smack sure does.
> > This seems dubious. I’m still trying to get you to explain to a non-Smack person why this makes sense.
>
> Process A sends a packet to process B.
> If A has access to TopSecret data and B is not
> allowed to see TopSecret data, the delivery should
> be prevented. Is that nonsensical?
It makes sense. As I see it, the way that a sensible policy should do
this is by making sure that there are no sockets, pipes, etc that
Process A can write and that Process B can read.
If you really want to prevent a malicious process with TopSecret data
from sending it to a different process, then you can't use Linux on
x86 or ARM. Maybe that will be fixed some day, but you're going to
need to use an extremely tight sandbox to make this work.
>
> >>> The signal example is inapplicable.
> >> From a modeling viewpoint the actions are identical.
> > This seems incorrect to me
>
> What would be correct then? Some convoluted combination
> of system entities that aren't owned or controlled by
> any mechanism?
>
POSIX signal restrictions aren't there to prevent two processes from
communicating. They're there to prevent the sender from manipulating
or crashing the receiver without appropriate privilege.
> > and, I think, to most everyone else reading this.
>
> That's quite the assertion. You may even be correct.
>
> > Can you explain?
> >
> > In SELinux-ese, when you write to a file, the subject is the writer and the object is the file. When you send a signal to a process, the object is the target process.
>
> YES!!!!!!!!!!!!
>
> And when a process triggers a notification it is the subject
> and the watching process is the object!
>
> Subject == active entity
> Object == passive entity
>
> Triggering an event is, like calling kill(), an action!
>
And here is where I disagree with your interpretation. Triggering an
event is a side effect of writing to the file. There are *two*
security relevant actions, not one, and they are:
First, the write:
Subject == the writer
Action == write
Object == the file
Then the event, which could be modeled in a couple of ways:
Subject == the file
Action == notify
Object == the recipient
or
Subject == the recipient
Action == watch
Object == the file
By conflating these two actions into one, you've made the modeling
very hard, and you start running into all these nasty questions like
"who actually closed this open file"
On 6/10/2019 12:53 PM, Andy Lutomirski wrote:
> On Mon, Jun 10, 2019 at 12:34 PM Casey Schaufler <[email protected]> wrote:
>>>>> I think you really need to give an example of a coherent policy that
>>>>> needs this.
>>>> I keep telling you, and you keep ignoring what I say.
>>>>
>>>>> As it stands, your analogy seems confusing.
>>>> It's pretty simple. I have given both the abstract
>>>> and examples.
>>> You gave the /dev/null example, which is inapplicable to this patchset.
>> That addressed an explicit objection, and pointed out
>> an exception to a generality you had asserted, which was
>> not true. It's also a red herring regarding the current
>> discussion.
> This argument is pointless.
>
> Please humor me and just give me an example. If you think you have
> already done so, feel free to repeat yourself. If you have no
> example, then please just say so.
To repeat the /dev/null example:
Process A and process B both open /dev/null.
A and B can write and read to their hearts content
to/from /dev/null without ever once communicating.
The mutual accessibility of /dev/null in no way implies that
A and B can communicate. If A can set a watch on /dev/null,
and B triggers an event, there still has to be an access
check on the delivery of the event because delivering an event
to A is not an action on /dev/null, but on A.
>
>>>>> If someone
>>>>> changes the system clock, we don't restrict who is allowed to be
>>>>> notified (via, for example, TFD_TIMER_CANCEL_ON_SET) that the clock
>>>>> was changed based on who changed the clock.
>>>> That's right. The system clock is not an object that
>>>> unprivileged processes can modify. In fact, it is not
>>>> an object at all. If you care to look, you will see that
>>>> Smack does nothing with the clock.
>>> And this is different from the mount tree how?
>> The mount tree can be modified by unprivileged users.
>> If nothing that unprivileged users can do to the mount
>> tree can trigger a notification you are correct, the
>> mount tree is very like the system clock. Is that the
>> case?
> The mount tree can't be modified by unprivileged users, unless a
> privileged user very carefully configured it as such.
"Unless" means *is* possible. In which case access control is
required. I will admit to being less then expert on the extent
to which mounts can be done without privilege.
> An unprivileged
> user can create a new userns and a new mount ns, but then they're
> modifying a whole different mount tree.
Within those namespaces you can still have multiple users,
constrained be system access control policy.
>
>>>>> Similarly, if someone
>>>>> tries to receive a packet on a socket, we check whether they have the
>>>>> right to receive on that socket (from the endpoint in question) and,
>>>>> if the sender is local, whether the sender can send to that socket.
>>>>> We do not check whether the sender can send to the receiver.
>>>> Bzzzt! Smack sure does.
>>> This seems dubious. I’m still trying to get you to explain to a non-Smack person why this makes sense.
>> Process A sends a packet to process B.
>> If A has access to TopSecret data and B is not
>> allowed to see TopSecret data, the delivery should
>> be prevented. Is that nonsensical?
> It makes sense. As I see it, the way that a sensible policy should do
> this is by making sure that there are no sockets, pipes, etc that
> Process A can write and that Process B can read.
You can't explain UDP controls without doing the access check
on packet delivery. The sendmsg() succeeds when the packet leaves
the sender. There doesn't even have to be a socket bound to the
port. The only opportunity you have for control is on packet
delivery, which is the only point at which you can have the
information required.
> If you really want to prevent a malicious process with TopSecret data
> from sending it to a different process, then you can't use Linux on
> x86 or ARM. Maybe that will be fixed some day, but you're going to
> need to use an extremely tight sandbox to make this work.
I won't be commenting on that.
>
>>>>> The signal example is inapplicable.
>>>> From a modeling viewpoint the actions are identical.
>>> This seems incorrect to me
>> What would be correct then? Some convoluted combination
>> of system entities that aren't owned or controlled by
>> any mechanism?
>>
> POSIX signal restrictions aren't there to prevent two processes from
> communicating. They're there to prevent the sender from manipulating
> or crashing the receiver without appropriate privilege.
POSIX signal restrictions have a long history. In the P10031e/2c
debates both communication and manipulation where seriously
considered. I would say both are true.
>>> and, I think, to most everyone else reading this.
>> That's quite the assertion. You may even be correct.
>>
>>> Can you explain?
>>>
>>> In SELinux-ese, when you write to a file, the subject is the writer and the object is the file. When you send a signal to a process, the object is the target process.
>> YES!!!!!!!!!!!!
>>
>> And when a process triggers a notification it is the subject
>> and the watching process is the object!
>>
>> Subject == active entity
>> Object == passive entity
>>
>> Triggering an event is, like calling kill(), an action!
>>
> And here is where I disagree with your interpretation. Triggering an
> event is a side effect of writing to the file. There are *two*
> security relevant actions, not one, and they are:
>
> First, the write:
>
> Subject == the writer
> Action == write
> Object == the file
>
> Then the event, which could be modeled in a couple of ways:
>
> Subject == the file
Files are not subjects. They are passive entities.
> Action == notify
> Object == the recipient
>
> or
>
> Subject == the recipient
> Action == watch
> Object == the file
>
> By conflating these two actions into one, you've made the modeling
> very hard, and you start running into all these nasty questions like
> "who actually closed this open file"
No, I've made the code more difficult. You can not call
the file a subject. That is just wrong. It's not a valid
model.
Casey Schaufler <[email protected]> wrote:
> Process A and process B both open /dev/null.
> A and B can write and read to their hearts content
> to/from /dev/null without ever once communicating.
> The mutual accessibility of /dev/null in no way implies that
> A and B can communicate. If A can set a watch on /dev/null,
> and B triggers an event, there still has to be an access
> check on the delivery of the event because delivering an event
> to A is not an action on /dev/null, but on A.
If a process has the privilege, it appears that fanotify() allows that process
to see others accessing /dev/null (FAN_ACCESS, FAN_ACCESS_PERM). There don't
seem to be any LSM checks there either.
On the other hand, the privilege required is CAP_SYS_ADMIN,
> > The mount tree can't be modified by unprivileged users, unless a
> > privileged user very carefully configured it as such.
>
> "Unless" means *is* possible. In which case access control is
> required. I will admit to being less then expert on the extent
> to which mounts can be done without privilege.
Automounts in network filesystems, for example.
The initial mount of the network filesystem requires local privilege, but then
mountpoints are managed with remote privilege as granted by things like
kerberos tickets. The local kernel has no control.
If you have CONFIG_AFS_FS enabled in your kernel, for example, and you install
the keyutils package (dnf, rpm, apt, etc.), then you should be able to do:
mount -t afs none /mnt -o dyn
ls /afs/grand.central.org/software/
for example. That will go through a couple of automount points. Assuming you
don't have a kerberos login on those servers, however, you shouldn't be able
to add new mountpoints.
Someone watching the mount topology can see events when an automount is
enacted and when it expires, the latter being an event with the system as the
subject since the expiry is done on a timeout set by the kernel.
David
> On Jun 10, 2019, at 2:25 PM, Casey Schaufler <[email protected]> wrote:
>
>> On 6/10/2019 12:53 PM, Andy Lutomirski wrote:
>> On Mon, Jun 10, 2019 at 12:34 PM Casey Schaufler <[email protected]> wrote:
>>>>>> I think you really need to give an example of a coherent policy that
>>>>>> needs this.
>>>>> I keep telling you, and you keep ignoring what I say.
>>>>>
>>>>>> As it stands, your analogy seems confusing.
>>>>> It's pretty simple. I have given both the abstract
>>>>> and examples.
>>>> You gave the /dev/null example, which is inapplicable to this patchset.
>>> That addressed an explicit objection, and pointed out
>>> an exception to a generality you had asserted, which was
>>> not true. It's also a red herring regarding the current
>>> discussion.
>> This argument is pointless.
>>
>> Please humor me and just give me an example. If you think you have
>> already done so, feel free to repeat yourself. If you have no
>> example, then please just say so.
>
> To repeat the /dev/null example:
>
> Process A and process B both open /dev/null.
> A and B can write and read to their hearts content
> to/from /dev/null without ever once communicating.
> The mutual accessibility of /dev/null in no way implies that
> A and B can communicate. If A can set a watch on /dev/null,
> and B triggers an event, there still has to be an access
> check on the delivery of the event because delivering an event
> to A is not an action on /dev/null, but on A.
>
At discussed, this is an irrelevant straw man. This patch series does not produce events when this happens. I’m looking for a relevant example, please.
>
>
>> An unprivileged
>> user can create a new userns and a new mount ns, but then they're
>> modifying a whole different mount tree.
>
> Within those namespaces you can still have multiple users,
> constrained be system access control policy.
And the one doing the mounting will be constrained by MAC and DAC policy, as always. The namespace creator is, from the perspective of those processes, admin.
>
>>
>>>>>> Similarly, if someone
>>>>>> tries to receive a packet on a socket, we check whether they have the
>>>>>> right to receive on that socket (from the endpoint in question) and,
>>>>>> if the sender is local, whether the sender can send to that socket.
>>>>>> We do not check whether the sender can send to the receiver.
>>>>> Bzzzt! Smack sure does.
>>>> This seems dubious. I’m still trying to get you to explain to a non-Smack person why this makes sense.
>>> Process A sends a packet to process B.
>>> If A has access to TopSecret data and B is not
>>> allowed to see TopSecret data, the delivery should
>>> be prevented. Is that nonsensical?
>> It makes sense. As I see it, the way that a sensible policy should do
>> this is by making sure that there are no sockets, pipes, etc that
>> Process A can write and that Process B can read.
>
> You can't explain UDP controls without doing the access check
> on packet delivery. The sendmsg() succeeds when the packet leaves
> the sender. There doesn't even have to be a socket bound to the
> port. The only opportunity you have for control is on packet
> delivery, which is the only point at which you can have the
> information required.
Huh? You sendmsg() from an address to an address. My point is that, for most purposes, that’s all the information that’s needed.
>
>> If you really want to prevent a malicious process with TopSecret data
>> from sending it to a different process, then you can't use Linux on
>> x86 or ARM. Maybe that will be fixed some day, but you're going to
>> need to use an extremely tight sandbox to make this work.
>
> I won't be commenting on that.
Then why is preventing this is an absolute requirement? It’s unattainable.
>
>>
>>>>>> The signal example is inapplicable.
>>>>> From a modeling viewpoint the actions are identical.
>>>> This seems incorrect to me
>>> What would be correct then? Some convoluted combination
>>> of system entities that aren't owned or controlled by
>>> any mechanism?
>>>
>> POSIX signal restrictions aren't there to prevent two processes from
>> communicating. They're there to prevent the sender from manipulating
>> or crashing the receiver without appropriate privilege.
>
> POSIX signal restrictions have a long history. In the P10031e/2c
> debates both communication and manipulation where seriously
> considered. I would say both are true.
>
>>>> and, I think, to most everyone else reading this.
>>> That's quite the assertion. You may even be correct.
>>>
>>>> Can you explain?
>>>>
>>>> In SELinux-ese, when you write to a file, the subject is the writer and the object is the file. When you send a signal to a process, the object is the target process.
>>> YES!!!!!!!!!!!!
>>>
>>> And when a process triggers a notification it is the subject
>>> and the watching process is the object!
>>>
>>> Subject == active entity
>>> Object == passive entity
>>>
>>> Triggering an event is, like calling kill(), an action!
>>>
>> And here is where I disagree with your interpretation. Triggering an
>> event is a side effect of writing to the file. There are *two*
>> security relevant actions, not one, and they are:
>>
>> First, the write:
>>
>> Subject == the writer
>> Action == write
>> Object == the file
>>
>> Then the event, which could be modeled in a couple of ways:
>>
>> Subject == the file
>
> Files are not subjects. They are passive entities.
>
>> Action == notify
>> Object == the recipient
Great. Then use the variant below.
>>
>> or
>>
>> Subject == the recipient
>> Action == watch
>> Object == the file
>>
>> By conflating these two actions into one, you've made the modeling
>> very hard, and you start running into all these nasty questions like
>> "who actually closed this open file"
>
> No, I've made the code more difficult.
> You can not call
> the file a subject. That is just wrong. It's not a valid
> model.
You’ve ignored the “Action == watch” variant. Do you care to comment?
To see if we can try and make progress on this, can we try and come at this
from another angle: what do LSMs *actually* need to do this? And I grant that
each LSM might require different things.
-~-
[A] There are a bunch of things available, some of which may be coincident,
depending on the context:
(1) The creds of the process that created a watch_queue (ie. opened
/dev/watch_queue).
(2) The creds of the process that set a watch (ie. called watch_sb,
KEYCTL_NOTIFY, ...);
(3) The creds of the process that tripped the event (which might be the
system).
(4) The security attributes of the object on which the watch was set (uid,
gid, mode, labels).
(5) The security attributes of the object on which the event was tripped.
(6) The security attributes of all the objects between the object in (5) and
the object in (4), assuming we work from (5) towards (4) if the two
aren't coincident (WATCH_INFO_RECURSIVE).
At the moment, when post_one_notification() wants to write a notification into
a queue, it calls security_post_notification() to ask if it should be allowed
to do so. This is passed (1) and (3) above plus the notification record.
[B] There are a number of places I can usefully potentially add hooks:
(a) The point at which a watch queue is created (ie. /dev/watch_queue is
opened).
(b) The point at which a watch is set (ie. watch_sb).
(c) The point at which a notification is generated (ie. an automount point is
tripped).
(d) The point at which a notification is delivered (ie. we write the message
into the queue).
(e) All the points at which we walk over an object in a chain from (c) to
find the watch on which we can effect (d) (eg. we walk rootwards from a
mountpoint to find watches on a branch in the mount topology).
[C] Problems that need to be resolved:
(x) Do I need to put a security pointer in struct watch for the active LSM to
fill in? If so, I presume this would need passing to
security_post_notification().
(y) What checks should be done on object destruction after final put and what
contexts need to be supplied?
This one is made all the harder because the creds that are in force when
close(), exit(), exec(), dup2(), etc. close a file descriptor might need
to be propagated to deferred-fput, which must in turn propagate them to
af_unix-cleanup, and thence back to deferred-fput and thence to implicit
unmount (dissolve_on_fput()[*]).
[*] Though it should be noted that if this happens, the subtree cannot be
attached to the root of a namespace.
Further, if several processes are sharing a file object, it's not
predictable as to which process the final notification will come from.
(z) Do intermediate objects, say in a mount topology notification, actually
need to be checked against the watcher's creds? For a mount topology
notification, would this require calling inode_permission() for each
intervening directory?
Doing that might be impractical as it would probably have to be done
outside of of the RCU read lock and the filesystem ->permission() hooks
might want to sleep (to touch disk or talk to a server).
David
On 6/10/19 8:13 PM, Andy Lutomirski wrote:
>
>
>> On Jun 10, 2019, at 2:25 PM, Casey Schaufler <[email protected]> wrote:
>>
>>> On 6/10/2019 12:53 PM, Andy Lutomirski wrote:
>>> On Mon, Jun 10, 2019 at 12:34 PM Casey Schaufler <[email protected]> wrote:
>>>>>>> I think you really need to give an example of a coherent policy that
>>>>>>> needs this.
>>>>>> I keep telling you, and you keep ignoring what I say.
>>>>>>
>>>>>>> As it stands, your analogy seems confusing.
>>>>>> It's pretty simple. I have given both the abstract
>>>>>> and examples.
>>>>> You gave the /dev/null example, which is inapplicable to this patchset.
>>>> That addressed an explicit objection, and pointed out
>>>> an exception to a generality you had asserted, which was
>>>> not true. It's also a red herring regarding the current
>>>> discussion.
>>> This argument is pointless.
>>>
>>> Please humor me and just give me an example. If you think you have
>>> already done so, feel free to repeat yourself. If you have no
>>> example, then please just say so.
>>
>> To repeat the /dev/null example:
>>
>> Process A and process B both open /dev/null.
>> A and B can write and read to their hearts content
>> to/from /dev/null without ever once communicating.
>> The mutual accessibility of /dev/null in no way implies that
>> A and B can communicate. If A can set a watch on /dev/null,
>> and B triggers an event, there still has to be an access
>> check on the delivery of the event because delivering an event
>> to A is not an action on /dev/null, but on A.
>>
>
> At discussed, this is an irrelevant straw man. This patch series does not produce events when this happens. I’m looking for a relevant example, please.
>>
>>
>>> An unprivileged
>>> user can create a new userns and a new mount ns, but then they're
>>> modifying a whole different mount tree.
>>
>> Within those namespaces you can still have multiple users,
>> constrained be system access control policy.
>
> And the one doing the mounting will be constrained by MAC and DAC policy, as always. The namespace creator is, from the perspective of those processes, admin.
>
>>
>>>
>>>>>>> Similarly, if someone
>>>>>>> tries to receive a packet on a socket, we check whether they have the
>>>>>>> right to receive on that socket (from the endpoint in question) and,
>>>>>>> if the sender is local, whether the sender can send to that socket.
>>>>>>> We do not check whether the sender can send to the receiver.
>>>>>> Bzzzt! Smack sure does.
>>>>> This seems dubious. I’m still trying to get you to explain to a non-Smack person why this makes sense.
>>>> Process A sends a packet to process B.
>>>> If A has access to TopSecret data and B is not
>>>> allowed to see TopSecret data, the delivery should
>>>> be prevented. Is that nonsensical?
>>> It makes sense. As I see it, the way that a sensible policy should do
>>> this is by making sure that there are no sockets, pipes, etc that
>>> Process A can write and that Process B can read.
>>
>> You can't explain UDP controls without doing the access check
>> on packet delivery. The sendmsg() succeeds when the packet leaves
>> the sender. There doesn't even have to be a socket bound to the
>> port. The only opportunity you have for control is on packet
>> delivery, which is the only point at which you can have the
>> information required.
>
> Huh? You sendmsg() from an address to an address. My point is that, for most purposes, that’s all the information that’s needed.
>
>>
>>> If you really want to prevent a malicious process with TopSecret data
>>> from sending it to a different process, then you can't use Linux on
>>> x86 or ARM. Maybe that will be fixed some day, but you're going to
>>> need to use an extremely tight sandbox to make this work.
>>
>> I won't be commenting on that.
>
> Then why is preventing this is an absolute requirement? It’s unattainable.
>
>>
>>>
>>>>>>> The signal example is inapplicable.
>>>>>> From a modeling viewpoint the actions are identical.
>>>>> This seems incorrect to me
>>>> What would be correct then? Some convoluted combination
>>>> of system entities that aren't owned or controlled by
>>>> any mechanism?
>>>>
>>> POSIX signal restrictions aren't there to prevent two processes from
>>> communicating. They're there to prevent the sender from manipulating
>>> or crashing the receiver without appropriate privilege.
>>
>> POSIX signal restrictions have a long history. In the P10031e/2c
>> debates both communication and manipulation where seriously
>> considered. I would say both are true.
>>
>>>>> and, I think, to most everyone else reading this.
>>>> That's quite the assertion. You may even be correct.
>>>>
>>>>> Can you explain?
>>>>>
>>>>> In SELinux-ese, when you write to a file, the subject is the writer and the object is the file. When you send a signal to a process, the object is the target process.
>>>> YES!!!!!!!!!!!!
>>>>
>>>> And when a process triggers a notification it is the subject
>>>> and the watching process is the object!
>>>>
>>>> Subject == active entity
>>>> Object == passive entity
>>>>
>>>> Triggering an event is, like calling kill(), an action!
>>>>
>>> And here is where I disagree with your interpretation. Triggering an
>>> event is a side effect of writing to the file. There are *two*
>>> security relevant actions, not one, and they are:
>>>
>>> First, the write:
>>>
>>> Subject == the writer
>>> Action == write
>>> Object == the file
>>>
>>> Then the event, which could be modeled in a couple of ways:
>>>
>>> Subject == the file
>>
>> Files are not subjects. They are passive entities.
>>
>>> Action == notify
>>> Object == the recipient
>
> Great. Then use the variant below.
>
>>>
>>> or
>>>
>>> Subject == the recipient
>>> Action == watch
>>> Object == the file
>>>
>>> By conflating these two actions into one, you've made the modeling
>>> very hard, and you start running into all these nasty questions like
>>> "who actually closed this open file"
>>
>> No, I've made the code more difficult.
>> You can not call
>> the file a subject. That is just wrong. It's not a valid
>> model.
>
> You’ve ignored the “Action == watch” variant. Do you care to comment?
While I agree with this model in general, I will note two caveats when
trying to apply this to watches/notifications:
1) The object on which the notification was triggered and the object on
which the watch was placed are not necessarily the same and access to
one might not imply access to the other,
2) If notifications can be triggered by read-like operations (as in
fanotify, for example), then a "read" can be turned into a "write" flow
through a notification.
Whether or not these caveats are applicable to the notifications in this
series I am not clear.
On 6/11/19 10:21 AM, David Howells wrote:
> To see if we can try and make progress on this, can we try and come at this
> from another angle: what do LSMs *actually* need to do this? And I grant that
> each LSM might require different things.
I think part of the problem here is that the discussion is too abstract
and not dealing with the specifics of the notifications in question.
Those details matter.
>
> -~-
>
> [A] There are a bunch of things available, some of which may be coincident,
> depending on the context:
>
> (1) The creds of the process that created a watch_queue (ie. opened
> /dev/watch_queue).
These will be used when checking permissions to open /dev/watch_queue.
> (2) The creds of the process that set a watch (ie. called watch_sb,
> KEYCTL_NOTIFY, ...);
These will be used when checking permissions to set a watch.
> (3) The creds of the process that tripped the event (which might be the
> system).
These will be used when checking permission to perform whatever
operation tripped the event (if the event is triggered by a userspace
operation).
> (4) The security attributes of the object on which the watch was set (uid,
> gid, mode, labels).
These will be used when checking permissions to set the watch.
> (5) The security attributes of the object on which the event was tripped.
These will be used when checking permission to perform whatever
operation tripped the event.
> (6) The security attributes of all the objects between the object in (5) and
> the object in (4), assuming we work from (5) towards (4) if the two
> aren't coincident (WATCH_INFO_RECURSIVE).
Does this apply to anything other than mount notifications? And for
mount notifications, isn't the notification actually for a change to the
mount namespace, not a change to any file? Hence, the real "object" for
events that trigger mount notifications is the mount namespace, right?
The watched path is just a way of identifying a subtree of the mount
namespace for notifications - it isn't the real object being watched.
> At the moment, when post_one_notification() wants to write a notification into
> a queue, it calls security_post_notification() to ask if it should be allowed
> to do so. This is passed (1) and (3) above plus the notification record.
Not convinced we need this.
> [B] There are a number of places I can usefully potentially add hooks:
>
> (a) The point at which a watch queue is created (ie. /dev/watch_queue is
> opened).
Already covered by existing hooks on opening files.
> (b) The point at which a watch is set (ie. watch_sb).
Yes, this requires a hook and corresponding check.
> (c) The point at which a notification is generated (ie. an automount point is
> tripped).
Preferably covered by existing hooks on object accesses that would
generate notifications.
> (d) The point at which a notification is delivered (ie. we write the message
> into the queue).
Preferably not needed.
> (e) All the points at which we walk over an object in a chain from (c) to
> find the watch on which we can effect (d) (eg. we walk rootwards from a
> mountpoint to find watches on a branch in the mount topology).
Not necessary if the real object of mount notifications is the mount
namespace and if we do not support recursive notifications on e.g.
directories or some other object where the two can truly diverge.
> [C] Problems that need to be resolved:
>
> (x) Do I need to put a security pointer in struct watch for the active LSM to
> fill in? If so, I presume this would need passing to
> security_post_notification().
I don't see why or where it would get used.
> (y) What checks should be done on object destruction after final put and what
> contexts need to be supplied?
IMHO, no.
>
> This one is made all the harder because the creds that are in force when
> close(), exit(), exec(), dup2(), etc. close a file descriptor might need
> to be propagated to deferred-fput, which must in turn propagate them to
> af_unix-cleanup, and thence back to deferred-fput and thence to implicit
> unmount (dissolve_on_fput()[*]).
>
> [*] Though it should be noted that if this happens, the subtree cannot be
> attached to the root of a namespace.
>
> Further, if several processes are sharing a file object, it's not
> predictable as to which process the final notification will come from.
>
> (z) Do intermediate objects, say in a mount topology notification, actually
> need to be checked against the watcher's creds? For a mount topology
> notification, would this require calling inode_permission() for each
> intervening directory?
I don't think so, because the real object is the mount namespace, not
the individual directories.
>
> Doing that might be impractical as it would probably have to be done
> outside of of the RCU read lock and the filesystem ->permission() hooks
> might want to sleep (to touch disk or talk to a server).
On 6/11/2019 7:21 AM, David Howells wrote:
> To see if we can try and make progress on this, can we try and come at this
> from another angle: what do LSMs *actually* need to do this? And I grant that
> each LSM might require different things.
>
> -~-
>
> [A] There are a bunch of things available, some of which may be coincident,
> depending on the context:
>
> (1) The creds of the process that created a watch_queue (ie. opened
> /dev/watch_queue).
Smack needs this for the filesystem access required to open /dev/watch_queue.
> (2) The creds of the process that set a watch (ie. called watch_sb,
> KEYCTL_NOTIFY, ...);
Smack needs this to set a watch on any named object (file, key, ...).
Smack needs this as the object information for event delivery.
> (3) The creds of the process that tripped the event (which might be the
> system).
Smack needs this as the subject information for the event delivery.
> (4) The security attributes of the object on which the watch was set (uid,
> gid, mode, labels).
Smack needs this to set a watch on the named object (file, key, ...).
I am going to say that if you can't access an object you can't watch it.
I think that read access is sufficient provided that no one else can
see what watches I've created.
> (5) The security attributes of the object on which the event was tripped.
Smack does not need these for the event mechanism as that object isn't
involved in the event delivery, except as may be required by (4).
> (6) The security attributes of all the objects between the object in (5) and
> the object in (4), assuming we work from (5) towards (4) if the two
> aren't coincident (WATCH_INFO_RECURSIVE).
Smack needs these only as they would apply to (4).
> At the moment, when post_one_notification() wants to write a notification into
> a queue, it calls security_post_notification() to ask if it should be allowed
> to do so. This is passed (1) and (3) above plus the notification record.
Is "current" (2)? Smack needs (2) for the event delivery access check.
> [B] There are a number of places I can usefully potentially add hooks:
>
> (a) The point at which a watch queue is created (ie. /dev/watch_queue is
> opened).
Smack would not need a new check as the filesystem checks should suffice.
> (b) The point at which a watch is set (ie. watch_sb).
Smack would need a check to ensure the watcher has access
in cases where what is being watched is an object.
> (c) The point at which a notification is generated (ie. an automount point is
> tripped).
Smack does not require an explicit check here.
> (d) The point at which a notification is delivered (ie. we write the message
> into the queue).
Smack requires a check here. (2) as the object and (3) as the subject.
> (e) All the points at which we walk over an object in a chain from (c) to
> find the watch on which we can effect (d) (eg. we walk rootwards from a
> mountpoint to find watches on a branch in the mount topology).
Smack does not require anything beyond existing checks.
> [C] Problems that need to be resolved:
>
> (x) Do I need to put a security pointer in struct watch for the active LSM to
> fill in? If so, I presume this would need passing to
> security_post_notification().
Smack does not need this.
> (y) What checks should be done on object destruction after final put and what
> contexts need to be supplied?
Classically there is no such thing as filesystem object deletion.
By making it possible to set a watch on that you've inadvertently
added a security relevant action to the security model. :o
> This one is made all the harder because the creds that are in force when
> close(), exit(), exec(), dup2(), etc. close a file descriptor might need
> to be propagated to deferred-fput, which must in turn propagate them to
> af_unix-cleanup, and thence back to deferred-fput and thence to implicit
> unmount (dissolve_on_fput()[*]).
>
> [*] Though it should be noted that if this happens, the subtree cannot be
> attached to the root of a namespace.
>
> Further, if several processes are sharing a file object, it's not
> predictable as to which process the final notification will come from.
How about we don't add filesystem object deletion to the security model?
If what you really care about is removal of last filesystem reference
(last unlink) this shouldn't be any harder than any other watched change
(e.g. chmod) to the object.
If, on the other hand, you really want to watch for the last inode
reference and actual destruction of the thing I will suggest an
argument based on the model itself. If all of the directory entries
are unlinked the object no longer exists in the filesystem namespace.
If all of the fd's are closed (by whatever mechanism, we don't really
care) it no longer exists in any process space. At that point it has
no names, and is no longer a named object. No process (subject) deletes
it. They can't. They don't have access to it without a name. The
deletion is a system event, like setting the clock. There is no policy
that says when or even if the destruction occurs.
If and when the system gets around to cleaning up what has become
nothing more than system resources any outstanding watches can be
triggered using the system credential.
> (z) Do intermediate objects, say in a mount topology notification, actually
> need to be checked against the watcher's creds? For a mount topology
> notification, would this require calling inode_permission() for each
> intervening directory?
Smack would not require this. Paths are an illusion.
> Doing that might be impractical as it would probably have to be done
> outside of of the RCU read lock and the filesystem ->permission() hooks
> might want to sleep (to touch disk or talk to a server).
>
> David
Stephen Smalley <[email protected]> wrote:
> 2) If notifications can be triggered by read-like operations (as in fanotify,
> for example), then a "read" can be turned into a "write" flow through a
> notification.
I don't think any of the things can be classed as "read-like" operations. At
the moment, there are the following groups:
(1) Addition of objects (eg. key_link, mount).
(2) Modifications to things (eg. keyctl_write, remount).
(3) Removal of objects (eg. key_unlink, unmount, fput+FMODE_NEED_UNMOUNT).
(4) I/O or hardware errors (eg. USB device add/remove, EDQUOT, ENOSPC).
I have not currently defined any access events.
I've been looking at the possibility of having epoll generate events this way,
but that's not as straightforward as I'd hoped and fanotify could potentially
use it also, but in both those cases, the process is already getting the
events currently by watching for them using synchronous waiting syscalls.
Instead this would generate an event to say it had happened.
David
Stephen Smalley <[email protected]> wrote:
> > (6) The security attributes of all the objects between the object in (5)
> > and the object in (4), assuming we work from (5) towards (4) if the
> > two aren't coincident (WATCH_INFO_RECURSIVE).
>
> Does this apply to anything other than mount notifications?
Not at the moment. I'm considering making it such that you can make a watch
on a keyring get automatically propagated to keys that get added to the
keyring (and removed upon unlink) - the idea being that there is no 'single
parent path' concept for a keyring as there is for a directory.
I'm also pondering the idea of making it possible to have superblock watches
automatically propagated to superblocks created by automount points on the
watched superblock.
> And for mount notifications, isn't the notification actually for a change to
> the mount namespace, not a change to any file?
Yes.
> Hence, the real "object" for events that trigger mount notifications is the
> mount namespace, right?
Um... arguably. Would that mean that that would need a label from somewhere?
> The watched path is just a way of identifying a subtree of the mount
> namespace for notifications - it isn't the real object being watched.
I like that argument.
Thanks,
David
Casey Schaufler <[email protected]> wrote:
> > (4) The security attributes of the object on which the watch was set (uid,
> > gid, mode, labels).
>
> Smack needs this to set a watch on the named object (file, key, ...).
> I am going to say that if you can't access an object you can't watch it.
So for the things I've so far defined:
(*) Keys/keyrings require View permission, but it could be Read permission
instead - though that may get disabled if the key type does not support
KEYCTL_READ.
(*) Mount/superblock watches - I've made these require execute permission on
the specified directory. Could be read permission instead.
(*) Device events (block/usb) don't require any permissions, but currently
only deliver hardware notifications.
> I think that read access is sufficient provided that no one else can
> see what watches I've created.
You can't find out what watches exist.
> > At the moment, when post_one_notification() wants to write a notification
> > into a queue, it calls security_post_notification() to ask if it should be
> > allowed to do so. This is passed (1) and (3) above plus the notification
> > record.
>
> Is "current" (2)? Smack needs (2) for the event delivery access check.
(2) was current_cred() when watch_sb(), KEYCTL_NOTIFY, etc. was called, but
isn't necessarily current_cred() at the point post_one_notification() is
called. The latter is called at the point the event is generated and
current_cred() is the creds of whatever thread that is called in (which may be
a work_queue thread if it got deferred).
At the moment, I'm storing the creds of whoever opened the queue (ie. (1)) and
using that, but I could cache the creds of whoever created each watch
(ie. (2)) and pass that to post_one_notification() instead.
However, it should be noted that (1) is the creds of the buffer owner.
> > (e) All the points at which we walk over an object in a chain from (c) to
> > find the watch on which we can effect (d) (eg. we walk rootwards from a
> > mountpoint to find watches on a branch in the mount topology).
>
> Smack does not require anything beyond existing checks.
I'm glad to hear that, as this might be sufficiently impractical as to render
it unusable with Smack. Calling i_op->permissions() a lot would suck.
> > (y) What checks should be done on object destruction after final put and
> > what contexts need to be supplied?
>
> Classically there is no such thing as filesystem object deletion.
> By making it possible to set a watch on that you've inadvertently
> added a security relevant action to the security model. :o
That wasn't my original intention - I intended fsmount(2) to mount directly as
mount(2) does, but Al had other ideas.
Now fsmount(2) produces a file descriptor referring to a new mount object that
can be mounted into by move_mount(2) before being spliced into the mount
topology by move_mount(2). However, if the fd is closed before the last step,
close() will destroy the mount subtree attached to it (kind of quasi-unmount).
That wouldn't be a problem, except that the fd from fsmount(2) can be used
anywhere an O_PATH fd can be used - including watch_mount(2), fchdir(2), ...
Further, FMODE_NEED_UNMOUNT gets cleared if the mount is spliced in at least
once.
Okay, having tried it you don't get an unmount event (since there's no actual
unmount), but you do get an event to say that your watch got deleted (if the
directory on which the watch was placed got removed from the system).
So... does the "your watch got deleted" message need checking? In my
opinion, it shouldn't get discarded because then the watcher doesn't know
their watch went away.
David
On 6/12/2019 10:41 AM, David Howells wrote:
> Casey Schaufler <[email protected]> wrote:
>
>>> (4) The security attributes of the object on which the watch was set (uid,
>>> gid, mode, labels).
>> Smack needs this to set a watch on the named object (file, key, ...).
>> I am going to say that if you can't access an object you can't watch it.
> So for the things I've so far defined:
>
> (*) Keys/keyrings require View permission, but it could be Read permission
> instead - though that may get disabled if the key type does not support
> KEYCTL_READ.
View is good enough.
> (*) Mount/superblock watches - I've made these require execute permission on
> the specified directory. Could be read permission instead.
Execute is good enough.
> (*) Device events (block/usb) don't require any permissions, but currently
> only deliver hardware notifications.
How do you specify what device you want to watch?
Don't you have to access a /dev/something?
"currently" makes me nervous.
>> I think that read access is sufficient provided that no one else can
>> see what watches I've created.
> You can't find out what watches exist.
Not even your own?
>>> At the moment, when post_one_notification() wants to write a notification
>>> into a queue, it calls security_post_notification() to ask if it should be
>>> allowed to do so. This is passed (1) and (3) above plus the notification
>>> record.
>> Is "current" (2)? Smack needs (2) for the event delivery access check.
> (2) was current_cred() when watch_sb(), KEYCTL_NOTIFY, etc. was called, but
> isn't necessarily current_cred() at the point post_one_notification() is
> called. The latter is called at the point the event is generated and
> current_cred() is the creds of whatever thread that is called in (which may be
> a work_queue thread if it got deferred).
>
> At the moment, I'm storing the creds of whoever opened the queue (ie. (1)) and
> using that, but I could cache the creds of whoever created each watch
> (ie. (2)) and pass that to post_one_notification() instead.
>
> However, it should be noted that (1) is the creds of the buffer owner.
How are buffers shared? Who besides the buffer creator can use it?
>>> (e) All the points at which we walk over an object in a chain from (c) to
>>> find the watch on which we can effect (d) (eg. we walk rootwards from a
>>> mountpoint to find watches on a branch in the mount topology).
>> Smack does not require anything beyond existing checks.
> I'm glad to hear that, as this might be sufficiently impractical as to render
> it unusable with Smack. Calling i_op->permissions() a lot would suck.
>
>>> (y) What checks should be done on object destruction after final put and
>>> what contexts need to be supplied?
>> Classically there is no such thing as filesystem object deletion.
>> By making it possible to set a watch on that you've inadvertently
>> added a security relevant action to the security model. :o
> That wasn't my original intention - I intended fsmount(2) to mount directly as
> mount(2) does, but Al had other ideas.
>
> Now fsmount(2) produces a file descriptor referring to a new mount object that
> can be mounted into by move_mount(2) before being spliced into the mount
> topology by move_mount(2). However, if the fd is closed before the last step,
> close() will destroy the mount subtree attached to it (kind of quasi-unmount).
>
> That wouldn't be a problem, except that the fd from fsmount(2) can be used
> anywhere an O_PATH fd can be used - including watch_mount(2), fchdir(2), ...
> Further, FMODE_NEED_UNMOUNT gets cleared if the mount is spliced in at least
> once.
>
> Okay, having tried it you don't get an unmount event (since there's no actual
> unmount), but you do get an event to say that your watch got deleted (if the
> directory on which the watch was placed got removed from the system).
>
> So... does the "your watch got deleted" message need checking? In my
> opinion, it shouldn't get discarded because then the watcher doesn't know
> their watch went away.
Can you glean information from the watch being deleted?
I wouldn't think so, and it seems like a one-time event
from the system, so I don't think an access check would
be required.
>
> David
Casey Schaufler <[email protected]> wrote:
> > (*) Device events (block/usb) don't require any permissions, but currently
> > only deliver hardware notifications.
>
> How do you specify what device you want to watch?
It's a general queue.
> Don't you have to access a /dev/something?
Not at the moment. One problem is that there may not be a /dev/something for
a device (take a bridge for example), and even if it does, the device driver
doesn't necessarily have access to the path. The messages contain the device
name string as appears in dmesg ("3-7" for a USB device, for example).
I think it would be wise to limit the general queue to hardware events that
either get triggered by someone physically mucking around with the hardware or
device errors, such as bad sectors or links going up and down.
> > You can't find out what watches exist.
>
> Not even your own?
No.
> > However, it should be noted that (1) is the creds of the buffer owner.
>
> How are buffers shared? Who besides the buffer creator can use it?
When you open /dev/watch_queue, you get buffers private to that file object; a
second open of the device, even by the same process, will get different
buffers.
The buffers are 'attached' to that file and are accessed by calling mmap() on
the fd; shareability is governed by how shareable the fd and a mapping are
shareable.
> Can you glean information from the watch being deleted?
> I wouldn't think so, and it seems like a one-time event
> from the system, so I don't think an access check would
> be required.
As you say, it's a one-time message per watch. The object that got deleted
would need to be recreated, rewatched and made available to both parties.
David
On 6/13/19 6:34 AM, David Howells wrote:
> Randy Dunlap <[email protected]> wrote:
>
>> What is the problem with inline functions in UAPI headers?
>
> It makes compiler problems more likely; it increases the potential for name
> collisions with userspace; it makes for more potential problems if the headers
> are imported into some other language; and it's not easy to fix a bug in one
> if userspace uses it, just in case fixing the bug breaks userspace.
>
> Further, in this case, the first of Darrick's functions (calculating the
> length) is probably reasonable, but the second is not. It should crank the
> tail pointer and then use that, but that requires
>
>>>> Also, weird multiline comment style.
>>>
>>> Not really.
>>
>> Yes really.
>
> No. It's not weird. If anything, the default style is less good for several
> reasons. I'm going to deal with this separately as I need to generate some
> stats first.
>
> David
>
OK, maybe you are objecting to the word "weird." So the multi-line comment style
that you used is not the preferred Linux kernel multi-line comment style
(except in networking code) [Documentation/process/coding-style.rst] that has been
in effect for 20+ years AFAIK.
--
~Randy
Randy Dunlap <[email protected]> wrote:
> What is the problem with inline functions in UAPI headers?
It makes compiler problems more likely; it increases the potential for name
collisions with userspace; it makes for more potential problems if the headers
are imported into some other language; and it's not easy to fix a bug in one
if userspace uses it, just in case fixing the bug breaks userspace.
Further, in this case, the first of Darrick's functions (calculating the
length) is probably reasonable, but the second is not. It should crank the
tail pointer and then use that, but that requires
> >> Also, weird multiline comment style.
> >
> > Not really.
>
> Yes really.
No. It's not weird. If anything, the default style is less good for several
reasons. I'm going to deal with this separately as I need to generate some
stats first.
David
On 6/12/19 7:43 AM, David Howells wrote:
> Stephen Smalley <[email protected]> wrote:
>
>>> (6) The security attributes of all the objects between the object in (5)
>>> and the object in (4), assuming we work from (5) towards (4) if the
>>> two aren't coincident (WATCH_INFO_RECURSIVE).
>>
>> Does this apply to anything other than mount notifications?
>
> Not at the moment. I'm considering making it such that you can make a watch
> on a keyring get automatically propagated to keys that get added to the
> keyring (and removed upon unlink) - the idea being that there is no 'single
> parent path' concept for a keyring as there is for a directory.
>
> I'm also pondering the idea of making it possible to have superblock watches
> automatically propagated to superblocks created by automount points on the
> watched superblock.
So at the point where you can set a watch on one object O1 with label X,
and receive notifications triggered upon operations on another object O2
with label Y, we have to consider whether the relationship between X and
Y is controlled in any way (possibly transitively through a series of
checks performed earlier) and whether we can reasonably infer that the
authorization to watch X implies the ability to be notified of
operations on Y. Not a problem for the mount notifications AFAICS
because there is only truly one object - the mount namespace itself, and
it is always our own.
>
>> And for mount notifications, isn't the notification actually for a change to
>> the mount namespace, not a change to any file?
>
> Yes.
>
>> Hence, the real "object" for events that trigger mount notifications is the
>> mount namespace, right?
>
> Um... arguably. Would that mean that that would need a label from somewhere?
That takes us into the whole question of whether namespaces should be
labeled (presumably from their creator), and the association between
processes and their namespaces should be controlled. I think when we
originally looked at them, it wasn't much of a concern since the only
means of creating a new namespace and associating with it was via
clone() and then later also via unshare(). /proc/pid/ns and setns()
changed that picture, but still requires ptrace read mode access, which
at least provides some control over entering namespaces created by
others. I suspect that ultimately we want namespaces to be labeled and
controlled but that isn't your problem to solve here.
For your purposes, a process is setting a watch on its own namespace,
and it already inherently can observe changes to that namespace without
needing watches/notifications, and it can modify that namespace iff
privileged wrt to the namespace. One might argue that no check is
required at all for setting the watch, and at most, it would be a check
between the process and its own label to match the checking when
accessing /proc/self/mounts. That presumes that no additional
information is conveyed via the notification that isn't already
available from /proc/self/mounts, particularly any information specific
to the process that triggered the notification. Does that make sense?
>
>> The watched path is just a way of identifying a subtree of the mount
>> namespace for notifications - it isn't the real object being watched.
>
> I like that argument.
>
> Thanks,
> David
>