From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:03:30
Here's a set of patches to add a general notification queue concept and to
add event sources such as:
(1) Keys/keyrings, such as linking and unlinking keys and changing their
attributes.
(2) Mount topology events, such as mounting, unmounting, mount expiry,
mount reconfiguration.
(3) Superblock events, such as R/W<->R/O changes, quota overrun and I/O
errors (not complete yet).
LSM hooks are included:
(1) A set of hooks are provided that allow an LSM to rule on whether or
not a watch may be set. Each of these hooks takes a different
"watched object" parameter, so they're not really shareable. The LSM
should use current's credentials. [Wanted by SELinux & Smack]
(2) A hook is provided to allow an LSM to rule on whether or not a
particular message may be posted to a particular queue. This is given
the credentials from the event generator (which may be the system) and
the watch setter. [Wanted by Smack]
I've provided SELinux and Smack with implementations of some of these hooks.
WHY
===
(1) Key/keyring notifications.
If you have your kerberos tickets in a file/directory, your gnome
desktop will monitor that using something like fanotify and tell you
if your credentials cache changes.
We also have the ability to cache your kerberos tickets in the
session, user or persistent keyring so that it isn't left around on
disk across a reboot or logout. Keyrings, however, cannot currently
be monitored asynchronously, so the desktop has to poll for it - not
so good on a laptop.
This source will allow the desktop to avoid the need to poll. Here's
a pull request for usage by gnome-online-accounts:
https://gitlab.gnome.org/GNOME/gnome-online-accounts/merge_requests/47
(2) Mount notifications.
This one is wanted to avoid repeated trawling of /proc/mounts or
similar to work out changes to the mount object attributes and mount
topology. I'm told that the proc file holding the namespace_sem is a
point of contention, especially as the process of generating the text
descriptions of the mounts/superblocks can be quite involved.
Whilst you can use poll() on /proc/mounts, it doesn't give you any
clues as to what changed. The notification generated here directly
indicates the mounts involved in any particular event and gives an
idea of what the change was.
This is combined with a new fsinfo() system call that allows, amongst
other things, the ability to retrieve in one go an { id,
change_counter } tuple from all the children of a specified mount,
allowing buffer overruns to be dealt with quickly.
This is of use to systemd to improve efficiency:
https://lore.kernel.org/linux-fsdevel/20200227151421.3u74ijhqt6ekbiss@ws.net.home/
And it's not just Red Hat that's potentially interested in this:
https://lore.kernel.org/linux-fsdevel/293c9bd3-f530-d75e-c353-ddeabac27cf6@6wind.com/
(3) Superblock notifications.
This one is provided to allow systemd or the desktop to more easily
detect events such as I/O errors and EDQUOT/ENOSPC. This would be of
interest to Postgres:
https://lore.kernel.org/linux-fsdevel/20200211005626.7yqjf5rbs3vbwagd@alap3.anarazel.de/
DESIGN DECISIONS
================
(1) The notification queue is built on top of a standard pipe. Messages
are effectively spliced in. The pipe is opened with a special flag:
pipe2(fds, O_NOTIFICATION_PIPE);
The special flag has the same value as O_EXCL (which doesn't seem like
it will ever be applicable in this context)[?]. It is given up front
to make it a lot easier to prohibit splice and co. from accessing the
pipe.
[?] Should this be done some other way? I'd rather not use up a new
O_* flag if I can avoid it - should I add a pipe3() system call
instead?
The pipe is then configured::
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, queue_depth);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_FILTER, &filter);
Messages are then read out of the pipe using read().
(2) It should be possible to allow write() to insert data into the
notification pipes too, but this is currently disabled as the kernel
has to be able to insert messages into the pipe *without* holding
pipe->mutex and the code to make this work needs careful auditing.
(3) sendfile(), splice() and vmsplice() are disabled on notification pipes
because of the pipe->mutex issue and also because they sometimes want
to revert what they just did - but one or more notification messages
might've been interleaved in the ring.
(4) The kernel inserts messages with the wait queue spinlock held. This
means that pipe_read() and pipe_write() have to take the spinlock to
update the queue pointers.
(5) Records in the buffer are binary, typed and have a length so that they
can be of varying size.
This allows multiple heterogeneous sources to share a common buffer;
there are 16 million types available, of which I've used just a few,
so there is scope for others to be used. Tags may be specified when a
watchpoint is created to help distinguish the sources.
(6) Records are filterable as types have up to 256 subtypes that can be
individually filtered. Other filtration is also available.
(7) Notification pipes don't interfere with each other; each may be bound
to a different set of watches. Any particular notification will be
copied to all the queues that are currently watching for it - and only
those that are watching for it.
(8) When recording a notification, the kernel will not sleep, but will
rather mark a queue as having lost a message if there's insufficient
space. read() will fabricate a loss notification message at an
appropriate point later.
(9) The notification pipe is created and then watchpoints are attached to
it, using one of:
keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fds[1], 0x01);
watch_mount(AT_FDCWD, "/", 0, fd, 0x02);
watch_sb(AT_FDCWD, "/mnt", 0, fd, 0x03);
where in both cases, fd indicates the queue and the number after is a
tag between 0 and 255.
(10) Watches are removed if either the notification pipe is destroyed or
the watched object is destroyed. In the latter case, a message will
be generated indicating the enforced watch removal.
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.
TESTING AND MANPAGES
====================
(*) The keyutils tree has a pipe-watch branch that has keyctl commands for
making use of notifications. Proposed manual pages can also be found
on this branch, though a couple of them really need to go to the main
manpages repository instead.
If the kernel supports the watching of keys, then running "make test"
on that branch will cause the testing infrastructure to spawn a
monitoring process on the side that monitors a notifications pipe for
all the key/keyring changes induced by the tests and they'll all be
checked off to make sure they happened.
https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/keyutils.git/log/?h=pipe-watch
(*) A test program is provided (samples/watch_queue/watch_test) that can
be used to monitor for keyrings, mount and superblock events.
Information on the notifications is simply logged to stdout.
The kernel patches can also be found here:
https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git/log/?h=notifications-pipe-core
Changes:
ver #5:
(*) Moved some of the bits of notify_mount() and notify_sb() out of line.
(*) Exported some event counters with the mount notifications to make it
easier for the monitoring application to maintain its state.
(*) Increment the topology change counter on the added, moved or removed
object as well as the parent(s).
(*) Renamed the "changed mount" to "auxiliary mount" in the mount
notification record.
ver #4:
(*) Dropped USB and device notifications for the moment as there's some
dispute over whether another avenue should be used for USB
notifications.
(*) Include mount and superblock event sources in the patchset.
- These now increment event counters that fsinfo() will be able to
retrieve (separate patch set).
ver #3:
(*) Rebase to after latest upstream pipe patches.
(*) Fix a missing ref get in add_watch_to_object().
ver #2:
(*) Declare O_NOTIFICATION_PIPE to use and switch it to be the same value
as O_EXCL rather then O_TMPFILE (the latter is a bit nasty in its
implementation).
ver #1:
(*) Build on top of standard pipes instead of having a driver.
David
---
David Howells (17):
uapi: General notification queue definitions
security: Add hooks to rule on setting a watch
security: Add a hook for the point of notification insertion
pipe: Add O_NOTIFICATION_PIPE
pipe: Add general notification queue support
watch_queue: Add a key/keyring notification facility
Add sample notification program
pipe: Allow buffers to be marked read-whole-or-error for notifications
pipe: Add notification lossage handling
selinux: Implement the watch_key security hook
smack: Implement the watch_key and post_notification hooks
watch_queue: Add security hooks to rule on setting mount and sb watches
watch_queue: Implement mount topology and attribute change notifications
watch_queue: sample: Display mount tree change notifications
watch_queue: Introduce a non-repeating system-unique superblock ID
watch_queue: Add superblock notifications
watch_queue: sample: Display superblock notifications
Documentation/security/keys/core.rst | 58 ++
Documentation/userspace-api/ioctl/ioctl-number.rst | 1
Documentation/watch_queue.rst | 361 +++++++++++
arch/alpha/kernel/syscalls/syscall.tbl | 2
arch/arm/tools/syscall.tbl | 2
arch/arm64/include/asm/unistd.h | 2
arch/arm64/include/asm/unistd32.h | 4
arch/ia64/kernel/syscalls/syscall.tbl | 2
arch/m68k/kernel/syscalls/syscall.tbl | 2
arch/microblaze/kernel/syscalls/syscall.tbl | 2
arch/mips/kernel/syscalls/syscall_n32.tbl | 2
arch/mips/kernel/syscalls/syscall_n64.tbl | 2
arch/mips/kernel/syscalls/syscall_o32.tbl | 2
arch/parisc/kernel/syscalls/syscall.tbl | 2
arch/powerpc/kernel/syscalls/syscall.tbl | 2
arch/s390/kernel/syscalls/syscall.tbl | 2
arch/sh/kernel/syscalls/syscall.tbl | 2
arch/sparc/kernel/syscalls/syscall.tbl | 2
arch/x86/entry/syscalls/syscall_32.tbl | 2
arch/x86/entry/syscalls/syscall_64.tbl | 2
arch/xtensa/kernel/syscalls/syscall.tbl | 2
fs/Kconfig | 21 +
fs/Makefile | 1
fs/internal.h | 1
fs/mount.h | 21 +
fs/mount_notify.c | 228 +++++++
fs/namespace.c | 22 +
fs/pipe.c | 242 +++++--
fs/splice.c | 12
fs/super.c | 205 ++++++
include/linux/dcache.h | 1
include/linux/fs.h | 62 ++
include/linux/key.h | 3
include/linux/lsm_audit.h | 1
include/linux/lsm_hooks.h | 62 ++
include/linux/pipe_fs_i.h | 27 +
include/linux/security.h | 47 +
include/linux/syscalls.h | 4
include/linux/watch_queue.h | 127 ++++
include/uapi/asm-generic/unistd.h | 6
include/uapi/linux/keyctl.h | 2
include/uapi/linux/watch_queue.h | 167 +++++
init/Kconfig | 12
kernel/Makefile | 1
kernel/sys_ni.c | 6
kernel/watch_queue.c | 659 ++++++++++++++++++++
samples/Kconfig | 6
samples/Makefile | 1
samples/watch_queue/Makefile | 7
samples/watch_queue/watch_test.c | 265 ++++++++
security/keys/Kconfig | 9
security/keys/compat.c | 3
security/keys/gc.c | 5
security/keys/internal.h | 30 +
security/keys/key.c | 38 +
security/keys/keyctl.c | 99 +++
security/keys/keyring.c | 20 -
security/keys/request_key.c | 4
security/security.c | 37 +
security/selinux/hooks.c | 14
security/smack/smack_lsm.c | 83 ++-
61 files changed, 2912 insertions(+), 107 deletions(-)
create mode 100644 Documentation/watch_queue.rst
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 kernel/watch_queue.c
create mode 100644 samples/watch_queue/Makefile
create mode 100644 samples/watch_queue/watch_test.c
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:03:32
Add UAPI definitions for the general notification queue, including the
following pieces:
(*) struct watch_notification.
This is the metadata header for notification messages. It includes a
type and subtype that indicate the source of the message
(eg. WATCH_TYPE_MOUNT_NOTIFY) and the kind of the message
(eg. NOTIFY_MOUNT_NEW_MOUNT).
The header also contains an information field that conveys the
following information:
- WATCH_INFO_LENGTH. The size of the entry (entries are variable
length).
- WATCH_INFO_ID. The watch ID specified when the watchpoint was
set.
- WATCH_INFO_TYPE_INFO. (Sub)type-specific information.
- WATCH_INFO_FLAG_*. Flag bits overlain on the type-specific
information. For use by the type.
All the information in the header can be used in filtering messages at
the point of writing into the buffer.
(*) struct watch_notification_removal
This is an extended watch-removal notification record that includes an
'id' field that can indicate the identifier of the object being
removed if available (for instance, a keyring serial number).
Signed-off-by: David Howells <dhowells@redhat.com>
---
include/uapi/linux/watch_queue.h | 55 ++++++++++++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 include/uapi/linux/watch_queue.h
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:03:44
Add security hooks that will allow an LSM to rule on whether or not a watch
may be set. More than one hook is required as the watches watch different
types of object.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Casey Schaufler <casey@schaufler-ca.com>
cc: Stephen Smalley <redacted>
cc: linux-security-module@vger.kernel.org
---
include/linux/lsm_hooks.h | 24 ++++++++++++++++++++++++
include/linux/security.h | 17 +++++++++++++++++
security/security.c | 14 ++++++++++++++
3 files changed, 55 insertions(+)
From: James Morris <jmorris@namei.org> Date: 2020-03-18 18:56:39
On Wed, 18 Mar 2020, David Howells wrote:
Add security hooks that will allow an LSM to rule on whether or not a watch
may be set. More than one hook is required as the watches watch different
types of object.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Casey Schaufler <casey@schaufler-ca.com>
cc: Stephen Smalley <redacted>
cc: linux-security-module@vger.kernel.org
---
include/linux/lsm_hooks.h | 24 ++++++++++++++++++++++++
include/linux/security.h | 17 +++++++++++++++++
security/security.c | 14 ++++++++++++++
3 files changed, 55 insertions(+)
Acked-by: James Morris <redacted>
--
James Morris
[off-list ref]
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:03:53
Add a security hook that allows an LSM to rule on whether a notification
message is allowed to be inserted into a particular watch queue.
The hook is given the following information:
(1) The credentials of the triggerer (which may be init_cred for a system
notification, eg. a hardware error).
(2) The credentials of the whoever set the watch.
(3) The notification message.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Casey Schaufler <casey@schaufler-ca.com>
cc: Stephen Smalley <redacted>
cc: linux-security-module@vger.kernel.org
---
include/linux/lsm_hooks.h | 14 ++++++++++++++
include/linux/security.h | 14 ++++++++++++++
security/security.c | 9 +++++++++
3 files changed, 37 insertions(+)
From: James Morris <jmorris@namei.org> Date: 2020-03-18 18:57:57
On Wed, 18 Mar 2020, David Howells wrote:
Add a security hook that allows an LSM to rule on whether a notification
message is allowed to be inserted into a particular watch queue.
The hook is given the following information:
(1) The credentials of the triggerer (which may be init_cred for a system
notification, eg. a hardware error).
(2) The credentials of the whoever set the watch.
(3) The notification message.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Casey Schaufler <casey@schaufler-ca.com>
cc: Stephen Smalley <redacted>
cc: linux-security-module@vger.kernel.org
---
include/linux/lsm_hooks.h | 14 ++++++++++++++
include/linux/security.h | 14 ++++++++++++++
security/security.c | 9 +++++++++
3 files changed, 37 insertions(+)
Acked-by: James Morris <redacted>
--
James Morris
[off-list ref]
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:04:02
Add an O_NOTIFICATION_PIPE flag that can be passed to pipe2() to indicate
that the pipe being created is going to be used for notifications. This
suppresses the use of splice(), vmsplice(), tee() and sendfile() on the
pipe as calling iov_iter_revert() on a pipe when a kernel notification
message has been inserted into the middle of a multi-buffer splice will be
messy.
The flag is given the same value as O_EXCL as it seems unlikely that
this flag will ever be applicable to pipes and I don't want to use up
another O_* bit unnecessarily. An alternative could be to add a pipe3()
system call.
Signed-off-by: David Howells <dhowells@redhat.com>
---
include/uapi/linux/watch_queue.h | 3 +++
1 file changed, 3 insertions(+)
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:04:12
Make it possible to have a general notification queue built on top of a
standard pipe. Notifications are 'spliced' into the pipe and then read
out. splice(), vmsplice() and sendfile() are forbidden on pipes used for
notifications as post_one_notification() cannot take pipe->mutex. This
means that notifications could be posted in between individual pipe
buffers, making iov_iter_revert() difficult to effect.
The way the notification queue is used is:
(1) An application opens a pipe with a special flag and indicates the
number of messages it wishes to be able to queue at once (this can
only be set once):
pipe2(fds, O_NOTIFICATION_PIPE);
ioctl(fds[0], IOC_WATCH_QUEUE_SET_SIZE, queue_depth);
(2) The application then uses poll() and read() as normal to extract data
from the pipe. read() will return multiple notifications if the
buffer is big enough, but it will not split a notification across
buffers - rather it will return a short read or EMSGSIZE.
Notification messages include a length in the header so that the
caller can split them up.
Each message has a 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 and type-specific flags.
Supplementary data, such as the key ID that generated an event, can be
attached in additional slots. The maximum message size is 127 bytes.
Messages may not be padded or aligned, so there is no guarantee, for
example, that the notification type will be on a 4-byte bounary.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/userspace-api/ioctl/ioctl-number.rst | 1
Documentation/watch_queue.rst | 339 ++++++++++
fs/pipe.c | 206 ++++--
fs/splice.c | 12
include/linux/pipe_fs_i.h | 19 +
include/linux/watch_queue.h | 127 ++++
include/uapi/linux/watch_queue.h | 20 +
init/Kconfig | 12
kernel/Makefile | 1
kernel/watch_queue.c | 657 ++++++++++++++++++++
10 files changed, 1318 insertions(+), 76 deletions(-)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 include/linux/watch_queue.h
create mode 100644 kernel/watch_queue.c
@@ -0,0 +1,339 @@+==============================+General notification mechanism+==============================++The general notification mechanism is built on top of the standard pipe driver+whereby it effectively splices notification messages from the kernel into pipes+opened by userspace. This can be used in conjunction with::++* Key/keyring notifications+++The notifications buffers can be enabled by:++ "General setup"/"General notification queue"+ (CONFIG_WATCH_QUEUE)++This document has the following sections:++..contents:: :local:+++Overview+========++This facility appears as a pipe that is opened in a special mode. The pipe's+internal ring buffer is used to hold messages that are generated by the kernel.+These messages are then read out by read(). Splice and similar are disabled on+such pipes due to them wanting to, under some circumstances, revert their+additions to the ring - which might end up interleaved with notification+messages.++The owner of the pipe has to tell the kernel which sources it would like to+watch through that pipe. Only sources that have been connected to a pipe will+insert messages into it. Note that a source may be bound to multiple pipes and+insert messages into all of them simultaneously.++Filters may also be emplaced on a pipe so that certain source types and+subevents can be ignored if they're not of interest.++A message will be discarded if there isn't a slot available in the ring or if+no preallocated message buffer is available. In both of these cases, read()+will insert a WATCH_META_LOSS_NOTIFICATION message into the output buffer after+the last message currently in the buffer has been read.++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.+++Message Structure+=================++Notification messages begin with a short 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 itself. There are two subtypes:++* WATCH_META_REMOVAL_NOTIFICATION+* WATCH_META_LOSS_NOTIFICATION++The first indicates that an object on which a watch was installed was removed+or destroyed and the second indicates that some messages have been lost.++"info" indicates a bunch of things, including:++* The length of the message in bytes, including the header (mask with+ WATCH_INFO_LENGTH and shift by WATCH_INFO_LENGTH__SHIFT). This indicates+ the size of the record, which may be between 8 and 127 bytes.++* The watch ID (mask with WATCH_INFO_ID and shift by WATCH_INFO_ID__SHIFT).+ This indicates that caller's ID of the watch, which may be between 0+ and 255. Multiple watches may share a queue, and this provides a means to+ distinguish them.++* A type-specific field (WATCH_INFO_TYPE_INFO). This is 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.++The header can be followed by supplementary information. The format of this is+at the discretion is defined by the type and subtype.+++Watch List (Notification Source) API+====================================++A "watch list" is a list of watchers that are subscribed to a source of+notifications. A list may be attached to an object (say a key or a superblock)+or may be global (say for device events). From a userspace perspective, a+non-global watch list is typically referred to by reference to the object it+belongs to (such as using KEYCTL_NOTIFY and giving it a key serial number to+watch that specific key).++To manage a watch list, the following functions are provided:++*``void init_watch_list(struct watch_list *wlist,+ void (*release_watch)(struct watch *wlist));``++ Initialise a watch list. If ``release_watch`` is not NULL, then this+ indicates a function that should be called when the watch_list object is+ destroyed to discard any references the watch list holds on the watched+ object.++*``void remove_watch_list(struct watch_list *wlist);``++ This removes all of the watches subscribed to a watch_list and frees them+ and then destroys the watch_list object itself.+++Watch Queue (Notification Output) API+=====================================++A "watch queue" is the buffer allocated by an application that notification+records will be written into. The workings of this are hidden entirely inside+of the pipe device driver, but it is necessary to gain a reference to it to set+a watch. These can be managed with:++*``struct watch_queue *get_watch_queue(int fd);``++ Since watch queues are indicated to the kernel by the fd of the pipe that+ implements the buffer, userspace must hand that fd through a system call.+ This can be used to look up an opaque pointer to the watch queue from the+ system call.++*``void put_watch_queue(struct watch_queue *wqueue);``++ This discards the reference obtained from ``get_watch_queue()``.+++Watch Subscription API+======================++A "watch" is a subscription on a watch list, indicating the watch queue, and+thus the buffer, into which notification records should be written. The watch+queue object may also carry filtering rules for that object, as set by+userspace. Some parts of the watch struct can be set by the driver::++ struct watch {+ union {+ u32 info_id; /* ID to be OR'd in to info field */+ ...+ };+ void *private; /* Private data for the watched object */+ u64 id; /* Internal identifier */+ ...+ };++The ``info_id`` value should be an 8-bit number obtained from userspace and+shifted by WATCH_INFO_ID__SHIFT. This is OR'd into the WATCH_INFO_ID field of+struct watch_notification::info when and if the notification is written into+the associated watch queue buffer.++The ``private`` field is the driver's data associated with the watch_list and+is cleaned up by the ``watch_list::release_watch()`` method.++The ``id`` field is the source's ID. Notifications that are posted with a+different ID are ignored.++The following functions are provided to manage watches:++*``void init_watch(struct watch *watch, struct watch_queue *wqueue);``++ Initialise a watch object, setting its pointer to the watch queue, using+ appropriate barriering to avoid lockdep complaints.++*``int add_watch_to_object(struct watch *watch, struct watch_list *wlist);``++ Subscribe a watch to a watch list (notification source). The+ driver-settable fields in the watch struct must have been set before this+ is called.++*``int remove_watch_from_object(struct watch_list *wlist,+ struct watch_queue *wqueue,+ u64 id, false);``++ Remove a watch from a watch list, where the watch must match the specified+ watch queue (``wqueue``) and object identifier (``id``). A notification+ (``WATCH_META_REMOVAL_NOTIFICATION``) is sent to the watch queue to+ indicate that the watch got removed.++*``int remove_watch_from_object(struct watch_list *wlist, NULL, 0, true);``++ Remove all the watches from a watch list. It is expected that this will be+ called preparatory to destruction and that the watch list will be+ inaccessible to new watches by this point. A notification+ (``WATCH_META_REMOVAL_NOTIFICATION``) is sent to the watch queue of each+ subscribed watch to indicate that the watch got removed.+++Notification Posting API+========================++To post a notification to watch list so that the subscribed watches can see it,+the following function should be used::++ void post_watch_notification(struct watch_list *wlist,+ struct watch_notification *n,+ const struct cred *cred,+ u64 id);++The notification should be preformatted and a pointer to the header (``n``)+should be passed in. The notification may be larger than this and the size in+units of buffer slots is noted in ``n->info & WATCH_INFO_LENGTH``.++The ``cred`` struct indicates the credentials of the source (subject) and is+passed to the LSMs, such as SELinux, to allow or suppress the recording of the+note in each individual queue according to the credentials of that queue+(object).++The ``id`` is the ID of the source object (such as the serial number on a key).+Only watches that have the same ID set in them will see this notification.+++Watch Sources+=============++Any particular buffer can be fed from multiple sources. Sources include:++* WATCH_TYPE_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.+++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_KEY_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 could be used, for example, to ignore events that are not exactly on+ the watched point in a mount tree.++*``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.+++Userspace Code Example+======================++A buffer is created with something like the following::++ pipe2(fds, O_TMPFILE);+ ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);++It can then be set to receive keyring change notifications::++ keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fds[1], 0x01);++The notifications can then be consumed by something like the following::++ static void consumer(int rfd, struct watch_queue_buffer *buf)+ {+ unsigned char buffer[128];+ ssize_t buf_len;++ while (buf_len = read(rfd, buffer, sizeof(buffer)),+ buf_len > 0+ ) {+ void *p = buffer;+ void *end = buffer + buf_len;+ while (p < end) {+ union {+ struct watch_notification n;+ unsigned char buf1[128];+ } n;+ size_t largest, len;++ largest = end - p;+ if (largest > 128)+ largest = 128;+ memcpy(&n, p, largest);++ len = (n->info & WATCH_INFO_LENGTH) >>+ WATCH_INFO_LENGTH__SHIFT;+ if (len == 0 || len > largest)+ return;++ switch (n.n.type) {+ case WATCH_TYPE_META:+ got_meta(&n.n);+ case WATCH_TYPE_KEY_NOTIFY:+ saw_key_change(&n.n);+ break;+ }++ p += len;+ }+ }+ }
@@ -906,6 +937,17 @@ int create_pipe_files(struct file **res, int flags)if(!inode)return-ENFILE;+if(flags&O_NOTIFICATION_PIPE){+#ifdef CONFIG_WATCH_QUEUE+if(watch_queue_init(inode->i_pipe)<0){+iput(inode);+return-ENOMEM;+}+#else+return-ENOPKG;+#endif+}+f=alloc_file_pseudo(inode,pipe_mnt,"",O_WRONLY|(flags&(O_NONBLOCK|O_DIRECT)),&pipefifo_fops);
@@ -936,7 +978,7 @@ static int __do_pipe_flags(int *fd, struct file **files, int flags)interror;intfdw,fdr;-if(flags&~(O_CLOEXEC|O_NONBLOCK|O_DIRECT))+if(flags&~(O_CLOEXEC|O_NONBLOCK|O_DIRECT|O_NOTIFICATION_PIPE))return-EINVAL;error=create_pipe_files(files,flags);
@@ -1184,42 +1226,12 @@ unsigned int round_pipe_size(unsigned long size)}/*-*Allocateanewarrayofpipebuffersandcopytheinfoover.Returnsthe-*pipesizeifsuccessful,orreturn-ERRORonerror.+*Resizethepiperingtoanumberofslots.*/-staticlongpipe_set_size(structpipe_inode_info*pipe,unsignedlongarg)+intpipe_resize_ring(structpipe_inode_info*pipe,unsignedintnr_slots){structpipe_buffer*bufs;-unsignedintsize,nr_slots,head,tail,mask,n;-unsignedlonguser_bufs;-longret=0;--size=round_pipe_size(arg);-nr_slots=size>>PAGE_SHIFT;--if(!nr_slots)-return-EINVAL;--/*-*Iftryingtoincreasethepipecapacity,checkthatan-*unprivilegeduserisnottryingtoexceedvariouslimits-*(softlimitcheckhere,hardlimitcheckjustbelow).-*Decreasingthepipecapacityisalwayspermitted,even-*iftheuseriscurrentlyoveralimit.-*/-if(nr_slots>pipe->ring_size&&-size>pipe_max_size&&!capable(CAP_SYS_RESOURCE))-return-EPERM;--user_bufs=account_pipe_buffers(pipe->user,pipe->ring_size,nr_slots);--if(nr_slots>pipe->ring_size&&-(too_many_pipe_buffers_hard(user_bufs)||-too_many_pipe_buffers_soft(user_bufs))&&-is_unprivileged_user()){-ret=-EPERM;-gotoout_revert_acct;-}+unsignedinthead,tail,mask,n;/**Wecanshrinkthepipe,ifargisgreaterthantheringoccupancy.
@@ -1231,17 +1243,13 @@ static long pipe_set_size(struct pipe_inode_info *pipe, unsigned long arg)head=pipe->head;tail=pipe->tail;n=pipe_occupancy(pipe->head,pipe->tail);-if(nr_slots<n){-ret=-EBUSY;-gotoout_revert_acct;-}+if(nr_slots<n)+return-EBUSY;bufs=kcalloc(nr_slots,sizeof(*bufs),GFP_KERNEL_ACCOUNT|__GFP_NOWARN);-if(unlikely(!bufs)){-ret=-ENOMEM;-gotoout_revert_acct;-}+if(unlikely(!bufs))+return-ENOMEM;/**Thepipearraywrapsaround,sojuststartthenewoneatzero
@@ -1269,16 +1277,68 @@ static long pipe_set_size(struct pipe_inode_info *pipe, unsigned long arg)kfree(pipe->bufs);pipe->bufs=bufs;pipe->ring_size=nr_slots;-pipe->max_usage=nr_slots;+if(pipe->max_usage>nr_slots)+pipe->max_usage=nr_slots;pipe->tail=tail;pipe->head=head;/* This might have made more room for writers */wake_up_interruptible(&pipe->wr_wait);+return0;+}++/*+*Allocateanewarrayofpipebuffersandcopytheinfoover.Returnsthe+*pipesizeifsuccessful,orreturn-ERRORonerror.+*/+staticlongpipe_set_size(structpipe_inode_info*pipe,unsignedlongarg)+{+unsignedlonguser_bufs;+unsignedintnr_slots,size;+longret=0;++#ifdef CONFIG_WATCH_QUEUE+if(pipe->watch_queue)+return-EBUSY;+#endif++size=round_pipe_size(arg);+nr_slots=size>>PAGE_SHIFT;++if(!nr_slots)+return-EINVAL;++/*+*Iftryingtoincreasethepipecapacity,checkthatan+*unprivilegeduserisnottryingtoexceedvariouslimits+*(softlimitcheckhere,hardlimitcheckjustbelow).+*Decreasingthepipecapacityisalwayspermitted,even+*iftheuseriscurrentlyoveralimit.+*/+if(nr_slots>pipe->max_usage&&+size>pipe_max_size&&!capable(CAP_SYS_RESOURCE))+return-EPERM;++user_bufs=account_pipe_buffers(pipe->user,pipe->nr_accounted,nr_slots);++if(nr_slots>pipe->max_usage&&+(too_many_pipe_buffers_hard(user_bufs)||+too_many_pipe_buffers_soft(user_bufs))&&+pipe_is_unprivileged_user()){+ret=-EPERM;+gotoout_revert_acct;+}++ret=pipe_resize_ring(pipe,nr_slots);+if(ret<0)+gotoout_revert_acct;++pipe->max_usage=nr_slots;+pipe->nr_accounted=nr_slots;returnpipe->max_usage*PAGE_SIZE;out_revert_acct:-(void)account_pipe_buffers(pipe->user,nr_slots,pipe->ring_size);+(void)account_pipe_buffers(pipe->user,nr_slots,pipe->nr_accounted);returnret;}
@@ -1287,9 +1347,17 @@ static long pipe_set_size(struct pipe_inode_info *pipe, unsigned long arg)*location,sochecking->i_pipeisnotenoughtoverifythatthisisa*pipe.*/-structpipe_inode_info*get_pipe_info(structfile*file)+structpipe_inode_info*get_pipe_info(structfile*file,boolfor_splice){-returnfile->f_op==&pipefifo_fops?file->private_data:NULL;+structpipe_inode_info*pipe=file->private_data;++if(file->f_op!=&pipefifo_fops||!pipe)+returnNULL;+#ifdef CONFIG_WATCH_QUEUE+if(for_splice&&pipe->watch_queue)+returnNULL;+#endif+returnpipe;}longpipe_fcntl(structfile*file,unsignedintcmd,unsignedlongarg)
@@ -1297,7 +1365,7 @@ long pipe_fcntl(struct file *file, unsigned int cmd, unsigned long arg)structpipe_inode_info*pipe;longret;-pipe=get_pipe_info(file);+pipe=get_pipe_info(file,false);if(!pipe)return-EBADF;
@@ -0,0 +1,127 @@+// SPDX-License-Identifier: GPL-2.0+/* User-mappable watch queue+*+*Copyright(C)2020RedHat,Inc.AllRightsReserved.+*WrittenbyDavidHowells(dhowells@redhat.com)+*+*SeeDocumentation/watch_queue.rst+*/++#ifndef _LINUX_WATCH_QUEUE_H+#define _LINUX_WATCH_QUEUE_H++#include<uapi/linux/watch_queue.h>+#include<linux/kref.h>+#include<linux/rcupdate.h>++#ifdef CONFIG_WATCH_QUEUE++structcred;++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;+structwatch_filter__rcu*filter;+structpipe_inode_info*pipe;/* The pipe we're using as a buffer */+structhlist_headwatches;/* Contributory watches */+structpage**notes;/* Preallocated notifications */+unsignedlong*notes_bitmap;/* Allocation bitmap for notes */+structkrefusage;/* Object usage count */+spinlock_tlock;+unsignedintnr_notes;/* Number of notes */+unsignedintnr_pages;/* Number of pages in notes[] */+booldefunct;/* T when queues closed */+};++/*+*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 */+conststructcred*cred;/* Creds of the owner of the watch */+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*);+externvoidinit_watch(structwatch*,structwatch_queue*);+externintadd_watch_to_object(structwatch*,structwatch_list*);+externintremove_watch_from_object(structwatch_list*,structwatch_queue*,u64,bool);+externlongwatch_queue_set_size(structpipe_inode_info*,unsignedint);+externlongwatch_queue_set_filter(structpipe_inode_info*,+structwatch_notification_filter__user*);+externintwatch_queue_init(structpipe_inode_info*);+externvoidwatch_queue_clear(structwatch_queue*);++staticinlinevoidinit_watch_list(structwatch_list*wlist,+void(*release_watch)(structwatch*))+{+INIT_HLIST_HEAD(&wlist->watchers);+spin_lock_init(&wlist->lock);+wlist->release_watch=release_watch;+}++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,u64id)+{+if(wlist){+remove_watch_from_object(wlist,NULL,id,true);+kfree_rcu(wlist,rcu);+}+}++/**+*watch_sizeof-Calculatetheinformationpartofthesizeofawatchrecord,+*giventhestructuresize.+*/+#define watch_sizeof(STRUCT) (sizeof(STRUCT) << WATCH_INFO_LENGTH__SHIFT)++#endif++#endif /* _LINUX_WATCH_QUEUE_H */
@@ -4,9 +4,13 @@#include<linux/types.h>#include<linux/fcntl.h>+#include<linux/ioctl.h>#define O_NOTIFICATION_PIPE O_EXCL /* Parameter to pipe2() selecting notification pipe */+#define IOC_WATCH_QUEUE_SET_SIZE _IO('W', 0x60) /* Set the size in pages */+#define IOC_WATCH_QUEUE_SET_FILTER _IO('W', 0x61) /* Set the filter */+enumwatch_notification_type{WATCH_TYPE_META=0,/* Special record */WATCH_TYPE__NR=1
@@ -41,6 +45,22 @@ struct watch_notification {#define WATCH_INFO_FLAG_7 0x00800000};+/*+*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[];+};+/**Extendedwatchremovalnotification.Thisisusedoptionallyifthetype
@@ -0,0 +1,657 @@+// SPDX-License-Identifier: GPL-2.0+/* Watch queue and general notification mechanism, built on pipes+*+*Copyright(C)2020RedHat,Inc.AllRightsReserved.+*WrittenbyDavidHowells(dhowells@redhat.com)+*+*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/sched/signal.h>+#include<linux/watch_queue.h>+#include<linux/pipe_fs_i.h>++MODULE_DESCRIPTION("Watch queue");+MODULE_AUTHOR("Red Hat, Inc.");+MODULE_LICENSE("GPL");++#define WATCH_QUEUE_NOTE_SIZE 128+#define WATCH_QUEUE_NOTES_PER_PAGE (PAGE_SIZE / WATCH_QUEUE_NOTE_SIZE)++staticvoidwatch_queue_pipe_buf_release(structpipe_inode_info*pipe,+structpipe_buffer*buf)+{+structwatch_queue*wqueue=(structwatch_queue*)buf->private;+structpage*page;+unsignedintbit;++/* We need to work out which note within the page this refers to, but+*thenotemighthavebeenmaximumsize,somerelyANDingtheoffset+*offdoesn'twork.OTOH,thenotemust'vebeenmorethanzerosize.+*/+bit=buf->offset+buf->len;+if((bit&(WATCH_QUEUE_NOTE_SIZE-1))==0)+bit-=WATCH_QUEUE_NOTE_SIZE;+bit/=WATCH_QUEUE_NOTE_SIZE;++page=buf->page;+bit+=page->index;++set_bit(bit,wqueue->notes_bitmap);+}++staticintwatch_queue_pipe_buf_steal(structpipe_inode_info*pipe,+structpipe_buffer*buf)+{+return-1;/* No. */+}++/* New data written to a pipe may be appended to a buffer with this type. */+staticconststructpipe_buf_operationswatch_queue_pipe_buf_ops={+.confirm=generic_pipe_buf_confirm,+.release=watch_queue_pipe_buf_release,+.steal=watch_queue_pipe_buf_steal,+.get=generic_pipe_buf_get,+};++/*+*Postanotificationtoawatchqueue.+*/+staticboolpost_one_notification(structwatch_queue*wqueue,+structwatch_notification*n)+{+void*p;+structpipe_inode_info*pipe=wqueue->pipe;+structpipe_buffer*buf;+structpage*page;+unsignedinthead,tail,mask,note,offset,len;+booldone=false;++if(!pipe)+returnfalse;++spin_lock_irq(&pipe->rd_wait.lock);++if(wqueue->defunct)+gotoout;++mask=pipe->ring_size-1;+head=pipe->head;+tail=pipe->tail;+if(pipe_full(head,tail,pipe->ring_size))+gotolost;++note=find_first_bit(wqueue->notes_bitmap,wqueue->nr_notes);+if(note>=wqueue->nr_notes)+gotolost;++page=wqueue->notes[note/WATCH_QUEUE_NOTES_PER_PAGE];+offset=note%WATCH_QUEUE_NOTES_PER_PAGE*WATCH_QUEUE_NOTE_SIZE;+get_page(page);+len=n->info&WATCH_INFO_LENGTH;+p=kmap_atomic(page);+memcpy(p+offset,n,len);+kunmap_atomic(p);++buf=&pipe->bufs[head&mask];+buf->page=page;+buf->private=(unsignedlong)wqueue;+buf->ops=&watch_queue_pipe_buf_ops;+buf->offset=offset;+buf->len=len;+buf->flags=0;+pipe->head=head+1;++if(!test_and_clear_bit(note,wqueue->notes_bitmap)){+spin_unlock_irq(&pipe->rd_wait.lock);+BUG();+}+wake_up_interruptible_sync_poll_locked(&pipe->rd_wait,EPOLLIN|EPOLLRDNORM);+done=true;++out:+spin_unlock_irq(&pipe->rd_wait.lock);+if(done)+kill_fasync(&pipe->fasync_readers,SIGIO,POLL_IN);+returndone;++lost:+gotoout;+}++/*+*Applyfilterrulestoanotification.+*/+staticboolfilter_watch_notification(conststructwatch_filter*wf,+conststructwatch_notification*n)+{+conststructwatch_type_filter*wt;+unsignedintst_bits=sizeof(wt->subtype_filter[0])*8;+unsignedintst_index=n->subtype/st_bits;+unsignedintst_bit=1U<<(n->subtype%st_bits);+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&&+(wt->subtype_filter[st_index]&st_bit)&&+(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.+*+*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;++if(((n->info&WATCH_INFO_LENGTH)>>WATCH_INFO_LENGTH__SHIFT)==0){+WARN_ON(1);+return;+}++rcu_read_lock();++hlist_for_each_entry_rcu(watch,&wlist->watchers,list_node){+if(watch->id!=id)+continue;+n->info&=~WATCH_INFO_ID;+n->info|=watch->info_id;++wqueue=rcu_dereference(watch->queue);+wf=rcu_dereference(wqueue->filter);+if(wf&&!filter_watch_notification(wf,n))+continue;++if(security_post_notification(watch->cred,cred,n)<0)+continue;++post_one_notification(wqueue,n);+}++rcu_read_unlock();+}+EXPORT_SYMBOL(__post_watch_notification);++/*+*Allocatesufficientpagestopreallocationfortherequestednumberof+*notifications.+*/+longwatch_queue_set_size(structpipe_inode_info*pipe,unsignedintnr_notes)+{+structwatch_queue*wqueue=pipe->watch_queue;+structpage**pages;+unsignedlong*bitmap;+unsignedlonguser_bufs;+unsignedintbmsize;+intret,i,nr_pages;++if(!wqueue)+return-ENODEV;+if(wqueue->notes)+return-EBUSY;++if(nr_notes<1||+nr_notes>512)/* TODO: choose a better hard limit */+return-EINVAL;++nr_pages=(nr_notes+WATCH_QUEUE_NOTES_PER_PAGE-1);+nr_pages/=WATCH_QUEUE_NOTES_PER_PAGE;+user_bufs=account_pipe_buffers(pipe->user,pipe->nr_accounted,nr_pages);++if(nr_pages>pipe->max_usage&&+(too_many_pipe_buffers_hard(user_bufs)||+too_many_pipe_buffers_soft(user_bufs))&&+pipe_is_unprivileged_user()){+ret=-EPERM;+gotoerror;+}++ret=pipe_resize_ring(pipe,nr_notes);+if(ret<0)+gotoerror;++pages=kcalloc(sizeof(structpage*),nr_pages,GFP_KERNEL);+if(!pages)+gotoerror;++for(i=0;i<nr_pages;i++){+pages[i]=alloc_page(GFP_KERNEL);+if(!pages[i])+gotoerror_p;+pages[i]->index=i*WATCH_QUEUE_NOTES_PER_PAGE;+}++bmsize=(nr_notes+BITS_PER_LONG-1)/BITS_PER_LONG;+bmsize*=sizeof(unsignedlong);+bitmap=kmalloc(bmsize,GFP_KERNEL);+if(!bitmap)+gotoerror_p;++memset(bitmap,0xff,bmsize);+wqueue->notes=pages;+wqueue->notes_bitmap=bitmap;+wqueue->nr_pages=nr_pages;+wqueue->nr_notes=nr_pages*WATCH_QUEUE_NOTES_PER_PAGE;+return0;++error_p:+for(i=0;i<nr_pages;i++)+__free_page(pages[i]);+kfree(pages);+error:+(void)account_pipe_buffers(pipe->user,nr_pages,pipe->nr_accounted);+returnret;+}++/*+*Setthefilteronawatchqueue.+*/+longwatch_queue_set_filter(structpipe_inode_info*pipe,+structwatch_notification_filter__user*_filter)+{+structwatch_notification_type_filter*tf;+structwatch_notification_filterfilter;+structwatch_type_filter*q;+structwatch_filter*wfilter;+structwatch_queue*wqueue=pipe->watch_queue;+intret,nr_filter=0,i;++if(!wqueue)+return-ENODEV;++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:+pipe_lock(pipe);+wfilter=rcu_replace_pointer(wqueue->filter,wfilter,+lockdep_is_held(&pipe->mutex));+pipe_unlock(pipe);+if(wfilter)+kfree_rcu(wfilter,rcu);+return0;++err_filter:+kfree(tf);+returnret;+}++staticvoid__put_watch_queue(structkref*kref)+{+structwatch_queue*wqueue=+container_of(kref,structwatch_queue,usage);+structwatch_filter*wfilter;+inti;++for(i=0;i<wqueue->nr_pages;i++)+__free_page(wqueue->notes[i]);++wfilter=rcu_access_pointer(wqueue->filter);+if(wfilter)+kfree_rcu(wfilter,rcu);+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));+put_cred(watch->cred);+}++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.@watch->credmustalso+*havebeensettotheappropriatecredentialsandareftakenonthem.+*+*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){+structwatch_queue*wq=rcu_access_pointer(w->queue);+if(wqueue==wq&&watch->id==w->id)+return-EBUSY;+}++watch->cred=get_current_cred();+rcu_assign_pointer(watch->watch_list,wlist);++spin_lock_bh(&wqueue->lock);+kref_get(&wqueue->usage);+kref_get(&watch->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_notification_removaln;+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.watch.type=WATCH_TYPE_META;+n.watch.subtype=WATCH_META_REMOVAL_NOTIFICATION;+n.watch.info=watch->info_id|watch_sizeof(n.watch);+n.id=id;+if(id!=0)+n.watch.info=watch->info_id|watch_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.watch);++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.+*/+voidwatch_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();+}++/**+*get_watch_queue-Getawatchqueuefromitsfiledescriptor.+*@fd:Thefdtoquery.+*/+structwatch_queue*get_watch_queue(intfd)+{+structpipe_inode_info*pipe;+structwatch_queue*wqueue=ERR_PTR(-EINVAL);+structfdf;++f=fdget(fd);+if(f.file){+pipe=get_pipe_info(f.file,false);+if(pipe&&pipe->watch_queue){+wqueue=pipe->watch_queue;+kref_get(&wqueue->usage);+}+fdput(f);+}++returnwqueue;+}+EXPORT_SYMBOL(get_watch_queue);++/*+*Initialiseawatchqueue+*/+intwatch_queue_init(structpipe_inode_info*pipe)+{+structwatch_queue*wqueue;++wqueue=kzalloc(sizeof(*wqueue),GFP_KERNEL);+if(!wqueue)+return-ENOMEM;++wqueue->pipe=pipe;+kref_init(&wqueue->usage);+spin_lock_init(&wqueue->lock);+INIT_HLIST_HEAD(&wqueue->watches);++pipe->watch_queue=wqueue;+return0;+}
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:04:18
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:
pipe2(fds, O_NOTIFICATION_PIPE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
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(fds[1], IOC_WATCH_QUEUE_SET_FILTER, &filter);
keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fds[1], 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 | 3 +
include/uapi/linux/keyctl.h | 2 +
include/uapi/linux/watch_queue.h | 28 +++++++++-
security/keys/Kconfig | 9 +++
security/keys/compat.c | 3 +
security/keys/gc.c | 5 ++
security/keys/internal.h | 30 ++++++++++
security/keys/key.c | 38 ++++++++-----
security/keys/keyctl.c | 99 +++++++++++++++++++++++++++++++++-
security/keys/keyring.c | 20 ++++---
security/keys/request_key.c | 4 +
12 files changed, 271 insertions(+), 28 deletions(-)
@@ -833,6 +833,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,
@@ -1026,6 +1027,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 ===============
@@ -176,6 +176,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 */
@@ -69,6 +69,7 @@#define KEYCTL_RESTRICT_KEYRING 29 /* Restrict keys allowed to link to a keyring */#define KEYCTL_MOVE 30 /* Move keys between keyrings */#define KEYCTL_CAPABILITIES 31 /* Find capabilities of keyrings subsystem */+#define KEYCTL_WATCH_KEY 32 /* Watch a key or ring of keys for changes *//* keyctl structures */structkeyctl_dh_params{
@@ -130,5 +131,6 @@ struct keyctl_pkey_params {#define KEYCTL_CAPS0_MOVE 0x80 /* KEYCTL_MOVE supported */#define KEYCTL_CAPS1_NS_KEYRING_NAME 0x01 /* Keyring names are per-user_namespace */#define KEYCTL_CAPS1_NS_KEY_TAG 0x02 /* Key indexing can include a namespace tag */+#define KEYCTL_CAPS1_NOTIFICATIONS 0x04 /* Keys generate watchable notifications */#endif /* _LINUX_KEYCTL_H */
@@ -131,6 +131,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->serial);+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);
@@ -444,6 +444,7 @@ static int __key_instantiate_and_link(struct key *key,/* mark the key as being instantiated */atomic_inc(&key->user->nikeys);mark_key_instantiated(key,0);+notify_key(key,NOTIFY_KEY_INSTANTIATED,0);if(test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT,&key->flags))awaken=1;
@@ -453,7 +454,7 @@ static int __key_instantiate_and_link(struct key *key,if(test_bit(KEY_FLAG_KEEP,&keyring->flags))set_bit(KEY_FLAG_KEEP,&key->flags);-__key_link(key,_edit);+__key_link(keyring,key,_edit);}/* disable the authorisation key */
@@ -601,6 +602,7 @@ int key_reject_and_link(struct key *key,/* mark the key as being negatively instantiated */atomic_inc(&key->user->nikeys);mark_key_instantiated(key,-error);+notify_key(key,NOTIFY_KEY_INSTANTIATED,-error);key->expiry=ktime_get_real_seconds()+timeout;key_schedule_gc(key->expiry+key_gc_delay);
@@ -611,7 +613,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)
@@ -764,9 +766,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);
@@ -1023,9 +1027,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);
@@ -1057,15 +1063,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);
@@ -1020,6 +1023,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;}
@@ -1411,10 +1415,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: James Morris <jmorris@namei.org> Date: 2020-03-18 19:04:30
On Wed, 18 Mar 2020, David Howells wrote:
quoted hunk
+++ b/Documentation/security/keys/core.rst
@@ -833,6 +833,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,
Extraneous newline.
Reviewed-by: James Morris <redacted>
--
James Morris
[off-list ref]
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:04:32
The sample program is run like:
./samples/watch_queue/watch_test
and watches "/" for mount changes and the current session keyring for key
changes:
# keyctl add user a a @s
1035096409
# keyctl unlink 1035096409 @s
producing:
# ./watch_test
read() = 16
NOTIFY[000]: ty=000001 sy=02 i=00000110
KEY 2ffc2e5d change=2[linked] aux=1035096409
read() = 16
NOTIFY[000]: ty=000001 sy=02 i=00000110
KEY 2ffc2e5d change=3[unlinked] aux=1035096409
Other events may be produced, such as with a failing disk:
read() = 22
NOTIFY[000]: ty=000003 sy=02 i=00000416
USB 3-7.7 dev-reset e=0 r=0
read() = 24
NOTIFY[000]: ty=000002 sy=06 i=00000418
BLOCK 00800050 e=6[critical medium] s=64000ef8
This corresponds to:
blk_update_request: critical medium error, dev sdf, sector 1677725432 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0
in dmesg.
Signed-off-by: David Howells <dhowells@redhat.com>
---
samples/Kconfig | 6 +
samples/Makefile | 1
samples/watch_queue/Makefile | 7 +
samples/watch_queue/watch_test.c | 183 ++++++++++++++++++++++++++++++++++++++
4 files changed, 197 insertions(+)
create mode 100644 samples/watch_queue/Makefile
create mode 100644 samples/watch_queue/watch_test.c
@@ -0,0 +1,7 @@+# 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
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:04:40
Allow a buffer to be marked such that read() must return the entire buffer
in one go or return ENOBUFS. Multiple buffers can be amalgamated into a
single read, but a short read will occur if the next "whole" buffer won't
fit.
This is useful for watch queue notifications to make sure we don't split a
notification across multiple reads, especially given that we need to
fabricate an overrun record under some circumstances - and that isn't in
the buffers.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/pipe.c | 8 +++++++-
include/linux/pipe_fs_i.h | 1 +
kernel/watch_queue.c | 2 +-
samples/watch_queue/watch_test.c | 2 +-
4 files changed, 10 insertions(+), 3 deletions(-)
@@ -8,6 +8,7 @@#define PIPE_BUF_FLAG_ATOMIC 0x02 /* was atomically mapped */#define PIPE_BUF_FLAG_GIFT 0x04 /* page is a gift */#define PIPE_BUF_FLAG_PACKET 0x08 /* read() as a packet */+#define PIPE_BUF_FLAG_WHOLE 0x10 /* read() must return entire buffer or error *//***structpipe_buffer-alinuxkernelpipebuffer
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:04:51
Add handling for loss of notifications by having read() insert a
loss-notification message after it has read the pipe buffer that was last
in the ring when the loss occurred.
Lossage can come about either by running out of notification descriptors or
by running out of space in the pipe ring.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/pipe.c | 28 ++++++++++++++++++++++++++++
include/linux/pipe_fs_i.h | 7 +++++++
kernel/watch_queue.c | 2 ++
samples/watch_queue/watch_test.c | 3 +++
4 files changed, 40 insertions(+)
@@ -9,6 +9,9 @@#define PIPE_BUF_FLAG_GIFT 0x04 /* page is a gift */#define PIPE_BUF_FLAG_PACKET 0x08 /* read() as a packet */#define PIPE_BUF_FLAG_WHOLE 0x10 /* read() must return entire buffer or error */+#ifdef CONFIG_WATCH_QUEUE+#define PIPE_BUF_FLAG_LOSS 0x20 /* Message loss happened after this buffer */+#endif/***structpipe_buffer-alinuxkernelpipebuffer
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:04:59
Implement the watch_key security hook to make sure that a key grants the
caller View permission in order to set a watch on a key.
For the moment, the watch_devices security hook is left unimplemented as
it's not obvious what the object should be since the queue is global and
didn't previously exist.
Signed-off-by: David Howells <dhowells@redhat.com>
Acked-by: Stephen Smalley <redacted>
---
security/selinux/hooks.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
From: James Morris <jmorris@namei.org> Date: 2020-03-18 19:06:34
On Wed, 18 Mar 2020, David Howells wrote:
Implement the watch_key security hook to make sure that a key grants the
caller View permission in order to set a watch on a key.
For the moment, the watch_devices security hook is left unimplemented as
it's not obvious what the object should be since the queue is global and
didn't previously exist.
Signed-off-by: David Howells <dhowells@redhat.com>
Acked-by: Stephen Smalley <redacted>
Reviewed-by: James Morris <redacted>
--
James Morris
[off-list ref]
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:05:11
Implement the watch_key security hook in Smack to make sure that a key
grants the caller Read permission in order to set a watch on a key.
Also implement the post_notification security hook to make sure that the
notification source is granted Write permission by the watch queue.
For the moment, the watch_devices security hook is left unimplemented as
it's not obvious what the object should be since the queue is global and
didn't previously exist.
Signed-off-by: David Howells <dhowells@redhat.com>
Acked-by: Casey Schaufler <casey@schaufler-ca.com>
---
include/linux/lsm_audit.h | 1 +
security/smack/smack_lsm.c | 83 +++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 83 insertions(+), 1 deletion(-)
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:05:18
Add security hooks that will allow an LSM to rule on whether or not a watch
may be set on a mount or on a superblock. More than one hook is required
as the watches watch different types of object.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Casey Schaufler <casey@schaufler-ca.com>
cc: Stephen Smalley <redacted>
cc: linux-security-module@vger.kernel.org
---
include/linux/lsm_hooks.h | 24 ++++++++++++++++++++++++
include/linux/security.h | 16 ++++++++++++++++
security/security.c | 14 ++++++++++++++
3 files changed, 54 insertions(+)
From: James Morris <jmorris@namei.org> Date: 2020-03-18 19:07:46
On Wed, 18 Mar 2020, David Howells wrote:
Add security hooks that will allow an LSM to rule on whether or not a watch
may be set on a mount or on a superblock. More than one hook is required
as the watches watch different types of object.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Casey Schaufler <casey@schaufler-ca.com>
cc: Stephen Smalley <redacted>
cc: linux-security-module@vger.kernel.org
Acked-by: James Morris <redacted>
--
James Morris
[off-list ref]
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:05:27
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.
Every mount is given a change counter than counts the number of topological
rearrangements in which it is involved and the number of attribute changes
it undergoes. This allows notification loss to be dealt with. Later
patches will provide a way to quickly retrieve this value, along with
information about topology and parameters for the superblock.
Firstly, a watch queue needs to be created:
pipe2(fds, O_NOTIFICATION_PIPE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
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(fds[1], IOC_WATCH_QUEUE_SET_FILTER, &filter);
watch_mount(AT_FDCWD, "/", 0, fds[1], 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 auxiliary_mount;
__u32 topology_changes;
__u32 attr_changes;
__u32 aux_topology_changes;
} *n;
Where:
n->watch.type will be WATCH_TYPE_MOUNT_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_MOUNT_NEW_MOUNT.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the fifth argument to
watch_mount(), shifted.
n->watch.info & NOTIFY_MOUNT_IN_SUBTREE if true indicates that the
notifcation was generated in the mount subtree rooted at the watch,
and not actually in the watch itself.
n->watch.info & NOTIFY_MOUNT_IS_RECURSIVE if true indicates that
the notifcation was generated by an event (eg. SETATTR) that was
applied recursively. The notification is only generated for the
object that initially triggered it.
n->watch.info & NOTIFY_MOUNT_IS_NOW_RO 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->watch.info & NOTIFY_MOUNT_IS_SUBMOUNT if true indicates that the
NOTIFY_MOUNT_NEW_MOUNT notification is in response to a mount
performed by the kernel (e.g. an automount).
n->triggered_on indicates the ID of the mount to which the change
was accounted (e.g. the new parent of a new mount).
n->axiliary_mount indicates the ID of an additional mount that was
affected (e.g. a new mount itself) or 0.
n->topology_changes provides the value of the topology change
counter of the triggered-on mount at the conclusion of the
operarion.
n->attr_changes provides the value of the attribute change counter
of the triggered-on mount at the conclusion of the operarion.
n->aux_topology_changes provides the value of the topology change
counter of the auxiliary mount at the conclusion of the operation.
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/watch_queue.rst | 12 +
arch/alpha/kernel/syscalls/syscall.tbl | 1
arch/arm/tools/syscall.tbl | 1
arch/arm64/include/asm/unistd.h | 2
arch/arm64/include/asm/unistd32.h | 2
arch/ia64/kernel/syscalls/syscall.tbl | 1
arch/m68k/kernel/syscalls/syscall.tbl | 1
arch/microblaze/kernel/syscalls/syscall.tbl | 1
arch/mips/kernel/syscalls/syscall_n32.tbl | 1
arch/mips/kernel/syscalls/syscall_n64.tbl | 1
arch/mips/kernel/syscalls/syscall_o32.tbl | 1
arch/parisc/kernel/syscalls/syscall.tbl | 1
arch/powerpc/kernel/syscalls/syscall.tbl | 1
arch/s390/kernel/syscalls/syscall.tbl | 1
arch/sh/kernel/syscalls/syscall.tbl | 1
arch/sparc/kernel/syscalls/syscall.tbl | 1
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
arch/xtensa/kernel/syscalls/syscall.tbl | 1
fs/Kconfig | 9 +
fs/Makefile | 1
fs/mount.h | 21 ++
fs/mount_notify.c | 228 +++++++++++++++++++++++++++
fs/namespace.c | 22 +++
include/linux/dcache.h | 1
include/linux/syscalls.h | 2
include/uapi/asm-generic/unistd.h | 4
include/uapi/linux/watch_queue.h | 36 ++++
kernel/sys_ni.c | 3
29 files changed, 355 insertions(+), 4 deletions(-)
create mode 100644 fs/mount_notify.c
@@ -8,6 +8,7 @@ opened by userspace. This can be used in conjunction with::* Key/keyring notifications+* Mount notifications. The notifications buffers can be enabled by:
@@ -233,6 +234,11 @@ Any particular buffer can be fed from multiple sources. Sources include: See Documentation/security/keys/core.rst for more information.+* WATCH_TYPE_MOUNT_NOTIFY++ Notifications of this type indicate changes to mount attributes and the+ mount topology within the subtree at the indicated point.+ Event Filtering ===============
@@ -292,9 +298,10 @@ A buffer is created with something like the following:: pipe2(fds, O_TMPFILE); ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);-It can then be set to receive keyring change notifications::+It can then be set to receive notifications:: keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fds[1], 0x01);+ watch_mount(AT_FDCWD, "/", 0, fds[1], 0x02); The notifications can then be consumed by something like the following::
@@ -331,6 +338,9 @@ The notifications can then be consumed by something like the following:: case WATCH_TYPE_KEY_NOTIFY: saw_key_change(&n.n); break;+ case WATCH_TYPE_MOUNT_NOTIFY:+ saw_mount_change(&n.n);+ break; } p += len;
@@ -477,3 +477,4 @@ # 545 reserved for clone3 547 common openat2 sys_openat2 548 common pidfd_getfd sys_pidfd_getfd+549 common watch_mount sys_watch_mount
@@ -451,3 +451,4 @@ 435 common clone3 sys_clone3 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+439 common watch_mount sys_watch_mount
@@ -358,3 +358,4 @@ # 435 reserved for clone3 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+439 common watch_mount sys_watch_mount
@@ -437,3 +437,4 @@ 435 common clone3 __sys_clone3 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+439 common watch_mount sys_watch_mount
@@ -443,3 +443,4 @@ 435 common clone3 sys_clone3 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+439 common watch_mount sys_watch_mount
@@ -435,3 +435,4 @@ 435 common clone3 sys_clone3_wrapper 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+439 common watch_mount sys_watch_mount
@@ -440,3 +440,4 @@ # 435 reserved for clone3 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+439 common watch_mount sys_watch_mount
@@ -483,3 +483,4 @@ # 435 reserved for clone3 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+439 common watch_mount sys_watch_mount
@@ -359,6 +359,7 @@ 435 common clone3 __x64_sys_clone3/ptregs 437 common openat2 __x64_sys_openat2 438 common pidfd_getfd __x64_sys_pidfd_getfd+439 common watch_mount __x64_sys_watch_mount # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -408,3 +408,4 @@ 435 common clone3 sys_clone3 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+439 common watch_mount sys_watch_mount
@@ -72,6 +73,12 @@ struct mount {intmnt_expiry_mark;/* true if marked for expiry */structhlist_headmnt_pins;structhlist_headmnt_stuck_children;+#ifdef CONFIG_MOUNT_NOTIFICATIONS+atomic_tmnt_topology_changes;/* Number of topology changes applied */+atomic_tmnt_attr_changes;/* Number of attribute changes applied */+atomic_tmnt_subtree_notifications;/* Number of notifications in subtree */+structwatch_list*mnt_watchers;/* Watches on dentries within this mount */+#endif}__randomize_layout;#define MNT_NS_INTERNAL ERR_PTR(-EINVAL) /* distinct from any mnt_namespace */
@@ -498,6 +498,9 @@ static int mnt_make_readonly(struct mount *mnt)smp_wmb();mnt->mnt.mnt_flags&=~MNT_WRITE_HOLD;unlock_mount_hash();+if(ret==0)+notify_mount(mnt,NULL,NOTIFY_MOUNT_READONLY,+NOTIFY_MOUNT_IS_NOW_RO);returnret;}
@@ -506,6 +509,7 @@ static int __mnt_unmake_readonly(struct mount *mnt)lock_mount_hash();mnt->mnt.mnt_flags&=~MNT_READONLY;unlock_mount_hash();+notify_mount(mnt,NULL,NOTIFY_MOUNT_READONLY,0);return0;}
@@ -819,6 +823,7 @@ static struct mountpoint *unhash_mnt(struct mount *mnt)*/staticvoidumount_mnt(structmount*mnt){+notify_mount(mnt->mnt_parent,mnt,NOTIFY_MOUNT_UNMOUNT,0);put_mountpoint(unhash_mnt(mnt));}
@@ -1159,6 +1164,11 @@ static void mntput_no_expire(struct mount *mnt)mnt->mnt.mnt_flags|=MNT_DOOMED;rcu_read_unlock();+#ifdef CONFIG_MOUNT_NOTIFICATIONS+if(mnt->mnt_watchers)+remove_watch_list(mnt->mnt_watchers,mnt->mnt_id);+#endif+list_del(&mnt->mnt_instance);if(unlikely(!list_empty(&mnt->mnt_mounts))){
@@ -2079,7 +2090,10 @@ static int attach_recursive_mnt(struct mount *source_mnt,}if(moving){unhash_mnt(source_mnt);+notify_mount(source_mnt->mnt_parent,source_mnt,+NOTIFY_MOUNT_MOVE_FROM,0);attach_mnt(source_mnt,dest_mnt,dest_mp);+notify_mount(dest_mnt,source_mnt,NOTIFY_MOUNT_MOVE_TO,0);touch_mnt_namespace(source_mnt->mnt_ns);}else{if(source_mnt->mnt_ns){
@@ -2088,6 +2102,11 @@ static int attach_recursive_mnt(struct mount *source_mnt,}mnt_set_mountpoint(dest_mnt,dest_mp,source_mnt);commit_tree(source_mnt);+notify_mount(dest_mnt,source_mnt,NOTIFY_MOUNT_NEW_MOUNT,+(source_mnt->mnt.mnt_sb->s_flags&SB_RDONLY?+NOTIFY_MOUNT_IS_NOW_RO:0)|+(source_mnt->mnt.mnt_sb->s_flags&SB_SUBMOUNT?+NOTIFY_MOUNT_IS_SUBMOUNT:0));}hlist_for_each_entry_safe(child,n,&tree_list,mnt_hash){
@@ -2464,6 +2483,8 @@ static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags)mnt->mnt.mnt_flags=mnt_flags;touch_mnt_namespace(mnt->mnt_ns);unlock_mount_hash();+notify_mount(mnt,NULL,NOTIFY_MOUNT_SETATTR,+(mnt_flags&SB_RDONLY?NOTIFY_MOUNT_IS_NOW_RO:0));}staticvoidmnt_warn_timestamp_expiry(structpath*mountpoint,structvfsmount*mnt)
@@ -1003,6 +1003,8 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,siginfo_t__user*info,unsignedintflags);asmlinkagelongsys_pidfd_getfd(intpidfd,intfd,unsignedintflags);+asmlinkagelongsys_watch_mount(intdfd,constchar__user*path,+unsignedintat_flags,intwatch_fd,intwatch_id);/**Architecture-specificsystemcalls
@@ -14,7 +14,8 @@enumwatch_notification_type{WATCH_TYPE_META=0,/* Special record */WATCH_TYPE_KEY_NOTIFY=1,/* Key change event notification */-WATCH_TYPE__NR=2+WATCH_TYPE_MOUNT_NOTIFY=2,/* Mount topology change notification */+WATCH_TYPE___NR=3};enumwatch_meta_notification_subtype{
@@ -101,4 +102,37 @@ 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) */+};++#define NOTIFY_MOUNT_IN_SUBTREE WATCH_INFO_FLAG_0 /* Event not actually at watched dentry */+#define NOTIFY_MOUNT_IS_RECURSIVE WATCH_INFO_FLAG_1 /* Change applied recursively */+#define NOTIFY_MOUNT_IS_NOW_RO WATCH_INFO_FLAG_2 /* Mount changed to R/O */+#define NOTIFY_MOUNT_IS_SUBMOUNT WATCH_INFO_FLAG_3 /* New mount is submount */++/*+*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 triggered the notification */+__u32auxiliary_mount;/* Added/moved/removed mount or 0 */+__u32topology_changes;/* trigger: Number of topology changes applied */+__u32attr_changes;/* trigger: Number of attribute changes applied */+__u32aux_topology_changes;/* aux: Number of topology changes applied */+__u32__padding;+};+#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
On Wed, Mar 18, 2020 at 4:05 PM David Howells [off-list ref] wrote:
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.
Every mount is given a change counter than counts the number of topological
rearrangements in which it is involved and the number of attribute changes
it undergoes. This allows notification loss to be dealt with.
Isn't queue overrun signalled anyway?
If an event is lost, there's no way to know which object was affected,
so how does the counter help here?
Later
patches will provide a way to quickly retrieve this value, along with
information about topology and parameters for the superblock.
So? If we receive a notification for MNT1 with change counter CTR1
and then receive the info for MNT1 with CTR2, then we know that we
either missed a notification or we raced and will receive the
notification later. This helps with not having to redo the query when
we receive the notification with CTR2, but this is just an
optimization, not really useful.
Firstly, a watch queue needs to be created:
pipe2(fds, O_NOTIFICATION_PIPE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
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(fds[1], IOC_WATCH_QUEUE_SET_FILTER, &filter);
watch_mount(AT_FDCWD, "/", 0, fds[1], 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.
Does it make sense to watch a single mount? A set of mounts? A
subtree with an exclusion list (subtrees, types, ???)?
Not asking for these to be implemented initially, just questioning
whether the API is flexible enough to allow these cases to be
implemented later if needed.
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 auxiliary_mount;
What guarantees that mount_id is going to remain a 32bit entity?
Being 32bit this introduces wraparound effects. Is that really worth it?
} *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.
Hmm, size of record limited to 112bytes? Is this verified somewhere?
Don't see a BUILD_BUG_ON() in watch_sizeof().
n->watch.info & WATCH_INFO_ID will be the fifth argument to
watch_mount(), shifted.
n->watch.info & NOTIFY_MOUNT_IN_SUBTREE if true indicates that the
notifcation was generated in the mount subtree rooted at the watch,
notification
and not actually in the watch itself.
n->watch.info & NOTIFY_MOUNT_IS_RECURSIVE if true indicates that
the notifcation was generated by an event (eg. SETATTR) that was
applied recursively. The notification is only generated for the
object that initially triggered it.
Unused in this patchset. Please don't add things to the API which are not used.
n->watch.info & NOTIFY_MOUNT_IS_NOW_RO will be used for
NOTIFY_MOUNT_READONLY, being set if the superblock becomes R/O, and
being cleared otherwise,
Does this refer to mount r/o flag or superblock r/o flag? Confused.
and for NOTIFY_MOUNT_NEW_MOUNT, being set
if the new mount is a submount (e.g. an automount).
Huh? What has r/o flag do with being a submount?
n->watch.info & NOTIFY_MOUNT_IS_SUBMOUNT if true indicates that the
NOTIFY_MOUNT_NEW_MOUNT notification is in response to a mount
performed by the kernel (e.g. an automount).
n->triggered_on indicates the ID of the mount to which the change
was accounted (e.g. the new parent of a new mount).
For move there are two parents that are affected. This doesn't look
sufficient to reflect that.
n->axiliary_mount indicates the ID of an additional mount that was
affected (e.g. a new mount itself) or 0.
n->topology_changes provides the value of the topology change
counter of the triggered-on mount at the conclusion of the
operarion.
operation
n->attr_changes provides the value of the attribute change counter
of the triggered-on mount at the conclusion of the operarion.
operation
n->aux_topology_changes provides the value of the topology change
counter of the auxiliary mount at the conclusion of the operation.
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.
From: Ian Kent <raven@themaw.net> Date: 2020-06-14 03:08:02
On Thu, 2020-04-02 at 17:19 +0200, Miklos Szeredi wrote:
quoted
Firstly, a watch queue needs to be created:
pipe2(fds, O_NOTIFICATION_PIPE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
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(fds[1], IOC_WATCH_QUEUE_SET_FILTER, &filter);
watch_mount(AT_FDCWD, "/", 0, fds[1], 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.
Does it make sense to watch a single mount? A set of mounts? A
subtree with an exclusion list (subtrees, types, ???)?
Yes, filtering, perhaps, I'm not sure a single mount is useful
as changes generally need to be monitored for a set of mounts.
Monitoring a subtree is obviously possible because the monitor
path doesn't need to be "/".
Or am I misunderstanding what your trying to get at.
The notion of filtering types and other things is interesting
but what I've seen that doesn't fit in the current implementation
so far probably isn't appropriate for kernel implementation.
There's a special case of acquiring a list of mounts where the
path is not a mount point itself but you need all mount below
that path prefix.
In this case you get all mounts, including the mounts of the mount
containing the path, so you still need to traverse the list to match
the prefix and that can easily mean the whole list of mounts in the
system.
Point is it leads to multiple traversals of a larger than needed list
of mounts, one to get the list of mounts to check, and one to filter
on the prefix.
I've seen this use case with fsinfo() and that's where it's needed
although it may be useful to carry it through to notifications as
well.
While this sounds like it isn't such a big deal it can sometimes
make a considerable difference to the number of mounts you need
to traverse when there are a large number of mounts in the system.
I didn't consider it appropriate for kernel implementation but
since you asked here it is. OTOH were checking for connectedness
in fsinfo() anyway so maybe this is something that could be done
without undue overhead.
But that's all I've seen so far.
Ian
On Sun, Jun 14, 2020 at 5:07 AM Ian Kent [off-list ref] wrote:
On Thu, 2020-04-02 at 17:19 +0200, Miklos Szeredi wrote:
quoted
quoted
Firstly, a watch queue needs to be created:
pipe2(fds, O_NOTIFICATION_PIPE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
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(fds[1], IOC_WATCH_QUEUE_SET_FILTER, &filter);
watch_mount(AT_FDCWD, "/", 0, fds[1], 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.
Does it make sense to watch a single mount? A set of mounts? A
subtree with an exclusion list (subtrees, types, ???)?
Yes, filtering, perhaps, I'm not sure a single mount is useful
as changes generally need to be monitored for a set of mounts.
Monitoring a subtree is obviously possible because the monitor
path doesn't need to be "/".
Or am I misunderstanding what your trying to get at.
The notion of filtering types and other things is interesting
but what I've seen that doesn't fit in the current implementation
so far probably isn't appropriate for kernel implementation.
There's a special case of acquiring a list of mounts where the
path is not a mount point itself but you need all mount below
that path prefix.
In this case you get all mounts, including the mounts of the mount
containing the path, so you still need to traverse the list to match
the prefix and that can easily mean the whole list of mounts in the
system.
Point is it leads to multiple traversals of a larger than needed list
of mounts, one to get the list of mounts to check, and one to filter
on the prefix.
I've seen this use case with fsinfo() and that's where it's needed
although it may be useful to carry it through to notifications as
well.
While this sounds like it isn't such a big deal it can sometimes
make a considerable difference to the number of mounts you need
to traverse when there are a large number of mounts in the system.
I didn't consider it appropriate for kernel implementation but
since you asked here it is. OTOH were checking for connectedness
in fsinfo() anyway so maybe this is something that could be done
without undue overhead.
Good point. Filtering notifications for mounts outside of the
specified path makes sense.
Thanks,
Miklos
From: David Howells <dhowells@redhat.com> Date: 2020-07-23 10:49:03
Miklos Szeredi [off-list ref] wrote:
On Wed, Mar 18, 2020 at 4:05 PM David Howells [off-list ref] wrote:
quoted
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.
Every mount is given a change counter than counts the number of topological
rearrangements in which it is involved and the number of attribute changes
it undergoes. This allows notification loss to be dealt with.
Isn't queue overrun signalled anyway?
If an event is lost, there's no way to know which object was affected,
so how does the counter help here?
An event may up the counter multiple times. For example, imagine that you
do the following:
mkdir /foo
mount -t tmpfs none /foo
mkdir /foo/b
chroot /foo/b
watch_mount("/")
now someone else comes along and does:
mkdir /foo/a
mkdir /foo/b/c
mount -t tmpfs none /foo/a
mount -o move /foo/a /foo/b/c
thereby moving a mount from outside your chroot window to inside of it. The
move will generate two events (move-from and move-to), but you'll only get to
see one of them. The usage on the mount at /foo, however, will be bumped by
2, not 1.
Also, if someone instead does this:
mkdir /foo/a/d
mkdir /foo/a/e
mount -t tmpfs none /foo/a/d
mount -o move /foo/a/e /foo/a/e
you won't get any notifications, but the counter still got bumped by 2.
You'll see an unusual bump in it at the next event, but you know you didn't
miss any events that pertain to you and can keep your copy of the counter up
to date... provided there hasn't been an overrun.
If there has been an overrun, you ask fsinfo() for a list of
{mount_id,counter} and then you have to scan anything where the counter has
changed unexpectedly. It gives you the chance to keep up to date more
readily.
Maybe putting the counter into the notification message isn't really
necessary, but it's cheap to do if the counter is available.
quoted
Later
patches will provide a way to quickly retrieve this value, along with
information about topology and parameters for the superblock.
So? If we receive a notification for MNT1 with change counter CTR1
and then receive the info for MNT1 with CTR2, then we know that we
either missed a notification or we raced and will receive the
notification later. This helps with not having to redo the query when
we receive the notification with CTR2, but this is just an
optimization, not really useful.
Are optimisations ever useful?
quoted
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.
Does it make sense to watch a single mount? A set of mounts? A
subtree with an exclusion list (subtrees, types, ???)?
Not asking for these to be implemented initially, just questioning
whether the API is flexible enough to allow these cases to be
implemented later if needed.
You can watch a single mount or a whole subtree. I could make it possible to
add exclusions into the filter list.
quoted
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 auxiliary_mount;
What guarantees that mount_id is going to remain a 32bit entity?
You think it likely we'd have >4 billion concurrent mounts on a system? That
would require >1.2TiB of RAM just for the struct mount allocations.
But I can expand it to __u64.
Being 32bit this introduces wraparound effects. Is that really worth it?
You'd have to make 2 billion changes without whoever's monitoring getting a
chance to update their counters. But maybe it's not worth it putting them
here. If you'd prefer, I can make the counters all 64-bit and just retrieve
them with fsinfo().
quoted
} *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.
Hmm, size of record limited to 112bytes? Is this verified somewhere?
Don't see a BUILD_BUG_ON() in watch_sizeof().
127 bytes now, including the header. I can add a BUILD_BUG_ON().
quoted
n->watch.info & NOTIFY_MOUNT_IS_RECURSIVE if true indicates that
the notifcation was generated by an event (eg. SETATTR) that was
applied recursively. The notification is only generated for the
object that initially triggered it.
Unused in this patchset. Please don't add things to the API which are not
used.
Christian Brauner has patches for mount_setattr() that will need to use this.
quoted
n->watch.info & NOTIFY_MOUNT_IS_NOW_RO will be used for
NOTIFY_MOUNT_READONLY, being set if the superblock becomes R/O, and
being cleared otherwise,
Does this refer to mount r/o flag or superblock r/o flag? Confused.
Sorry, that should be "mount".
quoted
and for NOTIFY_MOUNT_NEW_MOUNT, being set
if the new mount is a submount (e.g. an automount).
Huh? What has r/o flag do with being a submount?
That should read "if the new mount is readonly".
quoted
n->watch.info & NOTIFY_MOUNT_IS_SUBMOUNT if true indicates that the
NOTIFY_MOUNT_NEW_MOUNT notification is in response to a mount
performed by the kernel (e.g. an automount).
n->triggered_on indicates the ID of the mount to which the change
was accounted (e.g. the new parent of a new mount).
For move there are two parents that are affected. This doesn't look
sufficient to reflect that.
You get up to two messages in that case:
NOTIFY_MOUNT_MOVE_FROM = 5, /* Mount moved from here */
NOTIFY_MOUNT_MOVE_TO = 6, /* Mount moved to here (compare op_id) */
but either message may get filtered because the event occurred outside of your
watched tree.
David
From: David Howells <dhowells@redhat.com> Date: 2020-07-24 10:20:11
David Howells [off-list ref] wrote:
quoted
What guarantees that mount_id is going to remain a 32bit entity?
You think it likely we'd have >4 billion concurrent mounts on a system? That
would require >1.2TiB of RAM just for the struct mount allocations.
But I can expand it to __u64.
That said, sys_name_to_handle_at() assumes it's a 32-bit signed integer, so
we're currently limited to ~2 billion concurrent mounts:-/
David
From: Ian Kent <raven@themaw.net> Date: 2020-07-24 10:44:19
On Fri, 2020-07-24 at 11:19 +0100, David Howells wrote:
David Howells [off-list ref] wrote:
quoted
quoted
What guarantees that mount_id is going to remain a 32bit entity?
You think it likely we'd have >4 billion concurrent mounts on a
system? That
would require >1.2TiB of RAM just for the struct mount allocations.
But I can expand it to __u64.
That said, sys_name_to_handle_at() assumes it's a 32-bit signed
integer, so
we're currently limited to ~2 billion concurrent mounts:-/
I was wondering about id re-use.
Assuming that ids that are returned to the idr db are re-used
what would the chance that a recently used id would end up
being used?
Would that chance increase as ids are consumed and freed over
time?
Yeah, it's one of those questions ... ;)
Ian
From: David Howells <dhowells@redhat.com> Date: 2020-07-24 11:36:19
Ian Kent [off-list ref] wrote:
I was wondering about id re-use.
Assuming that ids that are returned to the idr db are re-used
what would the chance that a recently used id would end up
being used?
Would that chance increase as ids are consumed and freed over
time?
I've added something to deal with that in the fsinfo branch. I've given each
mount object and superblock a supplementary 64-bit unique ID that's not likely
to repeat before we're no longer around to have to worry about it.
fsinfo() then allows you to retrieve them by path or by mount ID.
So, yes, mnt_id and s_dev are not unique and may be reused very quickly, but
I'm also providing uniquifiers that you can check.
David
On Fri, Jul 24, 2020 at 1:36 PM David Howells [off-list ref] wrote:
Ian Kent [off-list ref] wrote:
quoted
I was wondering about id re-use.
Assuming that ids that are returned to the idr db are re-used
what would the chance that a recently used id would end up
being used?
Would that chance increase as ids are consumed and freed over
time?
I've added something to deal with that in the fsinfo branch. I've given each
mount object and superblock a supplementary 64-bit unique ID that's not likely
to repeat before we're no longer around to have to worry about it.
fsinfo() then allows you to retrieve them by path or by mount ID.
Shouldn't the notification interface provide the unique ID?
Thanks,
Miklos
So, yes, mnt_id and s_dev are not unique and may be reused very quickly, but
I'm also providing uniquifiers that you can check.
David
From: David Howells <dhowells@redhat.com> Date: 2020-08-03 10:18:18
Miklos Szeredi [off-list ref] wrote:
quoted
fsinfo() then allows you to retrieve them by path or by mount ID.
Shouldn't the notification interface provide the unique ID?
Hmmm... If I'm going to do that, I have to put the fsinfo-core branch first
otherwise you can't actually retrieve the unique ID - and thus won't be able
to make sense of the notification record. Such a rearrangement might make
sense anyway since Ian and Karel have been primarily concentrating on fsinfo
and only more recently started adding notification support.
David
On Mon, Aug 3, 2020 at 12:18 PM David Howells [off-list ref] wrote:
Miklos Szeredi [off-list ref] wrote:
quoted
quoted
fsinfo() then allows you to retrieve them by path or by mount ID.
Shouldn't the notification interface provide the unique ID?
Hmmm... If I'm going to do that, I have to put the fsinfo-core branch first
otherwise you can't actually retrieve the unique ID - and thus won't be able
to make sense of the notification record. Such a rearrangement might make
sense anyway since Ian and Karel have been primarily concentrating on fsinfo
and only more recently started adding notification support.
OTOH mount notification is way smaller and IMO a more mature
interface. So just picking the unique ID patch into this set might
make sense.
Thanks,
Miklos
From: David Howells <dhowells@redhat.com> Date: 2020-08-03 11:49:49
Miklos Szeredi [off-list ref] wrote:
OTOH mount notification is way smaller and IMO a more mature
interface. So just picking the unique ID patch into this set might
make sense.
But userspace can't retrieve the unique ID without fsinfo() as things stand.
I'm changing it so that the fields are 64-bit, but initialised with the
existing mount ID in the notifications set. The fsinfo set changes that to a
unique ID. I'm tempted to make the unique IDs start at UINT_MAX+1 to
disambiguate them.
David
From: Ian Kent <raven@themaw.net> Date: 2020-08-03 12:02:32
On Mon, 2020-08-03 at 12:49 +0100, David Howells wrote:
Miklos Szeredi [off-list ref] wrote:
quoted
OTOH mount notification is way smaller and IMO a more mature
interface. So just picking the unique ID patch into this set might
make sense.
But userspace can't retrieve the unique ID without fsinfo() as things
stand.
I'm changing it so that the fields are 64-bit, but initialised with
the
existing mount ID in the notifications set. The fsinfo set changes
that to a
unique ID. I'm tempted to make the unique IDs start at UINT_MAX+1 to
disambiguate them.
Mmm ... so what would I use as a mount id that's not used, like NULL
for strings?
I'm using -1 now but changing this will mean I need something
different.
Could we set aside a mount id that will never be used so it can be
used for this case?
Maybe mount ids should start at 1 instead of zero ...
Ian
From: David Howells <dhowells@redhat.com> Date: 2020-08-03 12:32:04
Ian Kent [off-list ref] wrote:
quoted
I'm changing it so that the fields are 64-bit, but initialised with the
existing mount ID in the notifications set. The fsinfo set changes that
to a unique ID. I'm tempted to make the unique IDs start at UINT_MAX+1 to
disambiguate them.
Mmm ... so what would I use as a mount id that's not used, like NULL
for strings?
Zero is skipped, so you could use that.
I'm using -1 now but changing this will mean I need something
different.
It's 64-bits, so you're not likely to see it reach -1, even if it does start
at UINT_MAX+1.
David
From: Ian Kent <raven@themaw.net> Date: 2020-08-03 14:30:43
On Mon, 2020-08-03 at 13:31 +0100, David Howells wrote:
Ian Kent [off-list ref] wrote:
quoted
quoted
I'm changing it so that the fields are 64-bit, but initialised
with the
existing mount ID in the notifications set. The fsinfo set
changes that
to a unique ID. I'm tempted to make the unique IDs start at
UINT_MAX+1 to
disambiguate them.
Mmm ... so what would I use as a mount id that's not used, like
NULL
for strings?
Zero is skipped, so you could use that.
quoted
I'm using -1 now but changing this will mean I need something
different.
It's 64-bits, so you're not likely to see it reach -1, even if it
does start
at UINT_MAX+1.
Ha, either or, I don't think it will be a problem, there's
bound to be a few changes so the components using this will
need to change a bit before it's finalized, shouldn't be a
big deal I think. At least not for me and shouldn't be much
for libmount either I think.
Ian
Being 32bit this introduces wraparound effects. Is that really worth it?
You'd have to make 2 billion changes without whoever's monitoring getting a
chance to update their counters. But maybe it's not worth it putting them
here. If you'd prefer, I can make the counters all 64-bit and just retrieve
them with fsinfo().
Yes, I think that would be preferable.
quoted
quoted
n->watch.info & NOTIFY_MOUNT_IS_RECURSIVE if true indicates that
the notifcation was generated by an event (eg. SETATTR) that was
applied recursively. The notification is only generated for the
object that initially triggered it.
Unused in this patchset. Please don't add things to the API which are not
used.
Christian Brauner has patches for mount_setattr() that will need to use this.
Fine, then that patch can add the flag.
Thanks,
Miklos
Being 32bit this introduces wraparound effects. Is that really
worth it?
You'd have to make 2 billion changes without whoever's monitoring
getting a
chance to update their counters. But maybe it's not worth it
putting them
here. If you'd prefer, I can make the counters all 64-bit and just
retrieve
them with fsinfo().
Yes, I think that would be preferable.
I think this is the source of the recommendation for removing the
change counters from the notification message, correct?
While it looks like I may not need those counters for systemd message
buffer overflow handling myself I think removing them from the
notification message isn't a sensible thing to do.
If you need to detect missing messages, perhaps due to message buffer
overflow, then you need change counters that are relevant to the
notification message itself. That's so the next time you get a message
for that object you can be sure that change counter comparisons you
you make relate to object notifications you have processed.
Yes, I know it isn't quite that simple, but tallying up what you have
processed in the current batch of messages (or in multiple batches of
messages if more than one read has been possible) to perform the check
is a user space responsibility. And it simply can't be done if the
counters consistency is in question which it would be if you need to
perform another system call to get it.
It's way more useful to have these in the notification than obtainable
via fsinfo() IMHO.
quoted
quoted
quoted
n->watch.info & NOTIFY_MOUNT_IS_RECURSIVE if true
indicates that
the notifcation was generated by an event (eg. SETATTR)
that was
applied recursively. The notification is only
generated for the
object that initially triggered it.
Unused in this patchset. Please don't add things to the API
which are not
used.
Christian Brauner has patches for mount_setattr() that will need to
use this.
Fine, then that patch can add the flag.
Thanks,
Miklos
Being 32bit this introduces wraparound effects. Is that really
worth it?
You'd have to make 2 billion changes without whoever's monitoring
getting a
chance to update their counters. But maybe it's not worth it
putting them
here. If you'd prefer, I can make the counters all 64-bit and just
retrieve
them with fsinfo().
Yes, I think that would be preferable.
I think this is the source of the recommendation for removing the
change counters from the notification message, correct?
While it looks like I may not need those counters for systemd message
buffer overflow handling myself I think removing them from the
notification message isn't a sensible thing to do.
If you need to detect missing messages, perhaps due to message buffer
overflow, then you need change counters that are relevant to the
notification message itself. That's so the next time you get a message
for that object you can be sure that change counter comparisons you
you make relate to object notifications you have processed.
I don't quite get it. Change notification is just that: a
notification. You need to know what object that notification relates
to, to be able to retrieve the up to date attributes of said object.
What happens if you get a change counter N in the notification
message, then get a change counter N + 1 in the attribute retrieval?
You know that another change happened, and you haven't yet processed
the notification yet. So when the notification with N + 1 comes in,
you can optimize away the attribute retrieve.
Nice optimization, but it's optimizing a race condition, and I don't
think that's warranted. I don't see any other use for the change
counter in the notification message.
Yes, I know it isn't quite that simple, but tallying up what you have
processed in the current batch of messages (or in multiple batches of
messages if more than one read has been possible) to perform the check
is a user space responsibility. And it simply can't be done if the
counters consistency is in question which it would be if you need to
perform another system call to get it.
It's way more useful to have these in the notification than obtainable
via fsinfo() IMHO.
What is it useful for?
If the notification itself would contain the list of updated
attributes and their new values, then yes, this would make sense. If
the notification just tells us that the object was modified, but not
the modifications themselves, then I don't see how the change counter
in itself could add any information (other than optimizing the race
condition above).
Thanks,
Miklos
Thanks,
quoted
quoted
quoted
quoted
n->watch.info & NOTIFY_MOUNT_IS_RECURSIVE if true
indicates that
the notifcation was generated by an event (eg. SETATTR)
that was
applied recursively. The notification is only
generated for the
object that initially triggered it.
Unused in this patchset. Please don't add things to the API
which are not
used.
Christian Brauner has patches for mount_setattr() that will need to
use this.
Fine, then that patch can add the flag.
Thanks,
Miklos
Being 32bit this introduces wraparound effects. Is that
really
worth it?
You'd have to make 2 billion changes without whoever's
monitoring
getting a
chance to update their counters. But maybe it's not worth it
putting them
here. If you'd prefer, I can make the counters all 64-bit and
just
retrieve
them with fsinfo().
Yes, I think that would be preferable.
I think this is the source of the recommendation for removing the
change counters from the notification message, correct?
While it looks like I may not need those counters for systemd
message
buffer overflow handling myself I think removing them from the
notification message isn't a sensible thing to do.
If you need to detect missing messages, perhaps due to message
buffer
overflow, then you need change counters that are relevant to the
notification message itself. That's so the next time you get a
message
for that object you can be sure that change counter comparisons you
you make relate to object notifications you have processed.
I don't quite get it. Change notification is just that: a
notification. You need to know what object that notification
relates
to, to be able to retrieve the up to date attributes of said object.
What happens if you get a change counter N in the notification
message, then get a change counter N + 1 in the attribute retrieval?
You know that another change happened, and you haven't yet processed
the notification yet. So when the notification with N + 1 comes in,
you can optimize away the attribute retrieve.
Nice optimization, but it's optimizing a race condition, and I don't
think that's warranted. I don't see any other use for the change
counter in the notification message.
quoted
Yes, I know it isn't quite that simple, but tallying up what you
have
processed in the current batch of messages (or in multiple batches
of
messages if more than one read has been possible) to perform the
check
is a user space responsibility. And it simply can't be done if the
counters consistency is in question which it would be if you need
to
perform another system call to get it.
It's way more useful to have these in the notification than
obtainable
via fsinfo() IMHO.
What is it useful for?
Only to verify that you have seen all the notifications.
If you have to grab that info with a separate call then the count
isn't necessarily consistent because other notifications can occur
while you grab it.
My per-object rant isn't quite right, what's needed is a consistent
way to verify you have seen everything you were supposed to.
I think your point is that if you grab the info in another call and
it doesn't match you need to refresh and that's fine but I think it's
better to be able to verify you have got everything that was sent as
you go and avoid the need for the refresh more often.
If the notification itself would contain the list of updated
attributes and their new values, then yes, this would make sense. If
the notification just tells us that the object was modified, but not
the modifications themselves, then I don't see how the change counter
in itself could add any information (other than optimizing the race
condition above).
Thanks,
Miklos
Thanks,
quoted
quoted
quoted
quoted
quoted
n->watch.info & NOTIFY_MOUNT_IS_RECURSIVE if true
indicates that
the notifcation was generated by an event (eg.
SETATTR)
that was
applied recursively. The notification is only
generated for the
object that initially triggered it.
Unused in this patchset. Please don't add things to the API
which are not
used.
Christian Brauner has patches for mount_setattr() that will
need to
use this.
Fine, then that patch can add the flag.
Thanks,
Miklos
On Wed, Aug 5, 2020 at 3:54 AM Ian Kent [off-list ref] wrote:
quoted
quoted
It's way more useful to have these in the notification than
obtainable
via fsinfo() IMHO.
What is it useful for?
Only to verify that you have seen all the notifications.
If you have to grab that info with a separate call then the count
isn't necessarily consistent because other notifications can occur
while you grab it.
No, no no. The watch queue will signal an overflow, without any
additional overhead for the normal case. If you think of this as a
protocol stack, then the overflow detection happens on the transport
layer, instead of the application layer. The application layer is
responsible for restoring state in case of a transport layer error,
but detection of that error is not the responsibility of the
application layer.
Thanks,
Miklos
From: Ian Kent <raven@themaw.net> Date: 2020-08-05 20:14:00
On Wed, 2020-08-05 at 09:43 +0200, Miklos Szeredi wrote:
On Wed, Aug 5, 2020 at 3:54 AM Ian Kent [off-list ref] wrote:
quoted
quoted
quoted
It's way more useful to have these in the notification than
obtainable
via fsinfo() IMHO.
What is it useful for?
Only to verify that you have seen all the notifications.
If you have to grab that info with a separate call then the count
isn't necessarily consistent because other notifications can occur
while you grab it.
No, no no. The watch queue will signal an overflow, without any
additional overhead for the normal case. If you think of this as a
protocol stack, then the overflow detection happens on the transport
layer, instead of the application layer. The application layer is
responsible for restoring state in case of a transport layer error,
but detection of that error is not the responsibility of the
application layer.
I can see in the kernel code that an error is returned if the message
buffer is full when trying to add a message, I just can't see where
to get it in the libmount code.
That's not really a communication protocol problem.
Still I need to work out how to detect it, maybe it is seen by
the code in libmount already and I simply can't see what I need
to do to recognise it ...
So I'm stuck wanting to verify I have got everything that was
sent and am having trouble moving on from that.
Ian
On Wed, Aug 5, 2020 at 1:36 PM Ian Kent [off-list ref] wrote:
I can see in the kernel code that an error is returned if the message
buffer is full when trying to add a message, I just can't see where
to get it in the libmount code.
That's not really a communication protocol problem.
Still I need to work out how to detect it, maybe it is seen by
the code in libmount already and I simply can't see what I need
to do to recognise it ...
So I'm stuck wanting to verify I have got everything that was
sent and am having trouble moving on from that.
This is the commit that should add the overrun detection capability:
e7d553d69cf6 ("pipe: Add notification lossage handling")
Thanks,
Miklos
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:05:32
This is run like:
./watch_test
and watches "/" for changes to the mount topology and the attributes of
individual mount objects.
# mount -t tmpfs none /mnt
# mount -o remount,ro /mnt
# mount -o remount,rw /mnt
producing:
# ./watch_test
read() = 16
NOTIFY[000]: ty=000002 sy=00 i=02000010
MOUNT 00000060 change=0[new_mount] aux=416
read() = 16
NOTIFY[000]: ty=000002 sy=04 i=02010010
MOUNT 000001a0 change=4[setattr] aux=0
read() = 16
NOTIFY[000]: ty=000002 sy=04 i=02010010
MOUNT 000001a0 change=4[setattr] aux=0
Signed-off-by: David Howells <dhowells@redhat.com>
---
samples/watch_queue/watch_test.c | 44 +++++++++++++++++++++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:05:43
Introduce an (effectively) non-repeating system-unique superblock ID that
can be used to determine that two object are in the same superblock without
risking reuse of the ID in the meantime (as is possible with device IDs).
The ID is time-based to make it harder to use it as a covert communications
channel.
In future patches, this ID will be used to tag superblock notification
messages. It will also be made queryable.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/internal.h | 1 +
fs/super.c | 24 ++++++++++++++++++++++++
include/linux/fs.h | 3 +++
3 files changed, 28 insertions(+)
@@ -1548,6 +1548,9 @@ struct super_block {spinlock_ts_inode_wblist_lock;structlist_heads_inodes_wb;/* writeback inodes */++/* Superblock event notifications */+u64s_unique_id;}__randomize_layout;/* Helper functions so that in most cases filesystems will
From: David Howells <dhowells@redhat.com> Date: 2020-03-18 15:05:53
Add a superblock event notification facility whereby notifications about
superblock events, such as I/O errors (EIO), quota limits being hit
(EDQUOT) and running out of space (ENOSPC) can be reported to a monitoring
process asynchronously. Note that this does not cover vfsmount topology
changes. watch_mount() is used for that.
Firstly, a watch queue needs to be created:
pipe2(fds, O_NOTIFICATION_PIPE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
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(fds[1], IOC_WATCH_QUEUE_SET_FILTER, &filter);
watch_sb(AT_FDCWD, "/home/dhowells", 0, fds[1], 0x03);
In this case, it would let me monitor my own homedir for events. After
setting the watch, records will be placed into the queue when, for example,
as superblock switches between read-write and read-only. Records are of
the following format:
struct superblock_notification {
struct watch_notification watch;
__u64 sb_id;
} *n;
Where:
n->watch.type will be WATCH_TYPE_SB_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_SUPERBLOCK_READONLY.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the fifth argument to
watch_sb(), shifted.
n->watch.info & NOTIFY_SUPERBLOCK_IS_NOW_RO 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.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/watch_queue.rst | 12 ++
arch/alpha/kernel/syscalls/syscall.tbl | 1
arch/arm/tools/syscall.tbl | 1
arch/arm64/include/asm/unistd.h | 2
arch/arm64/include/asm/unistd32.h | 2
arch/ia64/kernel/syscalls/syscall.tbl | 1
arch/m68k/kernel/syscalls/syscall.tbl | 1
arch/microblaze/kernel/syscalls/syscall.tbl | 1
arch/mips/kernel/syscalls/syscall_n32.tbl | 1
arch/mips/kernel/syscalls/syscall_n64.tbl | 1
arch/mips/kernel/syscalls/syscall_o32.tbl | 1
arch/parisc/kernel/syscalls/syscall.tbl | 1
arch/powerpc/kernel/syscalls/syscall.tbl | 1
arch/s390/kernel/syscalls/syscall.tbl | 1
arch/sh/kernel/syscalls/syscall.tbl | 1
arch/sparc/kernel/syscalls/syscall.tbl | 1
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
arch/xtensa/kernel/syscalls/syscall.tbl | 1
fs/Kconfig | 12 ++
fs/super.c | 181 +++++++++++++++++++++++++++
include/linux/fs.h | 59 +++++++++
include/linux/syscalls.h | 2
include/uapi/asm-generic/unistd.h | 4 -
include/uapi/linux/watch_queue.h | 31 ++++-
kernel/sys_ni.c | 3
26 files changed, 321 insertions(+), 3 deletions(-)
@@ -10,6 +10,8 @@ opened by userspace. This can be used in conjunction with::* Mount notifications.+* Superblock notifications.+ The notifications buffers can be enabled by: "General setup"/"General notification queue"
@@ -239,6 +241,12 @@ Any particular buffer can be fed from multiple sources. Sources include: Notifications of this type indicate changes to mount attributes and the mount topology within the subtree at the indicated point.+* WATCH_TYPE_SB_NOTIFY++ Notifications of this type indicate changes to superblock attributes and+ configuration and events generated within a superblock such as I/O errors,+ network status changes and out-of-space/out-of-quota errors.+ Event Filtering ===============
@@ -302,6 +310,7 @@ It can then be set to receive notifications:: keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fds[1], 0x01); watch_mount(AT_FDCWD, "/", 0, fds[1], 0x02);+ watch_sb(AT_FDCWD, "/", 0, fds[1], 0x03); The notifications can then be consumed by something like the following::
@@ -341,6 +350,9 @@ The notifications can then be consumed by something like the following:: case WATCH_TYPE_MOUNT_NOTIFY: saw_mount_change(&n.n); break;+ case WATCH_TYPE_SB_NOTIFY:+ saw_sb_event(&n.n);+ break; } p += len;
@@ -478,3 +478,4 @@ 547 common openat2 sys_openat2 548 common pidfd_getfd sys_pidfd_getfd 549 common watch_mount sys_watch_mount+550 common watch_sb sys_watch_sb
@@ -452,3 +452,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common watch_mount sys_watch_mount+440 common watch_sb sys_watch_sb
@@ -359,3 +359,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common watch_mount sys_watch_mount+440 common watch_sb sys_watch_sb
@@ -438,3 +438,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common watch_mount sys_watch_mount+440 common watch_sb sys_watch_sb
@@ -444,3 +444,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common watch_mount sys_watch_mount+440 common watch_sb sys_watch_sb
@@ -436,3 +436,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common watch_mount sys_watch_mount+440 common watch_sb sys_watch_sb
@@ -520,3 +520,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common watch_mount sys_watch_mount+440 common watch_sb sys_watch_sb
@@ -441,3 +441,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common watch_mount sys_watch_mount+440 common watch_sb sys_watch_sb
@@ -484,3 +484,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common watch_mount sys_watch_mount+440 common watch_sb sys_watch_sb
@@ -360,6 +360,7 @@ 437 common openat2 __x64_sys_openat2 438 common pidfd_getfd __x64_sys_pidfd_getfd 439 common watch_mount __x64_sys_watch_mount+440 common watch_sb __x64_sys_watch_sb # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -409,3 +409,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common watch_mount sys_watch_mount+440 common watch_sb sys_watch_sb
@@ -972,6 +978,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?NOTIFY_SUPERBLOCK_IS_NOW_RO:0);/**Somefilesystemsmodifytheirmetadataviasomeotherpaththanthe
@@ -1891,3 +1899,176 @@ void vfs_generate_unique_id(u64 *_id)*_id=id;}++#ifdef CONFIG_SB_NOTIFICATIONS+/*+*Postsuperblocknotifications.+*/+staticvoidpost_sb_notification(structsuper_block*s,structsuperblock_notification*n)+{+post_watch_notification(s->s_watchers,&n->watch,current_cred(),+s->s_unique_id);+}++/*+*Postsimplesuperblocknotification.+*/+void__notify_sb(structsuper_block*s,+enumsuperblock_notification_typesubtype,+u32info)+{+structsuperblock_notificationn={+.watch.type=WATCH_TYPE_SB_NOTIFY,+.watch.subtype=subtype,+.watch.info=watch_sizeof(n)|info,+.sb_id=s->s_unique_id,+};++post_sb_notification(s,&n);+}++/*+*Postsuperblockerrornotification.+*/+void__notify_sb_error(structsuper_block*s,interror)+{+structsuperblock_error_notificationn={+.s.watch.type=WATCH_TYPE_SB_NOTIFY,+.s.watch.subtype=NOTIFY_SUPERBLOCK_ERROR,+.s.watch.info=watch_sizeof(n),+.s.sb_id=s->s_unique_id,+.error_number=error,+.error_cookie=0,+};++post_sb_notification(s,&n.s);+}++/*+*Postsuperblockquotaoverrunnotification.+*/+void__notify_sb_EQDUOT(structsuper_block*s)+{+structsuperblock_notificationn={+.watch.type=WATCH_TYPE_SB_NOTIFY,+.watch.subtype=NOTIFY_SUPERBLOCK_EDQUOT,+.watch.info=watch_sizeof(n),+.sb_id=s->s_unique_id,+};++post_sb_notification(s,&n);+}++staticvoidsb_release_watch(structwatch*watch)+{+put_super(watch->private);+}++/**+*sys_watch_sb-Watchforsuperblockevents.+*@dfd:Basedirectorytopathwalkfromorfdreferringtosuperblock.+*@filename:Pathtosuperblocktoplacethewatchupon+*@at_flags:Pathwalkcontrolflags+*@watch_fd:Thewatchqueuetosendnotificationsto.+*@watch_id:ThewatchIDtobeplacedinthenotification(-1toremovewatch)+*/+SYSCALL_DEFINE5(watch_sb,+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=NULL;+structpathpath;+unsignedintlookup_flags=+LOOKUP_DIRECTORY|LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT;+booldrop_s_count=false;+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;++ret=inode_permission(path.dentry->d_inode,MAY_EXEC);+if(ret)+gotoerr_path;++wqueue=get_watch_queue(watch_fd);+if(IS_ERR(wqueue))+gotoerr_path;++s=path.dentry->d_sb;+if(watch_id>=0){+ret=-ENOMEM;+if(!READ_ONCE(s->s_watchers)){+wlist=kzalloc(sizeof(*wlist),GFP_KERNEL);+if(!wlist)+gotoerr_wqueue;+init_watch_list(wlist,sb_release_watch);+}++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;++ret=security_watch_sb(watch,s);+if(ret<0)+gotoerr_watch;++down_write(&s->s_umount);+ret=-EIO;+if(atomic_read(&s->s_active)){+if(!s->s_watchers){+s->s_watchers=wlist;+wlist=NULL;+}++spin_lock(&sb_lock);+s->s_count++;+spin_unlock(&sb_lock);+ret=add_watch_to_object(watch,s->s_watchers);+if(ret==0)+watch=NULL;/* It worked */+else+drop_s_count=true;+}+up_write(&s->s_umount);+if(drop_s_count)+put_super(s);+}else{+ret=-EBADSLT;+down_write(&s->s_umount);+ret=remove_watch_from_object(s->s_watchers,wqueue,+s->s_unique_id,false);+up_write(&s->s_umount);+}++err_watch:+kfree(watch);+err_wlist:+kfree(wlist);+err_wqueue:+put_watch_queue(wqueue);+err_path:+path_put(&path);+returnret;+}+#endif
@@ -1551,6 +1552,12 @@ struct super_block {/* Superblock event notifications */u64s_unique_id;++#ifdef CONFIG_SB_NOTIFICATIONS+structwatch_list*s_watchers;+#endif+atomic_ts_change_counter;/* Count of config change notifications */+atomic_ts_notify_counter;/* Count of other notifications */}__randomize_layout;/* Helper functions so that in most cases filesystems will
@@ -1005,6 +1005,8 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,asmlinkagelongsys_pidfd_getfd(intpidfd,intfd,unsignedintflags);asmlinkagelongsys_watch_mount(intdfd,constchar__user*path,unsignedintat_flags,intwatch_fd,intwatch_id);+asmlinkagelongsys_watch_sb(intdfd,constchar__user*path,+unsignedintat_flags,intwatch_fd,intwatch_id);/**Architecture-specificsystemcalls