From: David Howells <dhowells@redhat.com> Date: 2019-06-04 16:35:19
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) Block layer events, such as I/O errors.
(4) Key/keyring events, such as creating, linking and removal of keys.
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:
(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:
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 (8):
security: Override creds in __fput() with last fputter's creds
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
block: Add block layer notifications
Add sample notification program
Documentation/security/keys/core.rst | 58 ++
Documentation/watch_queue.rst | 328 ++++++++++++
arch/x86/entry/syscalls/syscall_32.tbl | 3
arch/x86/entry/syscalls/syscall_64.tbl | 3
block/Kconfig | 9
block/Makefile | 1
block/blk-core.c | 29 +
block/blk-notify.c | 83 +++
drivers/misc/Kconfig | 13
drivers/misc/Makefile | 1
drivers/misc/watch_queue.c | 895 ++++++++++++++++++++++++++++++++
fs/Kconfig | 21 +
fs/Makefile | 1
fs/file_table.c | 12
fs/fsinfo.c | 12
fs/mount.h | 33 +
fs/mount_notify.c | 186 +++++++
fs/namespace.c | 9
fs/super.c | 117 ++++
include/linux/blkdev.h | 10
include/linux/dcache.h | 1
include/linux/fs.h | 79 +++
include/linux/key.h | 4
include/linux/lsm_hooks.h | 15 +
include/linux/security.h | 14 +
include/linux/syscalls.h | 5
include/linux/watch_queue.h | 87 +++
include/uapi/linux/fsinfo.h | 10
include/uapi/linux/keyctl.h | 1
include/uapi/linux/watch_queue.h | 185 +++++++
kernel/sys_ni.c | 7
mm/interval_tree.c | 2
mm/memory.c | 1
samples/Kconfig | 6
samples/Makefile | 1
samples/vfs/test-fsinfo.c | 13
samples/watch_queue/Makefile | 9
samples/watch_queue/watch_test.c | 284 ++++++++++
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 | 89 +++
security/keys/keyring.c | 17 -
security/keys/request_key.c | 4
security/security.c | 9
47 files changed, 2713 insertions(+), 38 deletions(-)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 block/blk-notify.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
From: David Howells <dhowells@redhat.com> Date: 2019-06-04 16:35:24
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 <casey@schaufler-ca.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Casey Schaufler <casey@schaufler-ca.com>
---
fs/file_table.c | 12 ++++++++++++
include/linux/fs.h | 1 +
2 files changed, 13 insertions(+)
@@ -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+*thatthishastobemadeavailabletofurtherfputs,sayonfds+*trappedinaunixsocket.+*/+saved_cred=override_creds(file->f_fput_cred);+fsnotify_close(file);/**Thefunctioneventpoll_release()shouldbethefirstcalled
@@ -943,6 +943,7 @@ struct file {loff_tf_pos;structfown_structf_owner;conststructcred*f_cred;+conststructcred*f_fput_cred;/* Who did the last fput() (for LSM) */structfile_ra_statef_ra;u64f_version;
From: David Howells <dhowells@redhat.com> Date: 2019-06-04 16:35:33
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 <dhowells@redhat.com>
---
Documentation/watch_queue.rst | 328 ++++++++++++++
drivers/misc/Kconfig | 13 +
drivers/misc/Makefile | 1
drivers/misc/watch_queue.c | 895 ++++++++++++++++++++++++++++++++++++++
include/linux/lsm_hooks.h | 15 +
include/linux/security.h | 14 +
include/linux/watch_queue.h | 87 ++++
include/uapi/linux/watch_queue.h | 82 +++
mm/interval_tree.c | 2
mm/memory.c | 1
security/security.c | 9
11 files changed, 1447 insertions(+)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 drivers/misc/watch_queue.c
create mode 100644 include/linux/watch_queue.h
create mode 100644 include/uapi/linux/watch_queue.h
@@ -0,0 +1,328 @@+============================+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 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!
@@ -3,6 +3,7 @@# Makefile for misc devices that really don't fit anywhere else.#+obj-$(CONFIG_WATCH_QUEUE)+=watch_queue.oobj-$(CONFIG_IBM_ASM)+=ibmasm/obj-$(CONFIG_IBMVMC)+=ibmvmc.oobj-$(CONFIG_AD525X_DPOT)+=ad525x_dpot.o
@@ -0,0 +1,895 @@+/* User-mappable watch queue+*+*Copyright(C)2018RedHat,Inc.AllRightsReserved.+*WrittenbyDavidHowells(dhowells@redhat.com)+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsoftheGNUGeneralPublicLicence+*aspublishedbytheFreeSoftwareFoundation;eitherversion+*2oftheLicence,or(atyouroption)anylaterversion.+*+*SeeDocumentation/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");++structwatch_type_filter{+enumwatch_notification_typetype;+__u32subtype_filter[1];/* Bitmask of subtypes to filter on */+__u32info_filter;/* Filter on watch_notification::info */+__u32info_mask;/* Mask of relevant bits in info_filter */+};++structwatch_filter{+union{+structrcu_headrcu;+unsignedlongtype_filter[2];/* Bitmask of accepted types */+};+u32nr_filters;/* Number of filters */+structwatch_type_filterfilters[];+};++structwatch_queue{+structrcu_headrcu;+structaddress_spacemapping;+conststructcred*cred;/* Creds of the owner of the queue */+structwatch_filter__rcu*filter;+wait_queue_head_twaiters;+structhlist_headwatches;/* Contributory watches */+structkrefusage;/* Object usage count */+spinlock_tlock;+booldefunct;/* T when queues closed */+u8nr_pages;/* Size of pages[] */+u8flag_next;/* Flag to apply to next item */+#ifdef DEBUG_WITH_WRITE+u8debug;+#endif+u32size;+structwatch_queue_buffer*buffer;/* Pointer to first record */++/* The mappable pages. The zeroth page holds the ring pointers. */+structpage**pages;+};++/**+*post_one_notification-Postaneventnotificationtoonequeue+*@wqueue:Thewatchqueuetoaddtheeventto.+*@n:Thenotificationrecordtopost.+*@cred:Thecredentialstouseinsecuritychecks.+*+*Postanotificationofaneventintoanmmap'dqueueandlettheuserknow.+*Returnstrueifsuccessfulandfalseonfailure(eg.bufferoverrunor+*userspacemuckeduptheringindices).+*+*+*Thesizeofthenotificationshouldbesetinn->flags&WATCH_LENGTHand+*shouldbeinunitsofsizeof(*n).+*/+staticboolpost_one_notification(structwatch_queue*wqueue,+structwatch_notification*n,+conststructcred*cred)+{+structwatch_queue_buffer*buf=wqueue->buffer;+unsignedintmetalen=sizeof(buf->meta)/sizeof(buf->slots[0]);+unsignedintsize=wqueue->size,mask=size-1;+unsignedintlen;+unsignedintring_tail,tail,head,used,segment,h;++if(!buf)+returnfalse;++len=(n->info&WATCH_INFO_LENGTH)>>WATCH_LENGTH_SHIFT;+if(len==0)+returnfalse;++spin_lock_bh(&wqueue->lock);/* Protect head pointer */++if(wqueue->defunct||+security_post_notification(wqueue->cred,cred,n)<0)+gotoout;++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)+gotooverrun;+tail=ring_tail&mask;+if(tail>0&&tail<metalen)+gotooverrun;++h=head&mask;+if(h>=tail){+/* Head is at or after tail in the buffer. There may then be+*twosegments:onetotheendofbufferandoneatthe+*beginningofthebufferbetweenthemetadatablockandthe+*tailpointer.+*/+segment=size-h;+if(len>segment){+/* Not enough space in the post-head segment; we need+*towrap.Whenwrapping,wewillhavetoskipthe+*metadataatthebeginningofthebuffer.+*/+if(len>tail-metalen)+gotooverrun;++/* 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)+gotooverrun;+}+}++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)+gotooverrun;+}++if(h<tail){+/* Head is before tail in the buffer. There may be one segment+*betweenthetwo,butwemayneedtoskipthemetadatablock.+*/+segment=tail-h;+if(len>segment)+gotooverrun;+}++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);+returntrue;++overrun:+wqueue->flag_next=WATCH_INFO_OVERRUN;+out:+spin_unlock_bh(&wqueue->lock);+returnfalse;+}++/*+*Applyfilterrulestoanotification.+*/+staticboolfilter_watch_notification(conststructwatch_filter*wf,+conststructwatch_notification*n)+{+conststructwatch_type_filter*wt;+inti;++if(!test_bit(n->type,wf->type_filter))+returnfalse;++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)+returntrue;+}++returnfalse;/* If there is a filter, the default is to reject. */+}++/**+*__post_watch_notification-Postaneventnotification+*@wlist:Thewatchlisttoposttheeventto.+*@n:Thenotificationrecordtopost.+*@cred:Thecredsoftheprocessthattriggeredthenotification.+*@id:TheIDtomatchonthewatch.+*+*Postanotificationofaneventintoasetofwatchqueuesandlettheusers+*know.+*+*If@nisNULLthenWATCH_INFO_LENGTHwillbesetonthenexteventposted.+*+*Thesizeofthenotificationshouldbesetinn->info&WATCH_INFO_LENGTHand+*shouldbeinunitsofsizeof(*n).+*/+void__post_watch_notification(structwatch_list*wlist,+structwatch_notification*n,+conststructcred*cred,+u64id)+{+conststructwatch_filter*wf;+structwatch_queue*wqueue;+structwatch*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);++/*+*Allowthequeuetobepolled.+*/+static__poll_twatch_queue_poll(structfile*file,poll_table*wait)+{+structwatch_queue*wqueue=file->private_data;+structwatch_queue_buffer*buf=wqueue->buffer;+unsignedinthead,tail;+__poll_tmask=0;++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;+returnmask;+}++staticintwatch_queue_set_page_dirty(structpage*page)+{+SetPageDirty(page);+return0;+}++staticconststructaddress_space_operationswatch_queue_aops={+.set_page_dirty=watch_queue_set_page_dirty,+};++staticvm_fault_twatch_queue_fault(structvm_fault*vmf)+{+structwatch_queue*wqueue=vmf->vma->vm_file->private_data;+structpage*page;++page=wqueue->pages[vmf->pgoff];+get_page(page);+if(!lock_page_or_retry(page,vmf->vma->vm_mm,vmf->flags)){+put_page(page);+returnVM_FAULT_RETRY;+}+vmf->page=page;+returnVM_FAULT_LOCKED;+}++staticvoidwatch_queue_map_pages(structvm_fault*vmf,+pgoff_tstart_pgoff,pgoff_tend_pgoff)+{+structwatch_queue*wqueue=vmf->vma->vm_file->private_data;+structpage*page;++rcu_read_lock();++do{+page=wqueue->pages[start_pgoff];+if(trylock_page(page)){+vm_fault_tret;+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();+}++staticconststructvm_operations_structwatch_queue_vm_ops={+.fault=watch_queue_fault,+.map_pages=watch_queue_map_pages,+};++/*+*Mapthebuffer.+*/+staticintwatch_queue_mmap(structfile*file,structvm_area_struct*vma)+{+structwatch_queue*wqueue=file->private_data;+structinode*inode=file_inode(file);+u8nr_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);+return0;+}++/*+*Allocatetherequirednumberofpages.+*/+staticlongwatch_queue_set_size(structwatch_queue*wqueue,unsignedlongnr_pages)+{+structwatch_queue_buffer*buf;+u32len;+inti;++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(structpage*),GFP_KERNEL);+if(!wqueue->pages)+gotoerr;++for(i=0;i<nr_pages;i++){+wqueue->pages[i]=alloc_page(GFP_KERNEL|__GFP_ZERO);+if(!wqueue->pages[i])+gotoerr_some_pages;+wqueue->pages[i]->mapping=&wqueue->mapping;+SetPageUptodate(wqueue->pages[i]);+}++buf=vmap(wqueue->pages,nr_pages,VM_MAP,PAGE_SHARED);+if(!buf)+gotoerr_some_pages;++wqueue->buffer=buf;+wqueue->nr_pages=nr_pages;+wqueue->size=((nr_pages*PAGE_SIZE)/sizeof(structwatch_notification));++/* The first four slots in the buffer contain metadata about the ring,+*includingtheheadandtailindicesandmask.+*/+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;+return0;++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;+}++/*+*Setthefilteronawatchqueue.+*/+staticlongwatch_queue_set_filter(structinode*inode,+structwatch_queue*wqueue,+structwatch_notification_filter__user*_filter)+{+structwatch_notification_type_filter*tf;+structwatch_notification_filterfilter;+structwatch_type_filter*q;+structwatch_filter*wfilter;+intret,nr_filter=0,i;++if(!_filter){+/* Remove the old filter */+wfilter=NULL;+gotoset;+}++/* 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))+returnPTR_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)+gotoerr_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-specifiedfilters.+*/+ret=-ENOMEM;+wfilter=kzalloc(struct_size(wfilter,filters,nr_filter),GFP_KERNEL);+if(!wfilter)+gotoerr_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);+return0;++err_filter:+kfree(tf);+returnret;+}++/*+*Setparameters.+*/+staticlongwatch_queue_ioctl(structfile*file,unsignedintcmd,unsignedlongarg)+{+structwatch_queue*wqueue=file->private_data;+structinode*inode=file_inode(file);+longret;++switch(cmd){+caseIOC_WATCH_QUEUE_SET_SIZE:+inode_lock(inode);+ret=watch_queue_set_size(wqueue,arg);+inode_unlock(inode);+returnret;++caseIOC_WATCH_QUEUE_SET_FILTER:+ret=watch_queue_set_filter(+inode,wqueue,+(structwatch_notification_filter__user*)arg);+returnret;++default:+return-ENOTTY;+}+}++/*+*Openthefile.+*/+staticintwatch_queue_open(structinode*inode,structfile*file)+{+structwatch_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;+return0;+}++staticvoid__put_watch_queue(structkref*kref)+{+structwatch_queue*wqueue=+container_of(kref,structwatch_queue,usage);++kfree_rcu(wqueue,rcu);+}++/**+*put_watch_queue-Disposeofarefonawatchqueue.+*@wqueue:Thewatchqueuetounref.+*/+voidput_watch_queue(structwatch_queue*wqueue)+{+kref_put(&wqueue->usage,__put_watch_queue);+}+EXPORT_SYMBOL(put_watch_queue);++staticvoidfree_watch(structrcu_head*rcu)+{+structwatch*watch=container_of(rcu,structwatch,rcu);++put_watch_queue(rcu_access_pointer(watch->queue));+}++staticvoid__put_watch(structkref*kref)+{+structwatch*watch=container_of(kref,structwatch,usage);++call_rcu(&watch->rcu,free_watch);+}++/*+*Discardawatch.+*/+staticvoidput_watch(structwatch*watch)+{+kref_put(&watch->usage,__put_watch);+}++/**+*init_watch_queue-Initialiseawatch+*@watch:Thewatchtoinitialise.+*@wqueue:Thequeuetoassign.+*+*Initialiseawatchandsetthewatchqueue.+*/+voidinit_watch(structwatch*watch,structwatch_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-Addawatchonanobjecttoawatchlist+*@watch:Thewatchtoadd+*@wlist:Thewatchlisttoaddto+*+*@watch->queuemusthavebeensettopointtothequeuetopostnotifications+*toandthewatchlistoftheobjecttobewatched.+*+*Thecallermustpinthequeueandthelistbothandmustholdthelist+*lockedagainstracingwatchadditions/removals.+*/+intadd_watch_to_object(structwatch*watch,structwatch_list*wlist)+{+structwatch_queue*wqueue=rcu_access_pointer(watch->queue);+structwatch*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);+return0;+}+EXPORT_SYMBOL(add_watch_to_object);++/**+*remove_watch_from_object-Removeawatchorallwatchesfromanobject.+*@wlist:Thewatchlisttoremovefrom+*@wq:Thewatchqueueofinterest(ignoredif@allistrue)+*@id:TheIDofthewatchtoremove(ignoredif@allistrue)+*@all:Truetoremoveallobjects+*+*Removeaspecificwatchorallwatchesfromanobject.Anotificationis+*senttothewatchertotellthemthatthishappened.+*/+intremove_watch_from_object(structwatch_list*wlist,structwatch_queue*wq,+u64id,boolall)+{+structwatch_notificationn;+structwatch_queue*wqueue;+structwatch*watch;+intret=-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))+gotofound;+}+spin_unlock(&wlist->lock);+gotoout;++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*wqueuefromdeallocation.+*/+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)(structwatch*);++release_watch=wlist->release_watch;+rcu_read_unlock();+(*release_watch)(watch);+rcu_read_lock();+}+put_watch(watch);++if(all&&!hlist_empty(&wlist->watchers))+gotoagain;+out:+rcu_read_unlock();+returnret;+}+EXPORT_SYMBOL(remove_watch_from_object);++/*+*Removeallthewatchesthatarecontributorytoaqueue.Thishasthe+*potentialtoracewithremovalofthewatchesbythedestructionofthe+*objectsbeingwatchedorwiththedistributionofnotifications.+*/+staticvoidwatch_queue_clear(structwatch_queue*wqueue)+{+structwatch_list*wlist;+structwatch*watch;+boolrelease;++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,structwatch,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+*getthelistlock-whichwouldcauseadeadlockifsomeone+*wasremovingfromtheoppositedirectionatthesametimeor+*postinganotification.+*/+wlist=rcu_dereference(watch->watch_list);+if(wlist){+void(*release_watch)(structwatch*);++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+*wehavetodropallthelocks.+*/+(*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();+}++/*+*Releasethefile.+*/+staticintwatch_queue_release(structinode*inode,structfile*file)+{+structwatch_filter*wfilter;+structwatch_queue*wqueue=file->private_data;+inti,pgref;++watch_queue_clear(wqueue);++if(wqueue->pages&&wqueue->pages[0])+WARN_ON(page_ref_count(wqueue->pages[0])!=1);++if(wqueue->buffer)+vfree(wqueue->buffer);+for(i=0;i<wqueue->nr_pages;i++){+ClearPageUptodate(wqueue->pages[i]);+wqueue->pages[i]->mapping=NULL;+pgref=page_ref_count(wqueue->pages[i]);+WARN(pgref!=1,+"FREE PAGE[%d] refcount %d\n",i,page_ref_count(wqueue->pages[i]));+__free_page(wqueue->pages[i]);+}++wfilter=rcu_dereference_protected(wqueue->filter,true);+if(wfilter)+kfree_rcu(wfilter,rcu);+kfree(wqueue->pages);+put_cred(wqueue->cred);+put_watch_queue(wqueue);+return0;+}++#ifdef DEBUG_WITH_WRITE+staticssize_twatch_queue_write(structfile*file,+constchar__user*_buf,size_tlen,loff_t*pos)+{+structwatch_notification*n;+structwatch_queue*wqueue=file->private_data;+ssize_tret;++if(!wqueue->buffer)+return-ENOBUFS;++if(len&~WATCH_INFO_LENGTH||len==0||!_buf)+return-EINVAL;++n=memdup_user(_buf,len);+if(IS_ERR(n))+returnPTR_ERR(n);++ret=-EINVAL;+if((n->info&WATCH_INFO_LENGTH)!=len)+gotoerror;+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);+returnret;+}+#endif++staticconststructfile_operationswatch_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-Getawatchqueuefromitsfiledescriptor.+*@fd:Thefdtoquery.+*/+structwatch_queue*get_watch_queue(intfd)+{+structwatch_queue*wqueue=ERR_PTR(-EBADF);+structfdf;++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);+}++returnwqueue;+}+EXPORT_SYMBOL(get_watch_queue);++staticstructmiscdevicewatch_queue_dev={+.minor=MISC_DYNAMIC_MINOR,+.name="watch_queue",+.fops=&watch_queue_fops,+.mode=0666,+};+builtin_misc_device(watch_queue_dev);
@@ -0,0 +1,87 @@+/* User-mappable watch queue+*+*Copyright(C)2018RedHat,Inc.AllRightsReserved.+*WrittenbyDavidHowells(dhowells@redhat.com)+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsoftheGNUGeneralPublicLicence+*aspublishedbytheFreeSoftwareFoundation;eitherversion+*2oftheLicence,or(atyouroption)anylaterversion.+*+*SeeDocumentation/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++structwatch_queue;++/*+*Representationofawatchonanobject.+*/+structwatch{+union{+structrcu_headrcu;+u32info_id;/* ID to be OR'd in to info field */+};+structwatch_queue__rcu*queue;/* Queue to post events to */+structhlist_nodequeue_node;/* Link in queue->watches */+structwatch_list__rcu*watch_list;+structhlist_nodelist_node;/* Link in watch_list->watchers */+void*private;/* Private data for the watched object */+u64id;/* Internal identifier */+structkrefusage;/* Object usage count */+};++/*+*Listofwatchesonanobject.+*/+structwatch_list{+structrcu_headrcu;+structhlist_headwatchers;+void(*release_watch)(structwatch*);+spinlock_tlock;+};++externvoid__post_watch_notification(structwatch_list*,+structwatch_notification*,+conststructcred*,+u64);+externstructwatch_queue*get_watch_queue(int);+externvoidput_watch_queue(structwatch_queue*);+externvoidput_watch_list(structwatch_list*);+externvoidinit_watch(structwatch*,structwatch_queue*);+externintadd_watch_to_object(structwatch*,structwatch_list*);+externintremove_watch_from_object(structwatch_list*,structwatch_queue*,u64,bool);++staticinlinevoidinit_watch_list(structwatch_list*wlist)+{+INIT_HLIST_HEAD(&wlist->watchers);+spin_lock_init(&wlist->lock);+}++staticinlinevoidpost_watch_notification(structwatch_list*wlist,+structwatch_notification*n,+conststructcred*cred,+u64id)+{+if(unlikely(wlist))+__post_watch_notification(wlist,n,cred,id);+}++staticinlinevoidremove_watch_list(structwatch_list*wlist)+{+if(wlist){+remove_watch_from_object(wlist,NULL,0,true);+kfree_rcu(wlist,rcu);+}+}++#endif++#endif /* _LINUX_WATCH_QUEUE_H */
@@ -0,0 +1,82 @@+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */+#ifndef _UAPI_LINUX_WATCH_QUEUE_H+#define _UAPI_LINUX_WATCH_QUEUE_H++#include<linux/types.h>+#include<linux/ioctl.h>++#define IOC_WATCH_QUEUE_SET_SIZE _IO('s', 0x01) /* Set the size in pages */+#define IOC_WATCH_QUEUE_SET_FILTER _IO('s', 0x02) /* Set the filter */++enumwatch_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+};++enumwatch_meta_notification_subtype{+WATCH_META_SKIP_NOTIFICATION=0,/* Just skip this record */+WATCH_META_REMOVAL_NOTIFICATION=1,/* Watched object was removed */+};++/*+*Notificationrecord+*/+structwatch_notification{+__u32type:24;/* enum watch_notification_type */+__u32subtype:8;/* Type-specific subtype (filterable) */+__u32info;+#define WATCH_INFO_OVERRUN 0x00000001 /* Event(s) lost due to overrun */+#define WATCH_INFO_ENOMEM 0x00000002 /* Event(s) lost due to ENOMEM */+#define WATCH_INFO_RECURSIVE 0x00000004 /* Change was recursive */+#define WATCH_INFO_LENGTH 0x000001f8 /* Length of record / sizeof(watch_notification) */+#define WATCH_INFO_IN_SUBTREE 0x00000200 /* Change was not at watched root */+#define WATCH_INFO_TYPE_FLAGS 0x00ff0000 /* Type-specific flags */+#define WATCH_INFO_FLAG_0 0x00010000+#define WATCH_INFO_FLAG_1 0x00020000+#define WATCH_INFO_FLAG_2 0x00040000+#define WATCH_INFO_FLAG_3 0x00080000+#define WATCH_INFO_FLAG_4 0x00100000+#define WATCH_INFO_FLAG_5 0x00200000+#define WATCH_INFO_FLAG_6 0x00400000+#define WATCH_INFO_FLAG_7 0x00800000+#define WATCH_INFO_ID 0xff000000 /* ID of watchpoint */+};++#define WATCH_LENGTH_SHIFT 3++structwatch_queue_buffer{+union{+/* The first few entries are special, containing the+*ringmanagementvariables.+*/+struct{+structwatch_notificationwatch;/* WATCH_TYPE_META */+__u32head;/* Ring head index */+__u32tail;/* Ring tail index */+__u32mask;/* Ring index mask */+}meta;+structwatch_notificationslots[0];+};+};++/*+*Notificationfilteringrules(IOC_WATCH_QUEUE_SET_FILTER).+*/+structwatch_notification_type_filter{+__u32type;/* Type to apply filter to */+__u32info_filter;/* Filter on watch_notification::info */+__u32info_mask;/* Mask of relevant bits in info_filter */+__u32subtype_filter[8];/* Bitmask of subtypes to filter on */+};++structwatch_notification_filter{+__u32nr_filters;/* Number of filters */+__u32__reserved;/* Must be 0 */+structwatch_notification_type_filterfilters[];+};++#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
@@ -25,6 +25,8 @@ INTERVAL_TREE_DEFINE(struct vm_area_struct, shared.rb,unsignedlong,shared.rb_subtree_last,vma_start_pgoff,vma_last_pgoff,,vma_interval_tree)+EXPORT_SYMBOL_GPL(vma_interval_tree_insert);+/* Insert node immediately after prev in the interval tree */voidvma_interval_tree_insert_after(structvm_area_struct*node,structvm_area_struct*prev,
From: David Howells <dhowells@redhat.com> Date: 2019-06-04 16:35:45
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 <dhowells@redhat.com>
---
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 | 89 +++++++++++++++++++++++++++++++++-
security/keys/keyring.c | 17 +++++-
security/keys/request_key.c | 4 +-
12 files changed, 258 insertions(+), 24 deletions(-)
@@ -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 ===============
@@ -159,6 +159,9 @@ struct key {structlist_headgraveyard_link;structrb_nodeserial_node;};+#ifdef CONFIG_KEY_NOTIFICATIONS+structwatch_list*watchers;/* Entities watching this key for changes */+#endifstructrw_semaphoresem;/* change vs change sem */structkey_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*-thedescisusedtomatchakeyagainstsearchcriteria
@@ -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 */structkeyctl_dh_params{
@@ -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);
@@ -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);
@@ -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:
From: David Howells <dhowells@redhat.com> Date: 2019-06-04 16:35:53
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);
mount_notify(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
mount_notify(), 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 <dhowells@redhat.com>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 9 ++
fs/Makefile | 1
fs/mount.h | 33 ++++--
fs/mount_notify.c | 186 ++++++++++++++++++++++++++++++++
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, 256 insertions(+), 14 deletions(-)
create mode 100644 fs/mount_notify.c
@@ -356,6 +356,7 @@ 432 common fsmount __x64_sys_fsmount 433 common fspick __x64_sys_fspick 434 common fsinfo __x64_sys_fsinfo+435 common mount_notify __x64_sys_mount_notify # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -67,9 +68,13 @@ struct mount {intmnt_id;/* mount identifier */intmnt_group_id;/* peer group identifier */intmnt_expiry_mark;/* true if marked for expiry */+intmnt_nr_watchers;/* The number of subtree watches tracking this */structhlist_headmnt_pins;structfs_pinmnt_umount;structdentry*mnt_ex_mountpoint;+#ifdef CONFIG_MOUNT_NOTIFICATIONS+structwatch_list*mnt_watchers;/* Watches on dentries within this mount */+#endifatomic_tmnt_notify_counter;/* Number of notifications generated */}__randomize_layout;
@@ -153,18 +158,8 @@ static inline bool is_anon_ns(struct mnt_namespace *ns)returnns->seq==0;}-/*-*Typeofmounttopologychangenotification.-*/-enummount_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) */-};+externvoidpost_mount_notification(structmount*changed,+structmount_notification*notify);staticinlinevoidnotify_mount(structmount*changed,structmount*aux,
@@ -0,0 +1,186 @@+/* Provide mount topology/attribute change notifications.+*+*Copyright(C)2018RedHat,Inc.AllRightsReserved.+*WrittenbyDavidHowells(dhowells@redhat.com)+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsoftheGNUGeneralPublicLicence+*aspublishedbytheFreeSoftwareFoundation;eitherversion+*2oftheLicence,or(atyouroption)anylaterversion.+*/++#include<linux/fs.h>+#include<linux/namei.h>+#include<linux/syscalls.h>+#include<linux/slab.h>+#include"mount.h"++/*+*Postmountnotificationstoallwatchesgoingrootwardsalongthetree.+*+*Mustbecalledwiththemount_lockheld.+*/+voidpost_mount_notification(structmount*changed,+structmount_notification*notify)+{+conststructcred*cred=current_cred();+structpathcursor;+structmount*mnt;+unsignedseq;++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,+(unsignedlong)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)){+structmount*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;+gotorestart;+}++done_seqretry(&rename_lock,seq);+rcu_read_unlock();+}++staticvoidrelease_mount_watch(structwatch*watch)+{+structvfsmount*mnt=watch->private;+structdentry*dentry=(structdentry*)(unsignedlong)watch->id;++dput(dentry);+mntput(mnt);+}++/**+*sys_mount_notify-Watchformounttopology/attributechanges+*@dfd:Basedirectorytopathwalkfromorfdreferringtomount.+*@filename:Pathtomounttoplacethewatchupon+*@at_flags:Pathwalkcontrolflags+*@watch_fd:Thewatchqueuetosendnotificationsto.+*@watch_id:ThewatchIDtobeplacedinthenotification(-1toremovewatch)+*/+SYSCALL_DEFINE5(mount_notify,+int,dfd,+constchar__user*,filename,+unsignedint,at_flags,+int,watch_fd,+int,watch_id)+{+structwatch_queue*wqueue;+structwatch_list*wlist=NULL;+structwatch*watch;+structmount*m;+structpathpath;+unsignedintlookup_flags=+LOOKUP_DIRECTORY|LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT;+intret;++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)+returnret;++wqueue=get_watch_queue(watch_fd);+if(IS_ERR(wqueue))+gotoerr_path;++m=real_mount(path.mnt);++if(watch_id>=0){+if(!m->mnt_watchers){+wlist=kzalloc(sizeof(*wlist),GFP_KERNEL);+if(!wlist)+gotoerr_wqueue;+INIT_HLIST_HEAD(&wlist->watchers);+spin_lock_init(&wlist->lock);+wlist->release_watch=release_mount_watch;+}++watch=kzalloc(sizeof(*watch),GFP_KERNEL);+if(!watch)+gotoerr_wlist;++init_watch(watch,wqueue);+watch->id=(unsignedlong)path.dentry;+watch->private=path.mnt;+watch->info_id=(u32)watch_id<<24;++down_write(&m->mnt.mnt_sb->s_umount);+if(!m->mnt_watchers){+m->mnt_watchers=wlist;+wlist=NULL;+}++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);+}+up_write(&m->mnt.mnt_sb->s_umount);+if(ret<0)+kfree(watch);+}else{+ret=-EBADSLT;+if(m->mnt_watchers){+down_write(&m->mnt.mnt_sb->s_umount);+ret=remove_watch_from_object(m->mnt_watchers,wqueue,+(unsignedlong)path.dentry,+false);+up_write(&m->mnt.mnt_sb->s_umount);+}+}++err_wlist:+kfree(wlist);+err_wqueue:+put_watch_queue(wqueue);+err_path:+path_put(&path);+returnret;+}
@@ -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);returnret;}
@@ -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);}
@@ -1001,6 +1001,8 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,asmlinkagelongsys_fsinfo(intdfd,constchar__user*path,structfsinfo_params__user*params,void__user*buffer,size_tbuf_size);+asmlinkagelongsys_mount_notify(intdfd,constchar__user*path,+unsignedintat_flags,intwatch_fd,intwatch_id);/**Architecture-specificsystemcalls
@@ -104,4 +104,28 @@ struct key_notification {__u32aux;/* Per-type auxiliary data */};+/*+*Typeofmounttopologychangenotification.+*/+enummount_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) */+};++/*+*Mounttopology/configurationchangenotificationrecord.+*-watch.type=WATCH_TYPE_MOUNT_NOTIFY+*-watch.subtype=enummount_notification_subtype+*/+structmount_notification{+structwatch_notificationwatch;/* WATCH_TYPE_MOUNT_NOTIFY */+__u32triggered_on;/* The mount that the notify was on */+__u32changed_mount;/* The mount that got changed */+};+#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
From: David Howells <dhowells@redhat.com> Date: 2019-06-04 16:36:00
Add a superblock event notification facility whereby notifications about
superblock events, such as I/O errors (EIO), quota limits being hit
(EDQUOT) and running out of space (ENOSPC) can be reported to a monitoring
process asynchronously. Note that this does not cover vfsmount topology
changes. mount_notify() is used for that.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
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);
sb_notify(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
sb_notify(), 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 <dhowells@redhat.com>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 12 +++
fs/super.c | 116 ++++++++++++++++++++++++++++++++
include/linux/fs.h | 77 +++++++++++++++++++++
include/linux/syscalls.h | 2 +
include/uapi/linux/watch_queue.h | 26 +++++++
kernel/sys_ni.c | 3 +
8 files changed, 238 insertions(+)
@@ -357,6 +357,7 @@ 433 common fspick __x64_sys_fspick 434 common fsinfo __x64_sys_fsinfo 435 common mount_notify __x64_sys_mount_notify+436 common sb_notify __x64_sys_sb_notify # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -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);/**Somefilesystemsmodifytheirmetadataviasomeotherpaththanthe
@@ -1808,3 +1816,111 @@ int thaw_super(struct super_block *sb)returnthaw_super_locked(sb);}EXPORT_SYMBOL(thaw_super);++#ifdef CONFIG_SB_NOTIFICATIONS+/*+*Postsuperblocknotifications.+*/+voidpost_sb_notification(structsuper_block*s,structsuperblock_notification*n)+{+post_watch_notification(s->s_watchers,&n->watch,current_cred(),+s->s_unique_id);+}++/**+*sys_sb_notify-Watchforsuperblockevents.+*@dfd:Basedirectorytopathwalkfromorfdreferringtosuperblock.+*@filename:Pathtosuperblocktoplacethewatchupon+*@at_flags:Pathwalkcontrolflags+*@watch_fd:Thewatchqueuetosendnotificationsto.+*@watch_id:ThewatchIDtobeplacedinthenotification(-1toremovewatch)+*/+SYSCALL_DEFINE5(sb_notify,+int,dfd,+constchar__user*,filename,+unsignedint,at_flags,+int,watch_fd,+int,watch_id)+{+structwatch_queue*wqueue;+structsuper_block*s;+structwatch_list*wlist=NULL;+structwatch*watch;+structpathpath;+unsignedintlookup_flags=+LOOKUP_DIRECTORY|LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT;+intret;++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)+returnret;++wqueue=get_watch_queue(watch_fd);+if(IS_ERR(wqueue))+gotoerr_path;++s=path.dentry->d_sb;+if(watch_id>=0){+if(!s->s_watchers){+wlist=kzalloc(sizeof(*wlist),GFP_KERNEL);+if(!wlist)+gotoerr_wqueue;+INIT_HLIST_HEAD(&wlist->watchers);+spin_lock_init(&wlist->lock);+}++watch=kzalloc(sizeof(*watch),GFP_KERNEL);+if(!watch)+gotoerr_wlist;++init_watch(watch,wqueue);+watch->id=s->s_unique_id;+watch->private=s;+watch->info_id=(u32)watch_id<<24;++down_write(&s->s_umount);+ret=-EIO;+if(atomic_read(&s->s_active)){+if(!s->s_watchers){+s->s_watchers=wlist;+wlist=NULL;+}++ret=add_watch_to_object(watch,s->s_watchers);+if(ret==0){+spin_lock(&sb_lock);+s->s_count++;+spin_unlock(&sb_lock);+}+}+up_write(&s->s_umount);+if(ret<0)+kfree(watch);+}else{+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_wlist:+kfree(wlist);+err_wqueue:+put_watch_queue(wqueue);+err_path:+path_put(&path);+returnret;+}+#endif
@@ -1531,6 +1532,10 @@ struct super_block {/* Superblock event notifications */u64s_unique_id;++#ifdef CONFIG_SB_NOTIFICATIONS+structwatch_list*s_watchers;+#endif}__randomize_layout;/* Helper functions so that in most cases filesystems will
From: David Howells <dhowells@redhat.com> Date: 2019-06-04 16:36:09
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 <dhowells@redhat.com>
---
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(-)
@@ -1536,6 +1536,7 @@ struct super_block {#ifdef CONFIG_SB_NOTIFICATIONSstructwatch_list*s_watchers;#endif+atomic_ts_notify_counter;}__randomize_layout;/* Helper functions so that in most cases filesystems will
@@ -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_storageaddress;};+/*+*Informationstructforfsinfo(FSINFO_ATTR_SB_NOTIFICATIONS).+*/+structfsinfo_sb_notifications{+__u64watch_id;/* Watch ID for superblock. */+__u32notify_counter;/* Number of notifications. */+__u32__reserved[1];+};+#endif /* _UAPI_LINUX_FSINFO_H */
From: David Howells <dhowells@redhat.com> Date: 2019-06-04 16:36:19
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);
block_notify(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
block_notify(), 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 <dhowells@redhat.com>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
block/Kconfig | 9 +++
block/Makefile | 1
block/blk-core.c | 29 +++++++++++
block/blk-notify.c | 83 ++++++++++++++++++++++++++++++++
include/linux/blkdev.h | 10 ++++
include/linux/syscalls.h | 1
include/uapi/linux/watch_queue.h | 28 +++++++++++
kernel/sys_ni.c | 1
10 files changed, 164 insertions(+)
create mode 100644 block/blk-notify.c
@@ -358,6 +358,7 @@ 434 common fsinfo __x64_sys_fsinfo 435 common mount_notify __x64_sys_mount_notify 436 common sb_notify __x64_sys_sb_notify+437 common block_notify __x64_sys_block_notify # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -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
From: Andy Lutomirski <luto@kernel.org> Date: 2019-06-04 17:43:51
On Tue, Jun 4, 2019 at 9:35 AM David Howells [off-list ref] 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:
I asked before and didn't see a response, so I'll ask again. Why are
you paying any attention at all to the creds that generate an event?
It seems like the resulting security model will be vary hard to
understand and probably buggy. Can't you define a sensible model in
which only the listener creds matter?
LSM support is included:
(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.
That looks like duct tape that is, at best, likely to be very buggy.
(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.
This seems like the wrong approach. If an LSM wants to prevent covert
communication from, say, mount actions, then it shouldn't allow the
watch to be set up in the first place.
From: Andy Lutomirski <luto@kernel.org> Date: 2019-06-04 18:15:29
On Tue, Jun 4, 2019 at 9:35 AM David Howells [off-list ref] wrote:
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.
I don't think it's valid to capture credentials in close(). This
sounds very easy to spoof, especially when you consider that you can
stick an fd in unix socket and aim it at a service that's just going
to ignore it and close it.
IOW I think this is at least as invalid as looking at current_cred()
in write(), which is a classic bug that gets repeated regularly.
--Andy
On Tue, Jun 4, 2019 at 9:35 AM David Howells [off-list ref] wrote:
quoted
Hi Al,
Here's a set of patches to add a general variable-length notification queue
concept and to add sources of events for:
I asked before and didn't see a response, so I'll ask again. Why are
you paying any attention at all to the creds that generate an event?
It seems like the resulting security model will be vary hard to
understand and probably buggy. Can't you define a sensible model in
which only the listener creds matter?
We've spent the last 18 months reeling from the implications
of what can happen when one process has the ability to snoop
on another. Introducing yet another mechanism that is trivial
to exploit is a very bad idea.
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active. In the event delivery case, process A
does something (e.g. modifies a keyring) that generates an
event, which is then sent to process B's event buffer. Again,
A is active and B is passive. Process A must have write access
(defined by some policy) to process B's event buffer. To
implement such a policy requires A's credential, and some
information about the object (passive entity) to which the
event is being delivered. You can't just use the credential
from Process B because it is not the active entity, it is the
passive entity.
quoted
LSM support is included:
(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.
That looks like duct tape that is, at best, likely to be very buggy.
quoted
(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.
This seems like the wrong approach. If an LSM wants to prevent covert
communication from, say, mount actions, then it shouldn't allow the
watch to be set up in the first place.
From: David Howells <dhowells@redhat.com> Date: 2019-06-04 20:39:54
Andy Lutomirski [off-list ref] wrote:
quoted
Here's a set of patches to add a general variable-length notification queue
concept and to add sources of events for:
I asked before and didn't see a response, so I'll ask again. Why are you
paying any attention at all to the creds that generate an event?
Casey responded to you. It's one of his requirements.
I'm not sure of the need, and I particularly don't like trying to make
indirect destruction events (mount destruction keyed on fput, for instance)
carry the creds of the triggerer. Indeed, the trigger can come from all sorts
of places - including af_unix queue destruction, someone poking around in
procfs, a variety of processes fputting simultaneously. Only one of them can
win, and the LSM needs to handle *all* the possibilities.
However, the LSMs (or at least SELinux) ignore f_cred and use current_cred()
when checking permissions. See selinux_revalidate_file_permission() for
example - it uses current_cred() not file->f_cred to re-evaluate the perms,
and the fd might be shared between a number of processes with different creds.
This seems like the wrong approach. If an LSM wants to prevent covert
communication from, say, mount actions, then it shouldn't allow the
watch to be set up in the first place.
From: Andy Lutomirski <luto@kernel.org> Date: 2019-06-04 20:57:54
On Tue, Jun 4, 2019 at 1:39 PM David Howells [off-list ref] wrote:
Andy Lutomirski [off-list ref] wrote:
quoted
quoted
Here's a set of patches to add a general variable-length notification queue
concept and to add sources of events for:
I asked before and didn't see a response, so I'll ask again. Why are you
paying any attention at all to the creds that generate an event?
Casey responded to you. It's one of his requirements.
It being a "requirement" doesn't make it okay.
However, the LSMs (or at least SELinux) ignore f_cred and use current_cred()
when checking permissions. See selinux_revalidate_file_permission() for
example - it uses current_cred() not file->f_cred to re-evaluate the perms,
and the fd might be shared between a number of processes with different creds.
That's a bug. It's arguably a rather severe bug. If I ever get
around to writing the patch I keep thinking of that will warn if we
use creds from invalid contexts, it will warn.
Let's please not repeat this.
From: Andy Lutomirski <luto@kernel.org> Date: 2019-06-04 21:06:16
On Tue, Jun 4, 2019 at 1:31 PM Casey Schaufler [off-list ref] wrote:
n 6/4/2019 10:43 AM, Andy Lutomirski wrote:
quoted
On Tue, Jun 4, 2019 at 9:35 AM David Howells [off-list ref] wrote:
quoted
Hi Al,
Here's a set of patches to add a general variable-length notification queue
concept and to add sources of events for:
I asked before and didn't see a response, so I'll ask again. Why are
you paying any attention at all to the creds that generate an event?
It seems like the resulting security model will be vary hard to
understand and probably buggy. Can't you define a sensible model in
which only the listener creds matter?
We've spent the last 18 months reeling from the implications
of what can happen when one process has the ability to snoop
on another. Introducing yet another mechanism that is trivial
to exploit is a very bad idea.
If you're talking about Spectre, etc, this is IMO entirely irrelevant.
Among other things, setting these watches can and should require some
degree of privilege.
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active.
Are you stating what you see to be a requirement?
Process A must have write access
(defined by some policy) to process B's event buffer.
No, stop right here. Process B is monitoring some aspect of the
system. Process A is doing something. Process B should need
permission to monitor whatever it's monitoring, and process A should
have permission to do whatever it's doing. I don't think it makes
sense to try to ascribe an identity to the actor doing some action to
decide to omit it from the watch -- this has all kinds of correctness
issues.
If you're writing a policy and you don't like letting process B spy on
processes doing various things, then disallow that type of spying.
To
implement such a policy requires A's credential,
You may not design a new mechanism that looks at the credential in a
context where looking at a credential is invalid unless you have some
very strong justification for why all of the known reasons that it's a
bad idea don't apply to what you're doing.
So, without a much stronger justification, NAK.
Here's a set of patches to add a general variable-length notification queue
concept and to add sources of events for:
I asked before and didn't see a response, so I'll ask again. Why are you
paying any attention at all to the creds that generate an event?
Casey responded to you. It's one of his requirements.
Process A takes an action. As a result of that action,
an event is written to Process B's event buffer. This isn't
a covert channel, it's a direct access, just like sending
a signal. Process A is the subject and the event buffer,
which is part of Process B, is the object.
I'm not sure of the need, and I particularly don't like trying to make
indirect destruction events (mount destruction keyed on fput, for instance)
carry the creds of the triggerer. Indeed, the trigger can come from all sorts
of places - including af_unix queue destruction, someone poking around in
procfs, a variety of processes fputting simultaneously. Only one of them can
win, and the LSM needs to handle *all* the possibilities.
Yes, it's a hairy problem. It was a significant factor in the
demise of kdbus.
However, the LSMs (or at least SELinux) ignore f_cred and use current_cred()
when checking permissions. See selinux_revalidate_file_permission() for
example - it uses current_cred() not file->f_cred to re-evaluate the perms,
and the fd might be shared between a number of processes with different creds.
quoted
This seems like the wrong approach. If an LSM wants to prevent covert
communication from, say, mount actions, then it shouldn't allow the
watch to be set up in the first place.
Yeah, I can agree to that. Casey?
Back to your earlier point, you don't know where the
event is coming from when you create the event watch.
If you enforce a watch time, what are you going to check?
Isn't this going to be considered too restrictive?
On Tue, Jun 4, 2019 at 1:31 PM Casey Schaufler [off-list ref] wrote:
quoted
n 6/4/2019 10:43 AM, Andy Lutomirski wrote:
quoted
On Tue, Jun 4, 2019 at 9:35 AM David Howells [off-list ref] wrote:
quoted
Hi Al,
Here's a set of patches to add a general variable-length notification queue
concept and to add sources of events for:
I asked before and didn't see a response, so I'll ask again. Why are
you paying any attention at all to the creds that generate an event?
It seems like the resulting security model will be vary hard to
understand and probably buggy. Can't you define a sensible model in
which only the listener creds matter?
We've spent the last 18 months reeling from the implications
of what can happen when one process has the ability to snoop
on another. Introducing yet another mechanism that is trivial
to exploit is a very bad idea.
If you're talking about Spectre, etc, this is IMO entirely irrelevant.
We're seeing significant interest in using obscure mechanisms
in system exploits. Mechanisms will be exploited.
Among other things, setting these watches can and should require some
degree of privilege.
Requiring privilege would address the concerns for most
situations, although I don't see that it would help for
SELinux. SELinux does not generally put much credence in
what others consider "privilege".
Extreme care would probably be required for namespaces, too.
quoted
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active.
Are you stating what you see to be a requirement?
Basic subject/object access control is the core of
the Linux security model. Yes, there are exceptions,
but mostly they're historical in origin.
quoted
Process A must have write access
(defined by some policy) to process B's event buffer.
No, stop right here.
Listening ...
Process B is monitoring some aspect of the
system.
Process B is not "monitoring". At some point in the past it
has registered a request for information should an event occur.
It is currently passive.
Process A is doing something.
Yes. It is active.'
Process B should need
permission to monitor whatever it's monitoring,
OK, I'm good with that. But the only time you
can tell that is when the event is registered,
and at that time you can't tell who might be causing
the event. (Or can you?)
and process A should
have permission to do whatever it's doing.
So there needs to be some connection between what B
can request events for and what events A can cause.
Then you can deny B's requests because of A.
I don't think it makes
sense to try to ascribe an identity to the actor doing some action to
decide to omit it from the watch -- this has all kinds of correctness
issues.
It works for signals and UDP, but in general I get the concern.
If you're writing a policy and you don't like letting process B spy on
processes doing various things, then disallow that type of spying.
That gets you into a situation where you can't do the legitimate
monitoring you want to do just because there's the off chance you
might see something you shouldn't. "I hate security! It's confusing,
and always gets in the way!"
quoted
To
implement such a policy requires A's credential,
You may not design a new mechanism that looks at the credential in a
context where looking at a credential is invalid unless you have some
very strong justification for why all of the known reasons that it's a
bad idea don't apply to what you're doing.
Point. But you also don't get to ignore basic security policy
just because someone's spiffy lazy memory free cache hashing
tree (or similar mechanism) throws away references to important
information while it's still needed.
So, without a much stronger justification, NAK.
I try to be reasonable. Really. All I want is something
with a security model that can be explained coherently
within the context of the basic Linux security model.
There are enough variations as it is.
From: David Howells <dhowells@redhat.com> Date: 2019-06-05 08:41:49
Casey Schaufler [off-list ref] wrote:
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active. In the event delivery case, process A
does something (e.g. modifies a keyring) that generates an
event, which is then sent to process B's event buffer.
I think this might be the core sticking point here. It looks like two
different situations:
(1) A explicitly sends event to B (eg. signalling, sendmsg, etc.)
(2) A implicitly and unknowingly sends event to B as a side effect of some
other action (eg. B has a watch for the event A did).
The LSM treats them as the same: that is B must have MAC authorisation to send
a message to A.
But there are problems with not sending the event:
(1) B's internal state is then corrupt (or, at least, unknowingly invalid).
(2) B can potentially figure out that the event happened by other means.
I've implemented four event sources so far:
(1) Keys/keyrings. You can only get events on a key you have View permission
on and the other process has to have write access to it, so I think this
is good enough.
(2) Block layer. Currently this will only get you hardware error events,
which is probably safe. I'm not sure you can manipulate those without
permission to directly access the device files.
(3) Superblock. This is trickier since it can see events that can be
manufactured (R/W <-> R/O remounting, EDQUOT) as well as events that
can't without hardware control (EIO, network link loss, RF kill).
(4) Mount topology. This is the trickiest since it allows you to see events
beyond the point at which you placed your watch (in essence, you place a
subtree watch).
The question is what permission checking should I do? Ideally, I'd
emulate a pathwalk between the watchpoint and the eventing object to see
if the owner of the watchpoint could reach it.
I'd need to do a reverse walk, calling inode_permission(MAY_NOT_BLOCK)
for each directory between the eventing object and the watchpoint to see
if one rejects it - but some filesystems have a permission check that
can't be called in this state.
It would also be necessary to do this separately for each watchpoint in
the parental chain.
Further, each permissions check would generate an audit event and could
generate FAN_ACCESS and/or FAN_ACCESS_PERM fanotify events - which could
be a problem if fanotify is also trying to post those events to the same
watch queue.
David
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active. In the event delivery case, process A
does something (e.g. modifies a keyring) that generates an
event, which is then sent to process B's event buffer.
I think this might be the core sticking point here. It looks like two
different situations:
(1) A explicitly sends event to B (eg. signalling, sendmsg, etc.)
(2) A implicitly and unknowingly sends event to B as a side effect of some
other action (eg. B has a watch for the event A did).
The LSM treats them as the same: that is B must have MAC authorisation to send
a message to A.
YES!
Threat is about what you can do, not what you intend to do.
And it would be really great if you put some thought into what
a rational model would be for UID based controls, too.
But there are problems with not sending the event:
(1) B's internal state is then corrupt (or, at least, unknowingly invalid).
Then B is a badly written program.
(2) B can potentially figure out that the event happened by other means.
Then why does it need the event mechanism in the first place?
I've implemented four event sources so far:
(1) Keys/keyrings. You can only get events on a key you have View permission
on and the other process has to have write access to it, so I think this
is good enough.
Sounds fine.
(2) Block layer. Currently this will only get you hardware error events,
which is probably safe. I'm not sure you can manipulate those without
permission to directly access the device files.
There's an argument to be made that this should require CAP_SYS_ADMIN,
or that an LSM like SELinux might include hardware error events in
policy, but generally I agree that system generated events like this
are both harmless and pointless for the general public to watch.
(3) Superblock. This is trickier since it can see events that can be
manufactured (R/W <-> R/O remounting, EDQUOT) as well as events that
can't without hardware control (EIO, network link loss, RF kill).
The events generated by processes (the 1st set) need controls
like keys. The events generated by the system (the 2nd set) may
need controls like the block layer.
(4) Mount topology. This is the trickiest since it allows you to see events
beyond the point at which you placed your watch (in essence, you place a
subtree watch).
Like keys.
The question is what permission checking should I do? Ideally, I'd
emulate a pathwalk between the watchpoint and the eventing object to see
if the owner of the watchpoint could reach it.
That will depend, as I've been saying, on what causes
the event to be generated. If it's from a process, the
question is "can the active process, the one that generated
the event, write to the passive, watching process?"
If it's the system on a hardware event, you may want the watcher
to have CAP_SYS_ADMIN.
I'd need to do a reverse walk, calling inode_permission(MAY_NOT_BLOCK)
for each directory between the eventing object and the watchpoint to see
if one rejects it - but some filesystems have a permission check that
can't be called in this state.
This is for setting the watch, right?
It would also be necessary to do this separately for each watchpoint in
the parental chain.
Further, each permissions check would generate an audit event and could
generate FAN_ACCESS and/or FAN_ACCESS_PERM fanotify events - which could
be a problem if fanotify is also trying to post those events to the same
watch queue.
If you required that the watching process open(dir) what
you want to watch you'd get this for free. Or did I miss
something obvious?
From: Andy Lutomirski <luto@kernel.org> Date: 2019-06-05 16:04:24
On Wed, Jun 5, 2019 at 7:51 AM Casey Schaufler [off-list ref] wrote:
On 6/5/2019 1:41 AM, David Howells wrote:
quoted
Casey Schaufler [off-list ref] wrote:
quoted
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active. In the event delivery case, process A
does something (e.g. modifies a keyring) that generates an
event, which is then sent to process B's event buffer.
I think this might be the core sticking point here. It looks like two
different situations:
(1) A explicitly sends event to B (eg. signalling, sendmsg, etc.)
(2) A implicitly and unknowingly sends event to B as a side effect of some
other action (eg. B has a watch for the event A did).
The LSM treats them as the same: that is B must have MAC authorisation to send
a message to A.
YES!
Threat is about what you can do, not what you intend to do.
And it would be really great if you put some thought into what
a rational model would be for UID based controls, too.
quoted
But there are problems with not sending the event:
(1) B's internal state is then corrupt (or, at least, unknowingly invalid).
Then B is a badly written program.
Either I'm misunderstanding you or I strongly disagree. If B has
authority to detect a certain action, and A has authority to perform
that action, then refusing to notify B because B is somehow missing
some special authorization to be notified by A is nuts. This is just
introducing incorrectness into the design in support of a
not-actually-helpful security idea.
If I can read /proc/self/mounts, I can detect changes to my mount
namespace. Giving me a faster and nicer way to do this is fine, AS
LONG AS IT ACTUALLY WORKS. "Works" means it needs to detect all
changes.
From: David Howells <dhowells@redhat.com> Date: 2019-06-05 16:57:03
Casey Schaufler [off-list ref] wrote:
YES!
I'm trying to decide if that's fervour or irritation at this point ;-)
And it would be really great if you put some thought into what
a rational model would be for UID based controls, too.
I have put some thought into it, but I don't see a single rational model. It
depends very much on the situation.
In any case, that's what I was referring to when I said I might need to call
inode_permission(). But UIDs don't exist for all filesystems, for example,
and there are no UIDs on superblocks, mount objects or hardware events.
Now, I could see that you ignore UIDs on things like keys and
hardware-triggered events, but how does this interact with things like mount
watches that see directories that have UIDs?
Are you advocating making it such that process B can only see events triggered
by process A if they have the same UID, for example?
David
On Wed, Jun 5, 2019 at 7:51 AM Casey Schaufler [off-list ref] wrote:
quoted
On 6/5/2019 1:41 AM, David Howells wrote:
quoted
Casey Schaufler [off-list ref] wrote:
quoted
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active. In the event delivery case, process A
does something (e.g. modifies a keyring) that generates an
event, which is then sent to process B's event buffer.
I think this might be the core sticking point here. It looks like two
different situations:
(1) A explicitly sends event to B (eg. signalling, sendmsg, etc.)
(2) A implicitly and unknowingly sends event to B as a side effect of some
other action (eg. B has a watch for the event A did).
The LSM treats them as the same: that is B must have MAC authorisation to send
a message to A.
YES!
Threat is about what you can do, not what you intend to do.
And it would be really great if you put some thought into what
a rational model would be for UID based controls, too.
quoted
But there are problems with not sending the event:
(1) B's internal state is then corrupt (or, at least, unknowingly invalid).
Then B is a badly written program.
Either I'm misunderstanding you or I strongly disagree.
A program needs to be aware of the conditions under
which it gets event, *including the possibility that
it may not get an event that it's not allowed*. Do you
regularly write programs that go into corrupt states
if an open() fails? Or where read() returns less than
the amount of data you ask for?
If B has
authority to detect a certain action, and A has authority to perform
that action, then refusing to notify B because B is somehow missing
some special authorization to be notified by A is nuts.
You are hand-waving the notion of authority. You are assuming
that if A can read X and B can read X that A can write B.
This is just
introducing incorrectness into the design in support of a
not-actually-helpful security idea.
Where is the incorrectness? Are you seriously saying that
you expect all events to be generated exactly as you think
they should? Have you ever even used systemd?
If I can read /proc/self/mounts, I can detect changes to my mount
namespace.
Then read /proc/self/mounts!
Can't you poll on an fd open on /proc/self/mounts?
Giving me a faster and nicer way to do this is fine, AS
LONG AS IT ACTUALLY WORKS. "Works" means it needs to detect all
changes.
So long as "WORKS" includes maintaining the system security
policy, I agree. No, I don't. We already have too many bizarre
and unnatural mechanisms to address whimsical special cases.
If speed is such an issue you could look at making /proc better.
From: David Howells <dhowells@redhat.com> Date: 2019-06-05 17:21:38
Casey Schaufler [off-list ref] wrote:
quoted
But there are problems with not sending the event:
(1) B's internal state is then corrupt (or, at least, unknowingly invalid).
Then B is a badly written program.
No. It may have the expectation that it will get events but then it is denied
those events and doesn't even know they've happened.
quoted
(2) B can potentially figure out that the event happened by other means.
Then why does it need the event mechanism in the first place?
Why does a CPU have interrupt lines? It can always continuously poll the
hardware. Why do poll() and select() exist?
quoted
I've implemented four event sources so far:
(1) Keys/keyrings. You can only get events on a key you have View permission
on and the other process has to have write access to it, so I think this
is good enough.
Sounds fine.
quoted
(2) Block layer. Currently this will only get you hardware error events,
which is probably safe. I'm not sure you can manipulate those without
permission to directly access the device files.
There's an argument to be made that this should require CAP_SYS_ADMIN,
or that an LSM like SELinux might include hardware error events in
policy, but generally I agree that system generated events like this
are both harmless and pointless for the general public to watch.
CAP_SYS_ADMIN is probably too broad a hammer - this is something you might
want to let a file manager or desktop environment use. I wonder if we could
add a CAP_SYS_NOTIFY - or is it too late for adding new caps?
quoted
(3) Superblock. This is trickier since it can see events that can be
manufactured (R/W <-> R/O remounting, EDQUOT) as well as events that
can't without hardware control (EIO, network link loss, RF kill).
The events generated by processes (the 1st set) need controls
like keys. The events generated by the system (the 2nd set) may
need controls like the block layer.
quoted
(4) Mount topology. This is the trickiest since it allows you to see
events beyond the point at which you placed your watch (in essence,
you place a subtree watch).
Like keys.
quoted
The question is what permission checking should I do? Ideally, I'd
emulate a pathwalk between the watchpoint and the eventing object to
see if the owner of the watchpoint could reach it.
That will depend, as I've been saying, on what causes
the event to be generated. If it's from a process, the
question is "can the active process, the one that generated
the event, write to the passive, watching process?"
If it's the system on a hardware event, you may want the watcher
to have CAP_SYS_ADMIN.
quoted
I'd need to do a reverse walk, calling
inode_permission(MAY_NOT_BLOCK) for each directory between the
eventing object and the watchpoint to see if one rejects it - but
some filesystems have a permission check that can't be called in this
state.
This is for setting the watch, right?
No. Setting the watch requires execute permission on the directory on which
you're setting the watch, but there's no way to know what permissions will be
required for an event at that point.
I'm talking about when an event is generated (hence "eventing object").
Imagine you have a subpath:
dirA/dirB/dirC/dirD/dirE
where dir* are directories. If you place a watch on dirA and then an event
occurs on dirB (such as someone mounting on it), I do a walk back up the
parental tree, in the order:
dirE, dirD, dirC, dirB, dirA
If I need to check permissions on all the directories, I would find the
watchpoint on dirA, then I would have to repeat the walk to find out whether
the owner of the watchpoint can access all of those directories (perhaps
skipping dirA since I had permission to place a watchpoint thereon).
Note that this is subject to going awry if there's a race versus rename().
quoted
It would also be necessary to do this separately for each watchpoint in
the parental chain.
Further, each permissions check would generate an audit event and
could generate FAN_ACCESS and/or FAN_ACCESS_PERM fanotify events -
which could be a problem if fanotify is also trying to post those
events to the same watch queue.
If you required that the watching process open(dir) what
you want to watch you'd get this for free. Or did I miss
something obvious?
A subtree watch, such as the mount topology watch, watches not only the
directory and mount object you pointed directly at, but the subtree rooted
thereon.
Take the sample program in the last patch. It places a watch on "/" with no
filter against WATCH_INFO_RECURSIVE, so it sees all mount topology events that
happen under the VFS path subtree rooted at "/" - whether or not it can
actually pathwalk to those mounts.
David
I'm trying to decide if that's fervour or irritation at this point ;-)
I think I finally got the point that the underlying mechanism,
direct or indirect, isn't the issue. It's the end result that
matters. That makes me happier.
quoted
And it would be really great if you put some thought into what
a rational model would be for UID based controls, too.
I have put some thought into it, but I don't see a single rational model. It
depends very much on the situation.
Right. You're mixing the kind of things that can generate events,
and that makes having a single policy difficult.
In any case, that's what I was referring to when I said I might need to call
inode_permission(). But UIDs don't exist for all filesystems, for example,
and there are no UIDs on superblocks, mount objects or hardware events.
If you open() or stat() a file on those filesystems the UID
used in the access control comes from somewhere. Setting a watch
on things with UIDs should use the access mode on the file,
just like any other filesystem operation.
Things like superblocks are sticker because we don't generally
think of them as objects. If you can do statfs(), you should be
able to set a watch on the filesystem metadata.
How would you specify a watch for a hardware event? If you say
you have to open /dev/mumble to sent a watch for mumbles, you're
good there, too.
Now, I could see that you ignore UIDs on things like keys and
hardware-triggered events, but how does this interact with things like mount
watches that see directories that have UIDs?
Are you advocating making it such that process B can only see events triggered
by process A if they have the same UID, for example?
It's always seemed arbitrary to me that you can't open
your process up to get signals from other users. What about
putting mode bits on your ring buffer? By default you could
only accept your own events, but you could do a rb_chmod(0222)
and let all events through. Subject to LSM addition restrictions,
of course. That would require the cred of the process that
triggered the event or a system cred for "hardware" events.
If you don't like mode bits you could use an ACL for fine
granularity or a single "let'em all in" bit for coarse.
I'm not against access, I'm against uncontrolled access
in conflict with basic system policy.
From: Andy Lutomirski <luto@amacapital.net> Date: 2019-06-05 17:47:50
On Jun 5, 2019, at 10:01 AM, Casey Schaufler [off-list ref] wrote:
quoted
On 6/5/2019 9:04 AM, Andy Lutomirski wrote:
quoted
On Wed, Jun 5, 2019 at 7:51 AM Casey Schaufler [off-list ref] wrote:
quoted
On 6/5/2019 1:41 AM, David Howells wrote:
Casey Schaufler [off-list ref] wrote:
quoted
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active. In the event delivery case, process A
does something (e.g. modifies a keyring) that generates an
event, which is then sent to process B's event buffer.
I think this might be the core sticking point here. It looks like two
different situations:
(1) A explicitly sends event to B (eg. signalling, sendmsg, etc.)
(2) A implicitly and unknowingly sends event to B as a side effect of some
other action (eg. B has a watch for the event A did).
The LSM treats them as the same: that is B must have MAC authorisation to send
a message to A.
YES!
Threat is about what you can do, not what you intend to do.
And it would be really great if you put some thought into what
a rational model would be for UID based controls, too.
quoted
But there are problems with not sending the event:
(1) B's internal state is then corrupt (or, at least, unknowingly invalid).
Then B is a badly written program.
Either I'm misunderstanding you or I strongly disagree.
A program needs to be aware of the conditions under
which it gets event, *including the possibility that
it may not get an event that it's not allowed*. Do you
regularly write programs that go into corrupt states
if an open() fails? Or where read() returns less than
the amount of data you ask for?
I do not regularly write programs that handle read() omitting data in the middle of a TCP stream. I also don’t write programs that wait for processes to die and need to handle the case where a child is dead, waitid() can see it, but SIGCHLD wasn’t sent because “security”.
quoted
If B has
authority to detect a certain action, and A has authority to perform
that action, then refusing to notify B because B is somehow missing
some special authorization to be notified by A is nuts.
You are hand-waving the notion of authority. You are assuming
that if A can read X and B can read X that A can write B.
No, read it again please. I’m assuming that if A can *write* X and B can read X then A can send information to B.
On Jun 5, 2019, at 10:01 AM, Casey Schaufler [off-list ref] wrote:
quoted
On 6/5/2019 9:04 AM, Andy Lutomirski wrote:
quoted
On Wed, Jun 5, 2019 at 7:51 AM Casey Schaufler [off-list ref] wrote:
quoted
On 6/5/2019 1:41 AM, David Howells wrote:
Casey Schaufler [off-list ref] wrote:
quoted
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active. In the event delivery case, process A
does something (e.g. modifies a keyring) that generates an
event, which is then sent to process B's event buffer.
I think this might be the core sticking point here. It looks like two
different situations:
(1) A explicitly sends event to B (eg. signalling, sendmsg, etc.)
(2) A implicitly and unknowingly sends event to B as a side effect of some
other action (eg. B has a watch for the event A did).
The LSM treats them as the same: that is B must have MAC authorisation to send
a message to A.
YES!
Threat is about what you can do, not what you intend to do.
And it would be really great if you put some thought into what
a rational model would be for UID based controls, too.
quoted
But there are problems with not sending the event:
(1) B's internal state is then corrupt (or, at least, unknowingly invalid).
Then B is a badly written program.
Either I'm misunderstanding you or I strongly disagree.
A program needs to be aware of the conditions under
which it gets event, *including the possibility that
it may not get an event that it's not allowed*. Do you
regularly write programs that go into corrupt states
if an open() fails? Or where read() returns less than
the amount of data you ask for?
I do not regularly write programs that handle read() omitting data in the middle of a TCP stream. I also don’t write programs that wait for processes to die and need to handle the case where a child is dead, waitid() can see it, but SIGCHLD wasn’t sent because “security”.
quoted
quoted
If B has
authority to detect a certain action, and A has authority to perform
that action, then refusing to notify B because B is somehow missing
some special authorization to be notified by A is nuts.
You are hand-waving the notion of authority. You are assuming
that if A can read X and B can read X that A can write B.
No, read it again please. I’m assuming that if A can *write* X and B can read X then A can send information to B.
That is *not* a valid assumption:
A can write to /dev/null.
B can read from /dev/null.
Does not imply B can read what A wrote.
Does not imply A can send a signal to B.
A can send a UDP datagram to port 3343
B can is bound to port 3343
Does not imply the packet will be delivered
From: Stephen Smalley <hidden> Date: 2019-06-05 18:25:59
On 6/5/19 1:47 PM, Andy Lutomirski wrote:
quoted
On Jun 5, 2019, at 10:01 AM, Casey Schaufler [off-list ref] wrote:
quoted
On 6/5/2019 9:04 AM, Andy Lutomirski wrote:
quoted
On Wed, Jun 5, 2019 at 7:51 AM Casey Schaufler [off-list ref] wrote:
quoted
On 6/5/2019 1:41 AM, David Howells wrote:
Casey Schaufler [off-list ref] wrote:
quoted
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active. In the event delivery case, process A
does something (e.g. modifies a keyring) that generates an
event, which is then sent to process B's event buffer.
I think this might be the core sticking point here. It looks like two
different situations:
(1) A explicitly sends event to B (eg. signalling, sendmsg, etc.)
(2) A implicitly and unknowingly sends event to B as a side effect of some
other action (eg. B has a watch for the event A did).
The LSM treats them as the same: that is B must have MAC authorisation to send
a message to A.
YES!
Threat is about what you can do, not what you intend to do.
And it would be really great if you put some thought into what
a rational model would be for UID based controls, too.
quoted
But there are problems with not sending the event:
(1) B's internal state is then corrupt (or, at least, unknowingly invalid).
Then B is a badly written program.
Either I'm misunderstanding you or I strongly disagree.
A program needs to be aware of the conditions under
which it gets event, *including the possibility that
it may not get an event that it's not allowed*. Do you
regularly write programs that go into corrupt states
if an open() fails? Or where read() returns less than
the amount of data you ask for?
I do not regularly write programs that handle read() omitting data in the middle of a TCP stream. I also don’t write programs that wait for processes to die and need to handle the case where a child is dead, waitid() can see it, but SIGCHLD wasn’t sent because “security”.
quoted
quoted
If B has
authority to detect a certain action, and A has authority to perform
that action, then refusing to notify B because B is somehow missing
some special authorization to be notified by A is nuts.
You are hand-waving the notion of authority. You are assuming
that if A can read X and B can read X that A can write B.
No, read it again please. I’m assuming that if A can *write* X and B can read X then A can send information to B.
I guess the questions here are:
1) How do we handle recursive notification support, since we can't check
that B can read everything below a given directory easily? Perhaps we
can argue that if I have watch permission to / then that implies
visibility to everything below it but that is rather broad.
2) Is there always a corresponding labeled object in view for each of
these notifications to which we can check access when the watch is set?
3) Are notifications only generated for write events or can they be
generated by processes that only have read access to the object?
On Wed, Jun 05, 2019 at 02:25:33PM -0400, Stephen Smalley wrote:
On 6/5/19 1:47 PM, Andy Lutomirski wrote:
quoted
quoted
On Jun 5, 2019, at 10:01 AM, Casey Schaufler [off-list ref] wrote:
quoted
On 6/5/2019 9:04 AM, Andy Lutomirski wrote:
quoted
On Wed, Jun 5, 2019 at 7:51 AM Casey Schaufler [off-list ref] wrote:
quoted
On 6/5/2019 1:41 AM, David Howells wrote:
Casey Schaufler [off-list ref] wrote:
quoted
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active. In the event delivery case, process A
does something (e.g. modifies a keyring) that generates an
event, which is then sent to process B's event buffer.
I think this might be the core sticking point here. It looks like two
different situations:
(1) A explicitly sends event to B (eg. signalling, sendmsg, etc.)
(2) A implicitly and unknowingly sends event to B as a side effect of some
other action (eg. B has a watch for the event A did).
The LSM treats them as the same: that is B must have MAC authorisation to send
a message to A.
YES!
Threat is about what you can do, not what you intend to do.
And it would be really great if you put some thought into what
a rational model would be for UID based controls, too.
quoted
But there are problems with not sending the event:
(1) B's internal state is then corrupt (or, at least, unknowingly invalid).
Then B is a badly written program.
Either I'm misunderstanding you or I strongly disagree.
A program needs to be aware of the conditions under
which it gets event, *including the possibility that
it may not get an event that it's not allowed*. Do you
regularly write programs that go into corrupt states
if an open() fails? Or where read() returns less than
the amount of data you ask for?
I do not regularly write programs that handle read() omitting data in the middle of a TCP stream. I also don’t write programs that wait for processes to die and need to handle the case where a child is dead, waitid() can see it, but SIGCHLD wasn’t sent because “security”.
quoted
quoted
If B has
authority to detect a certain action, and A has authority to perform
that action, then refusing to notify B because B is somehow missing
some special authorization to be notified by A is nuts.
You are hand-waving the notion of authority. You are assuming
that if A can read X and B can read X that A can write B.
No, read it again please. I’m assuming that if A can *write* X and B can read X then A can send information to B.
I guess the questions here are:
1) How do we handle recursive notification support, since we can't check
that B can read everything below a given directory easily? Perhaps we can
argue that if I have watch permission to / then that implies visibility to
everything below it but that is rather broad.
How do you handle fanotify today which I think can do this?
thanks,
greg k-h
From: David Howells <dhowells@redhat.com> Date: 2019-06-05 21:06:23
Casey Schaufler [off-list ref] wrote:
Right. You're mixing the kind of things that can generate events,
and that makes having a single policy difficult.
Whilst that's true, the notifications are clearly marked as to type, so it
should be possible to select different policies for different notification
types.
Question for you: what does the LSM *actually* need? There are a bunch of
things available, some of which may be the same thing:
(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 sb_notify,
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.
The only problem I really have is that for a destruction message you want to
get the creds of who did the last put on an object and caused it to be
destroyed - I think everything else probably gets the right creds, even if
they aren't even in the same namespaces (mount propagation, yuck).
However, that one is a biggie because close()/exit() must propagate it to
deferred-fput, which must propagate it 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.
quoted
In any case, that's what I was referring to when I said I might need to call
inode_permission(). But UIDs don't exist for all filesystems, for example,
and there are no UIDs on superblocks, mount objects or hardware events.
If you open() or stat() a file on those filesystems the UID
used in the access control comes from somewhere. Setting a watch
on things with UIDs should use the access mode on the file,
just like any other filesystem operation.
Another question for you: Do I need to let the LSM pass judgement on a watch
that a process is trying to set? I think I probably do. This would require
separate hooks for different object types:
int security_watch_key(struct watch *watch, struct key *key);
int security_watch_sb(struct watch *watch, struct path *path);
int security_watch_mount(struct watch *watch, struct path *path);
int security_watch_devices(struct watch *watch);
so that the LSM can see the object the watch is being placed on (the last has
a global queue, so there is no object).
Further, do I need to put a "void *security" pointer in struct watch and
indicate to the LSM the object bring watched? The watch could then be passed
to security_post_notification() instead of the watch queue creds (which I
could then dispense with).
security_post_notification(const struct watch *watch,
const struct cred *trigger_cred,
struct watch_notification *n);
Also, should I let the LSM audit/edit the filter set by
IOC_WATCH_QUEUE_SET_FILTER? Userspace can't retrieve the filter, so the LSM
could edit it to exclude certain things. That might be a bit too complicated,
though.
Things like superblocks are sticker because we don't generally
think of them as objects. If you can do statfs(), you should be
able to set a watch on the filesystem metadata.
How would you specify a watch for a hardware event? If you say
you have to open /dev/mumble to sent a watch for mumbles, you're
good there, too.
That's not how that works at the moment. There's a global watch list for
device events. I've repurposed it to carry any device's events - so it will
carry blockdev events (I/O errors only at the moment) and usb events
(add/remove device, add/remove bus, reset device at the moment).
quoted
Now, I could see that you ignore UIDs on things like keys and
hardware-triggered events, but how does this interact with things like mount
watches that see directories that have UIDs?
Are you advocating making it such that process B can only see events
triggered by process A if they have the same UID, for example?
It's always seemed arbitrary to me that you can't open your process up to
get signals from other users. What about putting mode bits on your ring
buffer? By default you could only accept your own events, but you could do a
rb_chmod(0222) and let all events through.
Ummm... This mechanism is pretty much about events generated by others.
Depend on what you mean by 'you' and 'your own events', it might be considered
that you would know what events you were directly causing and wouldn't need a
notification system for it.
Subject to LSM addition restrictions, of course. That would require the cred
of the process that triggered the event or a system cred for "hardware"
events. If you don't like mode bits you could use an ACL for fine
granularity or a single "let'em all in" bit for coarse.
I'm not entirely sure how an ACL would help. If someone creates a watch
queue, sets an ACL with only a "let everything in" ACE, we're back to the
situation we're in now.
As I understand it, the issue you have is stopping them getting events that
they're willing to accept that you think they shouldn't be allowed.
I'm not against access, I'm against uncontrolled access in conflict with
basic system policy.
From: Stephen Smalley <hidden> Date: 2019-06-05 21:11:59
On 6/5/19 3:28 PM, Greg KH wrote:
On Wed, Jun 05, 2019 at 02:25:33PM -0400, Stephen Smalley wrote:
quoted
On 6/5/19 1:47 PM, Andy Lutomirski wrote:
quoted
quoted
On Jun 5, 2019, at 10:01 AM, Casey Schaufler [off-list ref] wrote:
quoted
On 6/5/2019 9:04 AM, Andy Lutomirski wrote:
quoted
On Wed, Jun 5, 2019 at 7:51 AM Casey Schaufler [off-list ref] wrote:
quoted
On 6/5/2019 1:41 AM, David Howells wrote:
Casey Schaufler [off-list ref] wrote:
quoted
I will try to explain the problem once again. If process A
sends a signal (writes information) to process B the kernel
checks that either process A has the same UID as process B
or that process A has privilege to override that policy.
Process B is passive in this access control decision, while
process A is active. In the event delivery case, process A
does something (e.g. modifies a keyring) that generates an
event, which is then sent to process B's event buffer.
I think this might be the core sticking point here. It looks like two
different situations:
(1) A explicitly sends event to B (eg. signalling, sendmsg, etc.)
(2) A implicitly and unknowingly sends event to B as a side effect of some
other action (eg. B has a watch for the event A did).
The LSM treats them as the same: that is B must have MAC authorisation to send
a message to A.
YES!
Threat is about what you can do, not what you intend to do.
And it would be really great if you put some thought into what
a rational model would be for UID based controls, too.
quoted
But there are problems with not sending the event:
(1) B's internal state is then corrupt (or, at least, unknowingly invalid).
Then B is a badly written program.
Either I'm misunderstanding you or I strongly disagree.
A program needs to be aware of the conditions under
which it gets event, *including the possibility that
it may not get an event that it's not allowed*. Do you
regularly write programs that go into corrupt states
if an open() fails? Or where read() returns less than
the amount of data you ask for?
I do not regularly write programs that handle read() omitting data in the middle of a TCP stream. I also don’t write programs that wait for processes to die and need to handle the case where a child is dead, waitid() can see it, but SIGCHLD wasn’t sent because “security”.
quoted
quoted
If B has
authority to detect a certain action, and A has authority to perform
that action, then refusing to notify B because B is somehow missing
some special authorization to be notified by A is nuts.
You are hand-waving the notion of authority. You are assuming
that if A can read X and B can read X that A can write B.
No, read it again please. I’m assuming that if A can *write* X and B can read X then A can send information to B.
I guess the questions here are:
1) How do we handle recursive notification support, since we can't check
that B can read everything below a given directory easily? Perhaps we can
argue that if I have watch permission to / then that implies visibility to
everything below it but that is rather broad.
How do you handle fanotify today which I think can do this?
Doesn't appear to have been given much thought; looks like
fanotify_init() checks capable(CAP_SYS_ADMIN) and fanotify_mark() checks
inode_permission(MAY_READ) on the mount/directory/file. File
descriptors for monitored files returned upon events at least get vetted
through security_file_open() so that can prevent the monitoring process
from receiving arbitrary descriptors. Would be preferable if
fanotify_mark() did some kind of security_path_watch() or similar check,
and distinguished mounts versus directories since monitoring of
directories is not recursive.