* [RFC PATCH 01/14] uapi: General notification queue definitions [ver #2]
From: David Howells @ 2019-11-07 13:35 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
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
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
new file mode 100644
index 000000000000..5f3d21e8a34b
--- /dev/null
+++ b/include/uapi/linux/watch_queue.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_WATCH_QUEUE_H
+#define _UAPI_LINUX_WATCH_QUEUE_H
+
+#include <linux/types.h>
+
+enum watch_notification_type {
+ WATCH_TYPE_META = 0, /* Special record */
+ WATCH_TYPE__NR = 1
+};
+
+enum watch_meta_notification_subtype {
+ WATCH_META_REMOVAL_NOTIFICATION = 0, /* Watched object was removed */
+ WATCH_META_LOSS_NOTIFICATION = 1, /* Data loss occurred */
+};
+
+/*
+ * Notification record header. This is aligned to 64-bits so that subclasses
+ * can contain __u64 fields.
+ */
+struct watch_notification {
+ __u32 type:24; /* enum watch_notification_type */
+ __u32 subtype:8; /* Type-specific subtype (filterable) */
+ __u32 info;
+#define WATCH_INFO_LENGTH 0x0000007f /* Length of record */
+#define WATCH_INFO_LENGTH__SHIFT 0
+#define WATCH_INFO_ID 0x0000ff00 /* ID of watchpoint */
+#define WATCH_INFO_ID__SHIFT 8
+#define WATCH_INFO_TYPE_INFO 0xffff0000 /* Type-specific info */
+#define WATCH_INFO_TYPE_INFO__SHIFT 16
+#define WATCH_INFO_FLAG_0 0x00010000 /* Type-specific info, flag bit 0 */
+#define WATCH_INFO_FLAG_1 0x00020000 /* ... */
+#define WATCH_INFO_FLAG_2 0x00040000
+#define WATCH_INFO_FLAG_3 0x00080000
+#define WATCH_INFO_FLAG_4 0x00100000
+#define WATCH_INFO_FLAG_5 0x00200000
+#define WATCH_INFO_FLAG_6 0x00400000
+#define WATCH_INFO_FLAG_7 0x00800000
+};
+
+
+/*
+ * Extended watch removal notification. This is used optionally if the type
+ * wants to indicate an identifier for the object being watched, if there is
+ * such. This can be distinguished by the length.
+ *
+ * type -> WATCH_TYPE_META
+ * subtype -> WATCH_META_REMOVAL_NOTIFICATION
+ */
+struct watch_notification_removal {
+ struct watch_notification watch;
+ __u64 id; /* Type-dependent identifier */
+};
+
+#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
^ permalink raw reply related
* [RFC PATCH 02/14] security: Add hooks to rule on setting a watch [ver #2]
From: David Howells @ 2019-11-07 13:35 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
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 <sds@tycho.nsa.gov>
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(+)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index a3763247547c..d62e92b768c4 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1416,6 +1416,18 @@
* @ctx is a pointer in which to place the allocated security context.
* @ctxlen points to the place to put the length of @ctx.
*
+ * Security hooks for the general notification queue:
+ *
+ * @watch_key:
+ * Check to see if a process is allowed to watch for event notifications
+ * from a key or keyring.
+ * @key: The key to watch.
+ *
+ * @watch_devices:
+ * Check to see if a process is allowed to watch for event notifications
+ * from devices (as a global set).
+ *
+ *
* Security hooks for using the eBPF maps and programs functionalities through
* eBPF syscalls.
*
@@ -1698,6 +1710,12 @@ union security_list_options {
int (*inode_notifysecctx)(struct inode *inode, void *ctx, u32 ctxlen);
int (*inode_setsecctx)(struct dentry *dentry, void *ctx, u32 ctxlen);
int (*inode_getsecctx)(struct inode *inode, void **ctx, u32 *ctxlen);
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ int (*watch_key)(struct key *key);
+#endif
+#ifdef CONFIG_DEVICE_NOTIFICATIONS
+ int (*watch_devices)(void);
+#endif
#ifdef CONFIG_SECURITY_NETWORK
int (*unix_stream_connect)(struct sock *sock, struct sock *other,
@@ -1977,6 +1995,12 @@ struct security_hook_heads {
struct hlist_head inode_notifysecctx;
struct hlist_head inode_setsecctx;
struct hlist_head inode_getsecctx;
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ struct hlist_head watch_key;
+#endif
+#ifdef CONFIG_DEVICE_NOTIFICATIONS
+ struct hlist_head watch_devices;
+#endif
#ifdef CONFIG_SECURITY_NETWORK
struct hlist_head unix_stream_connect;
struct hlist_head unix_may_send;
diff --git a/include/linux/security.h b/include/linux/security.h
index a8d59d612d27..5d1867772022 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -1271,6 +1271,23 @@ static inline int security_locked_down(enum lockdown_reason what)
}
#endif /* CONFIG_SECURITY */
+#if defined(CONFIG_SECURITY) && defined(CONFIG_KEY_NOTIFICATIONS)
+int security_watch_key(struct key *key);
+#else
+static inline int security_watch_key(struct key *key)
+{
+ return 0;
+}
+#endif
+#if defined(CONFIG_SECURITY) && defined(CONFIG_DEVICE_NOTIFICATIONS)
+int security_watch_devices(void);
+#else
+static inline int security_watch_devices(void)
+{
+ return 0;
+}
+#endif
+
#ifdef CONFIG_SECURITY_NETWORK
int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk);
diff --git a/security/security.c b/security/security.c
index 1bc000f834e2..4f970716fa6c 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1956,6 +1956,20 @@ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen)
}
EXPORT_SYMBOL(security_inode_getsecctx);
+#ifdef CONFIG_KEY_NOTIFICATIONS
+int security_watch_key(struct key *key)
+{
+ return call_int_hook(watch_key, 0, key);
+}
+#endif
+
+#ifdef CONFIG_DEVICE_NOTIFICATIONS
+int security_watch_devices(void)
+{
+ return call_int_hook(watch_devices, 0);
+}
+#endif
+
#ifdef CONFIG_SECURITY_NETWORK
int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk)
^ permalink raw reply related
* [RFC PATCH 03/14] security: Add a hook for the point of notification insertion [ver #2]
From: David Howells @ 2019-11-07 13:35 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
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 <sds@tycho.nsa.gov>
cc: linux-security-module@vger.kernel.org
---
include/linux/lsm_hooks.h | 14 ++++++++++++++
include/linux/security.h | 15 ++++++++++++++-
security/security.c | 9 +++++++++
3 files changed, 37 insertions(+), 1 deletion(-)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index d62e92b768c4..8e1cbc27fc62 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1427,6 +1427,12 @@
* Check to see if a process is allowed to watch for event notifications
* from devices (as a global set).
*
+ * @post_notification:
+ * Check to see if a watch notification can be posted to a particular
+ * queue.
+ * @w_cred: The credentials of the whoever set the watch.
+ * @cred: The event-triggerer's credentials
+ * @n: The notification being posted
*
* Security hooks for using the eBPF maps and programs functionalities through
* eBPF syscalls.
@@ -1716,6 +1722,11 @@ union security_list_options {
#ifdef CONFIG_DEVICE_NOTIFICATIONS
int (*watch_devices)(void);
#endif
+#ifdef CONFIG_WATCH_QUEUE
+ int (*post_notification)(const struct cred *w_cred,
+ const struct cred *cred,
+ struct watch_notification *n);
+#endif
#ifdef CONFIG_SECURITY_NETWORK
int (*unix_stream_connect)(struct sock *sock, struct sock *other,
@@ -2001,6 +2012,9 @@ struct security_hook_heads {
#ifdef CONFIG_DEVICE_NOTIFICATIONS
struct hlist_head watch_devices;
#endif
+#ifdef CONFIG_WATCH_QUEUE
+ struct hlist_head post_notification;
+#endif /* CONFIG_WATCH_QUEUE */
#ifdef CONFIG_SECURITY_NETWORK
struct hlist_head unix_stream_connect;
struct hlist_head unix_may_send;
diff --git a/include/linux/security.h b/include/linux/security.h
index 5d1867772022..ee98e020c749 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -57,6 +57,8 @@ struct mm_struct;
struct fs_context;
struct fs_parameter;
enum fs_value_type;
+struct watch;
+struct watch_notification;
/* Default (no) options for the capable function */
#define CAP_OPT_NONE 0x0
@@ -1287,6 +1289,18 @@ static inline int security_watch_devices(void)
return 0;
}
#endif
+#if defined(CONFIG_SECURITY) && defined(CONFIG_WATCH_QUEUE)
+int security_post_notification(const struct cred *w_cred,
+ const struct cred *cred,
+ struct watch_notification *n);
+#else
+static inline int security_post_notification(const struct cred *w_cred,
+ const struct cred *cred,
+ struct watch_notification *n)
+{
+ return 0;
+}
+#endif
#ifdef CONFIG_SECURITY_NETWORK
@@ -1912,4 +1926,3 @@ static inline void security_bpf_prog_free(struct bpf_prog_aux *aux)
#endif /* CONFIG_BPF_SYSCALL */
#endif /* ! __LINUX_SECURITY_H */
-
diff --git a/security/security.c b/security/security.c
index 4f970716fa6c..c9d5c3d5472a 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1956,6 +1956,15 @@ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen)
}
EXPORT_SYMBOL(security_inode_getsecctx);
+#ifdef CONFIG_WATCH_QUEUE
+int security_post_notification(const struct cred *w_cred,
+ const struct cred *cred,
+ struct watch_notification *n)
+{
+ return call_int_hook(post_notification, 0, w_cred, cred, n);
+}
+#endif /* CONFIG_WATCH_QUEUE */
+
#ifdef CONFIG_KEY_NOTIFICATIONS
int security_watch_key(struct key *key)
{
^ permalink raw reply related
* [RFC PATCH 04/14] pipe: Add O_NOTIFICATION_PIPE [ver #2]
From: David Howells @ 2019-11-07 13:35 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
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(+)
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 5f3d21e8a34b..9df72227f515 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -3,6 +3,9 @@
#define _UAPI_LINUX_WATCH_QUEUE_H
#include <linux/types.h>
+#include <linux/fcntl.h>
+
+#define O_NOTIFICATION_PIPE O_EXCL /* Parameter to pipe2() selecting notification pipe */
enum watch_notification_type {
WATCH_TYPE_META = 0, /* Special record */
^ permalink raw reply related
* [RFC PATCH 05/14] pipe: Add general notification queue support [ver #2]
From: David Howells @ 2019-11-07 13:36 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
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/ioctl/ioctl-number.rst | 1
Documentation/watch_queue.rst | 354 ++++++++++++++++++
fs/pipe.c | 193 ++++++----
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 | 656 ++++++++++++++++++++++++++++++++++
10 files changed, 1318 insertions(+), 77 deletions(-)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 include/linux/watch_queue.h
create mode 100644 kernel/watch_queue.c
diff --git a/Documentation/ioctl/ioctl-number.rst b/Documentation/ioctl/ioctl-number.rst
index bef79cd4c6b4..303fe17d871e 100644
--- a/Documentation/ioctl/ioctl-number.rst
+++ b/Documentation/ioctl/ioctl-number.rst
@@ -202,6 +202,7 @@ Code Seq# Include File Comments
'W' 00-1F linux/wanrouter.h conflict! (pre 3.9)
'W' 00-3F sound/asound.h conflict!
'W' 40-5F drivers/pci/switch/switchtec.c
+'W' 60-61 linux/watch_queue.h
'X' all fs/xfs/xfs_fs.h, conflict!
fs/xfs/linux-2.6/xfs_ioctl32.h,
include/linux/falloc.h,
diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
new file mode 100644
index 000000000000..d8f70282d247
--- /dev/null
+++ b/Documentation/watch_queue.rst
@@ -0,0 +1,354 @@
+==============================
+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
+
+ * General device event 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 watch_queue 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.
+
+ * WATCH_TYPE_BLOCK_NOTIFY
+
+ Notifications of this type indicate block layer events, such as I/O errors
+ or temporary link loss. Watches of this type are set on a global queue.
+
+
+Event Filtering
+===============
+
+Once a watch queue has been created, a set of filters can be applied to limit
+the events that are received using::
+
+ struct watch_notification_filter filter = {
+ ...
+ };
+ ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter)
+
+The filter description is a variable of type::
+
+ struct watch_notification_filter {
+ __u32 nr_filters;
+ __u32 __reserved;
+ struct watch_notification_type_filter filters[];
+ };
+
+Where "nr_filters" is the number of filters in filters[] and "__reserved"
+should be 0. The "filters" array has elements of the following type::
+
+ struct watch_notification_type_filter {
+ __u32 type;
+ __u32 info_filter;
+ __u32 info_mask;
+ __u32 subtype_filter[8];
+ };
+
+Where:
+
+ * ``type`` is the event type to filter for and should be something like
+ "WATCH_TYPE_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 and device event
+notifications::
+
+ keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fds[1], 0x01);
+ watch_devices(fds[1], 0x2);
+
+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;
+ case WATCH_TYPE_BLOCK_NOTIFY:
+ saw_block_event(&n.n);
+ break;
+ case WATCH_TYPE_USB_NOTIFY:
+ saw_usb_event(&n.n);
+ break;
+ }
+
+ p += len;
+ }
+ }
+ }
diff --git a/fs/pipe.c b/fs/pipe.c
index 9cd5cbef9552..099bf4b657dd 100644
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -24,6 +24,7 @@
#include <linux/syscalls.h>
#include <linux/fcntl.h>
#include <linux/memcontrol.h>
+#include <linux/watch_queue.h>
#include <linux/uaccess.h>
#include <asm/ioctls.h>
@@ -407,6 +408,11 @@ pipe_write(struct kiocb *iocb, struct iov_iter *from)
goto out;
}
+ if (pipe->watch_queue) {
+ ret = -EXDEV;
+ goto out;
+ }
+
head = pipe->head;
max_usage = pipe->max_usage;
mask = pipe->ring_size - 1;
@@ -546,25 +552,37 @@ pipe_write(struct kiocb *iocb, struct iov_iter *from)
static long pipe_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct pipe_inode_info *pipe = filp->private_data;
- int count, head, tail, mask;
+ int count, head, tail, mask, ret;
switch (cmd) {
- case FIONREAD:
- __pipe_lock(pipe);
- count = 0;
- head = pipe->head;
- tail = pipe->tail;
- mask = pipe->ring_size - 1;
+ case FIONREAD:
+ __pipe_lock(pipe);
+ count = 0;
+ head = pipe->head;
+ tail = pipe->tail;
+ mask = pipe->ring_size - 1;
- while (tail != head) {
- count += pipe->bufs[tail & mask].len;
- tail++;
- }
- __pipe_unlock(pipe);
+ while (tail != head) {
+ count += pipe->bufs[tail & mask].len;
+ tail++;
+ }
+ __pipe_unlock(pipe);
+
+ return put_user(count, (int __user *)arg);
- return put_user(count, (int __user *)arg);
- default:
- return -ENOIOCTLCMD;
+ case IOC_WATCH_QUEUE_SET_SIZE:
+ __pipe_lock(pipe);
+ ret = watch_queue_set_size(pipe, arg);
+ __pipe_unlock(pipe);
+ return ret;
+
+ case IOC_WATCH_QUEUE_SET_FILTER:
+ ret = watch_queue_set_filter(
+ pipe, (struct watch_notification_filter __user *)arg);
+ return ret;
+
+ default:
+ return -ENOIOCTLCMD;
}
}
@@ -660,27 +678,27 @@ pipe_fasync(int fd, struct file *filp, int on)
return retval;
}
-static unsigned long account_pipe_buffers(struct user_struct *user,
- unsigned long old, unsigned long new)
+unsigned long account_pipe_buffers(struct user_struct *user,
+ unsigned long old, unsigned long new)
{
return atomic_long_add_return(new - old, &user->pipe_bufs);
}
-static bool too_many_pipe_buffers_soft(unsigned long user_bufs)
+bool too_many_pipe_buffers_soft(unsigned long user_bufs)
{
unsigned long soft_limit = READ_ONCE(pipe_user_pages_soft);
return soft_limit && user_bufs > soft_limit;
}
-static bool too_many_pipe_buffers_hard(unsigned long user_bufs)
+bool too_many_pipe_buffers_hard(unsigned long user_bufs)
{
unsigned long hard_limit = READ_ONCE(pipe_user_pages_hard);
return hard_limit && user_bufs > hard_limit;
}
-static bool is_unprivileged_user(void)
+bool pipe_is_unprivileged_user(void)
{
return !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN);
}
@@ -702,12 +720,12 @@ struct pipe_inode_info *alloc_pipe_info(void)
user_bufs = account_pipe_buffers(user, 0, pipe_bufs);
- if (too_many_pipe_buffers_soft(user_bufs) && is_unprivileged_user()) {
+ if (too_many_pipe_buffers_soft(user_bufs) && pipe_is_unprivileged_user()) {
user_bufs = account_pipe_buffers(user, pipe_bufs, 1);
pipe_bufs = 1;
}
- if (too_many_pipe_buffers_hard(user_bufs) && is_unprivileged_user())
+ if (too_many_pipe_buffers_hard(user_bufs) && pipe_is_unprivileged_user())
goto out_revert_acct;
pipe->bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
@@ -718,6 +736,7 @@ struct pipe_inode_info *alloc_pipe_info(void)
pipe->r_counter = pipe->w_counter = 1;
pipe->max_usage = pipe_bufs;
pipe->ring_size = pipe_bufs;
+ pipe->nr_accounted = pipe_bufs;
pipe->user = user;
mutex_init(&pipe->mutex);
return pipe;
@@ -735,7 +754,12 @@ void free_pipe_info(struct pipe_inode_info *pipe)
{
int i;
- (void) account_pipe_buffers(pipe->user, pipe->ring_size, 0);
+ if (pipe->watch_queue) {
+ watch_queue_clear(pipe->watch_queue);
+ put_watch_queue(pipe->watch_queue);
+ }
+
+ (void) account_pipe_buffers(pipe->user, pipe->nr_accounted, 0);
free_uid(pipe->user);
for (i = 0; i < pipe->ring_size; i++) {
struct pipe_buffer *buf = pipe->bufs + i;
@@ -811,6 +835,13 @@ int create_pipe_files(struct file **res, int flags)
if (!inode)
return -ENFILE;
+ if (flags & O_NOTIFICATION_PIPE) {
+ if (watch_queue_init(inode->i_pipe) < 0) {
+ iput(inode);
+ return -ENOMEM;
+ }
+ }
+
f = alloc_file_pseudo(inode, pipe_mnt, "",
O_WRONLY | (flags & (O_NONBLOCK | O_DIRECT)),
&pipefifo_fops);
@@ -839,7 +870,7 @@ static int __do_pipe_flags(int *fd, struct file **files, int flags)
int error;
int fdw, 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);
@@ -1086,42 +1117,12 @@ unsigned int round_pipe_size(unsigned long size)
}
/*
- * Allocate a new array of pipe buffers and copy the info over. Returns the
- * pipe size if successful, or return -ERROR on error.
+ * Resize the pipe ring to a number of slots.
*/
-static long pipe_set_size(struct pipe_inode_info *pipe, unsigned long arg)
+int pipe_resize_ring(struct pipe_inode_info *pipe, unsigned int nr_slots)
{
struct pipe_buffer *bufs;
- unsigned int size, nr_slots, head, tail, mask, n;
- unsigned long user_bufs;
- long ret = 0;
-
- size = round_pipe_size(arg);
- nr_slots = size >> PAGE_SHIFT;
-
- if (!nr_slots)
- return -EINVAL;
-
- /*
- * If trying to increase the pipe capacity, check that an
- * unprivileged user is not trying to exceed various limits
- * (soft limit check here, hard limit check just below).
- * Decreasing the pipe capacity is always permitted, even
- * if the user is currently over a limit.
- */
- 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;
- goto out_revert_acct;
- }
+ unsigned int head, tail, mask, n;
/*
* We can shrink the pipe, if arg is greater than the ring occupancy.
@@ -1133,17 +1134,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;
- goto out_revert_acct;
- }
+ if (nr_slots < n)
+ return -EBUSY;
bufs = kcalloc(nr_slots, sizeof(*bufs),
GFP_KERNEL_ACCOUNT | __GFP_NOWARN);
- if (unlikely(!bufs)) {
- ret = -ENOMEM;
- goto out_revert_acct;
- }
+ if (unlikely(!bufs))
+ return -ENOMEM;
/*
* The pipe array wraps around, so just start the new one at zero
@@ -1171,13 +1168,63 @@ 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;
+ return 0;
+}
+
+/*
+ * Allocate a new array of pipe buffers and copy the info over. Returns the
+ * pipe size if successful, or return -ERROR on error.
+ */
+long pipe_set_size(struct pipe_inode_info *pipe, unsigned long arg)
+{
+ unsigned long user_bufs;
+ unsigned int nr_slots, size;
+ long ret = 0;
+
+ if (pipe->watch_queue)
+ return -EBUSY;
+
+ size = round_pipe_size(arg);
+ nr_slots = size >> PAGE_SHIFT;
+
+ if (!nr_slots)
+ return -EINVAL;
+
+ /*
+ * If trying to increase the pipe capacity, check that an
+ * unprivileged user is not trying to exceed various limits
+ * (soft limit check here, hard limit check just below).
+ * Decreasing the pipe capacity is always permitted, even
+ * if the user is currently over a limit.
+ */
+ 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;
+ goto out_revert_acct;
+ }
+
+ ret = pipe_resize_ring(pipe, nr_slots);
+ if (ret < 0)
+ goto out_revert_acct;
+
+ pipe->max_usage = nr_slots;
+ pipe->nr_accounted = nr_slots;
return pipe->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);
return ret;
}
@@ -1186,9 +1233,15 @@ static long pipe_set_size(struct pipe_inode_info *pipe, unsigned long arg)
* location, so checking ->i_pipe is not enough to verify that this is a
* pipe.
*/
-struct pipe_inode_info *get_pipe_info(struct file *file)
+struct pipe_inode_info *get_pipe_info(struct file *file, bool for_splice)
{
- return file->f_op == &pipefifo_fops ? file->private_data : NULL;
+ struct pipe_inode_info *pipe = file->private_data;
+
+ if (file->f_op != &pipefifo_fops || !pipe)
+ return NULL;
+ if (for_splice && pipe->watch_queue)
+ return NULL;
+ return pipe;
}
long pipe_fcntl(struct file *file, unsigned int cmd, unsigned long arg)
@@ -1196,7 +1249,7 @@ long pipe_fcntl(struct file *file, unsigned int cmd, unsigned long arg)
struct pipe_inode_info *pipe;
long ret;
- pipe = get_pipe_info(file);
+ pipe = get_pipe_info(file, false);
if (!pipe)
return -EBADF;
diff --git a/fs/splice.c b/fs/splice.c
index c521090a0469..01eb34e0dfac 100644
--- a/fs/splice.c
+++ b/fs/splice.c
@@ -1118,8 +1118,8 @@ static long do_splice(struct file *in, loff_t __user *off_in,
loff_t offset;
long ret;
- ipipe = get_pipe_info(in);
- opipe = get_pipe_info(out);
+ ipipe = get_pipe_info(in, true);
+ opipe = get_pipe_info(out, true);
if (ipipe && opipe) {
if (off_in || off_out)
@@ -1271,7 +1271,7 @@ static int pipe_to_user(struct pipe_inode_info *pipe, struct pipe_buffer *buf,
static long vmsplice_to_user(struct file *file, struct iov_iter *iter,
unsigned int flags)
{
- struct pipe_inode_info *pipe = get_pipe_info(file);
+ struct pipe_inode_info *pipe = get_pipe_info(file, true);
struct splice_desc sd = {
.total_len = iov_iter_count(iter),
.flags = flags,
@@ -1306,7 +1306,7 @@ static long vmsplice_to_pipe(struct file *file, struct iov_iter *iter,
if (flags & SPLICE_F_GIFT)
buf_flag = PIPE_BUF_FLAG_GIFT;
- pipe = get_pipe_info(file);
+ pipe = get_pipe_info(file, true);
if (!pipe)
return -EBADF;
@@ -1770,8 +1770,8 @@ static int link_pipe(struct pipe_inode_info *ipipe,
static long do_tee(struct file *in, struct file *out, size_t len,
unsigned int flags)
{
- struct pipe_inode_info *ipipe = get_pipe_info(in);
- struct pipe_inode_info *opipe = get_pipe_info(out);
+ struct pipe_inode_info *ipipe = get_pipe_info(in, true);
+ struct pipe_inode_info *opipe = get_pipe_info(out, true);
int ret = -EINVAL;
/*
diff --git a/include/linux/pipe_fs_i.h b/include/linux/pipe_fs_i.h
index 44f2245debda..816bc0d3aecd 100644
--- a/include/linux/pipe_fs_i.h
+++ b/include/linux/pipe_fs_i.h
@@ -34,6 +34,7 @@ struct pipe_buffer {
* @tail: The point of buffer consumption
* @max_usage: The maximum number of slots that may be used in the ring
* @ring_size: total number of buffers (should be a power of 2)
+ * @nr_accounted: The amount this pipe accounts for in user->pipe_bufs
* @tmp_page: cached released page
* @readers: number of current readers of this pipe
* @writers: number of current writers of this pipe
@@ -45,6 +46,7 @@ struct pipe_buffer {
* @fasync_writers: writer side fasync
* @bufs: the circular array of pipe buffers
* @user: the user who created this pipe
+ * @watch_queue: If this pipe is a watch_queue, this is the stuff for that
**/
struct pipe_inode_info {
struct mutex mutex;
@@ -53,6 +55,7 @@ struct pipe_inode_info {
unsigned int tail;
unsigned int max_usage;
unsigned int ring_size;
+ unsigned int nr_accounted;
unsigned int readers;
unsigned int writers;
unsigned int files;
@@ -64,6 +67,9 @@ struct pipe_inode_info {
struct fasync_struct *fasync_writers;
struct pipe_buffer *bufs;
struct user_struct *user;
+#ifdef CONFIG_WATCH_QUEUE
+ struct watch_queue *watch_queue;
+#endif
};
/*
@@ -238,9 +244,20 @@ void pipe_buf_mark_unmergeable(struct pipe_buffer *buf);
extern const struct pipe_buf_operations nosteal_pipe_buf_ops;
+#ifdef CONFIG_WATCH_QUEUE
+unsigned long account_pipe_buffers(struct user_struct *user,
+ unsigned long old, unsigned long new);
+bool too_many_pipe_buffers_soft(unsigned long user_bufs);
+bool too_many_pipe_buffers_hard(unsigned long user_bufs);
+bool pipe_is_unprivileged_user(void);
+#endif
+
/* for F_SETPIPE_SZ and F_GETPIPE_SZ */
+#ifdef CONFIG_WATCH_QUEUE
+int pipe_resize_ring(struct pipe_inode_info *pipe, unsigned int nr_slots);
+#endif
long pipe_fcntl(struct file *, unsigned int, unsigned long arg);
-struct pipe_inode_info *get_pipe_info(struct file *file);
+struct pipe_inode_info *get_pipe_info(struct file *file, bool for_splice);
int create_pipe_files(struct file **, int);
unsigned int round_pipe_size(unsigned long size);
diff --git a/include/linux/watch_queue.h b/include/linux/watch_queue.h
new file mode 100644
index 000000000000..b705db0a2868
--- /dev/null
+++ b/include/linux/watch_queue.h
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+/* User-mappable watch queue
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * See Documentation/watch_queue.rst
+ */
+
+#ifndef _LINUX_WATCH_QUEUE_H
+#define _LINUX_WATCH_QUEUE_H
+
+#include <uapi/linux/watch_queue.h>
+#include <linux/kref.h>
+#include <linux/rcupdate.h>
+
+#ifdef CONFIG_WATCH_QUEUE
+
+struct cred;
+
+struct watch_type_filter {
+ enum watch_notification_type type;
+ __u32 subtype_filter[1]; /* Bitmask of subtypes to filter on */
+ __u32 info_filter; /* Filter on watch_notification::info */
+ __u32 info_mask; /* Mask of relevant bits in info_filter */
+};
+
+struct watch_filter {
+ union {
+ struct rcu_head rcu;
+ unsigned long type_filter[2]; /* Bitmask of accepted types */
+ };
+ u32 nr_filters; /* Number of filters */
+ struct watch_type_filter filters[];
+};
+
+struct watch_queue {
+ struct rcu_head rcu;
+ struct watch_filter __rcu *filter;
+ struct pipe_inode_info *pipe; /* The pipe we're using as a buffer */
+ struct hlist_head watches; /* Contributory watches */
+ struct page **notes; /* Preallocated notifications */
+ unsigned long *notes_bitmap; /* Allocation bitmap for notes */
+ struct kref usage; /* Object usage count */
+ spinlock_t lock;
+ unsigned int nr_notes; /* Number of notes */
+ unsigned int nr_pages; /* Number of pages in notes[] */
+ bool defunct; /* T when queues closed */
+};
+
+/*
+ * Representation of a watch on an object.
+ */
+struct watch {
+ union {
+ struct rcu_head rcu;
+ u32 info_id; /* ID to be OR'd in to info field */
+ };
+ struct watch_queue __rcu *queue; /* Queue to post events to */
+ struct hlist_node queue_node; /* Link in queue->watches */
+ struct watch_list __rcu *watch_list;
+ struct hlist_node list_node; /* Link in watch_list->watchers */
+ const struct cred *cred; /* Creds of the owner of the watch */
+ void *private; /* Private data for the watched object */
+ u64 id; /* Internal identifier */
+ struct kref usage; /* Object usage count */
+};
+
+/*
+ * List of watches on an object.
+ */
+struct watch_list {
+ struct rcu_head rcu;
+ struct hlist_head watchers;
+ void (*release_watch)(struct watch *);
+ spinlock_t lock;
+};
+
+extern void __post_watch_notification(struct watch_list *,
+ struct watch_notification *,
+ const struct cred *,
+ u64);
+extern struct watch_queue *get_watch_queue(int);
+extern void put_watch_queue(struct watch_queue *);
+extern void init_watch(struct watch *, struct watch_queue *);
+extern int add_watch_to_object(struct watch *, struct watch_list *);
+extern int remove_watch_from_object(struct watch_list *, struct watch_queue *, u64, bool);
+extern long watch_queue_set_size(struct pipe_inode_info *, unsigned int);
+extern long watch_queue_set_filter(struct pipe_inode_info *,
+ struct watch_notification_filter __user *);
+extern int watch_queue_init(struct pipe_inode_info *);
+extern void watch_queue_clear(struct watch_queue *);
+
+static inline void init_watch_list(struct watch_list *wlist,
+ void (*release_watch)(struct watch *))
+{
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+ wlist->release_watch = release_watch;
+}
+
+static inline void post_watch_notification(struct watch_list *wlist,
+ struct watch_notification *n,
+ const struct cred *cred,
+ u64 id)
+{
+ if (unlikely(wlist))
+ __post_watch_notification(wlist, n, cred, id);
+}
+
+static inline void remove_watch_list(struct watch_list *wlist, u64 id)
+{
+ if (wlist) {
+ remove_watch_from_object(wlist, NULL, id, true);
+ kfree_rcu(wlist, rcu);
+ }
+}
+
+/**
+ * watch_sizeof - Calculate the information part of the size of a watch record,
+ * given the structure size.
+ */
+#define watch_sizeof(STRUCT) (sizeof(STRUCT) << WATCH_INFO_LENGTH__SHIFT)
+
+#endif
+
+#endif /* _LINUX_WATCH_QUEUE_H */
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 9df72227f515..3a5790f1f05d 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/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 */
+
enum watch_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
};
+/*
+ * Notification filtering rules (IOC_WATCH_QUEUE_SET_FILTER).
+ */
+struct watch_notification_type_filter {
+ __u32 type; /* Type to apply filter to */
+ __u32 info_filter; /* Filter on watch_notification::info */
+ __u32 info_mask; /* Mask of relevant bits in info_filter */
+ __u32 subtype_filter[8]; /* Bitmask of subtypes to filter on */
+};
+
+struct watch_notification_filter {
+ __u32 nr_filters; /* Number of filters */
+ __u32 __reserved; /* Must be 0 */
+ struct watch_notification_type_filter filters[];
+};
+
/*
* Extended watch removal notification. This is used optionally if the type
diff --git a/init/Kconfig b/init/Kconfig
index b4daad2bac23..eb177b78f560 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -358,6 +358,18 @@ config POSIX_MQUEUE_SYSCTL
depends on SYSCTL
default y
+config WATCH_QUEUE
+ bool "General notification queue"
+ default n
+ help
+
+ This is a general notification queue for the kernel to pass events to
+ userspace by splicing them into pipes. It can be used in conjunction
+ with watches for key/keyring change notifications and device
+ notifications.
+
+ See Documentation/watch_queue.rst
+
config CROSS_MEMORY_ATTACH
bool "Enable process_vm_readv/writev syscalls"
depends on MMU
diff --git a/kernel/Makefile b/kernel/Makefile
index daad787fb795..067975dd2e0c 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -114,6 +114,7 @@ obj-$(CONFIG_TORTURE_TEST) += torture.o
obj-$(CONFIG_HAS_IOMEM) += iomem.o
obj-$(CONFIG_RSEQ) += rseq.o
+obj-$(CONFIG_WATCH_QUEUE) += watch_queue.o
obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak.o
KASAN_SANITIZE_stackleak.o := n
diff --git a/kernel/watch_queue.c b/kernel/watch_queue.c
new file mode 100644
index 000000000000..b73fbfe0d467
--- /dev/null
+++ b/kernel/watch_queue.c
@@ -0,0 +1,656 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Watch queue and general notification mechanism, built on pipes
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * See Documentation/watch_queue.rst
+ */
+
+#define pr_fmt(fmt) "watchq: " fmt
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/printk.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/file.h>
+#include <linux/security.h>
+#include <linux/cred.h>
+#include <linux/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)
+
+static void watch_queue_pipe_buf_release(struct pipe_inode_info *pipe,
+ struct pipe_buffer *buf)
+{
+ struct watch_queue *wqueue = (struct watch_queue *)buf->private;
+ struct page *page;
+ unsigned int bit;
+
+ /* We need to work out which note within the page this refers to, but
+ * the note might have been maximum size, so merely ANDing the offset
+ * off doesn't work. OTOH, the note must've been more than zero size.
+ */
+ 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);
+}
+
+static int watch_queue_pipe_buf_steal(struct pipe_inode_info *pipe,
+ struct pipe_buffer *buf)
+{
+ return -1; /* No. */
+}
+
+/* New data written to a pipe may be appended to a buffer with this type. */
+static const struct pipe_buf_operations watch_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,
+};
+
+/*
+ * Post a notification to a watch queue.
+ */
+static bool post_one_notification(struct watch_queue *wqueue,
+ struct watch_notification *n)
+{
+ void *p;
+ struct pipe_inode_info *pipe = wqueue->pipe;
+ struct pipe_buffer *buf;
+ struct page *page;
+ unsigned int head, tail, mask, note, offset, len;
+ bool done = false;
+
+ if (!pipe)
+ return false;
+
+ spin_lock_irq(&pipe->wait.lock);
+
+ if (wqueue->defunct)
+ goto out;
+
+ mask = pipe->ring_size - 1;
+ head = pipe->head;
+ tail = pipe->tail;
+ if (pipe_full(head, tail, pipe->ring_size))
+ goto lost;
+
+ note = find_first_bit(wqueue->notes_bitmap, wqueue->nr_notes);
+ if (note >= wqueue->nr_notes)
+ goto lost;
+
+ 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 = (unsigned long)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->wait.lock);
+ BUG();
+ }
+ wake_up_interruptible_sync_poll_locked(&pipe->wait, EPOLLIN | EPOLLRDNORM);
+ done = true;
+
+out:
+ spin_unlock_irq(&pipe->wait.lock);
+ if (done)
+ kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN);
+ return done;
+
+lost:
+ goto out;
+}
+
+/*
+ * Apply filter rules to a notification.
+ */
+static bool filter_watch_notification(const struct watch_filter *wf,
+ const struct watch_notification *n)
+{
+ const struct watch_type_filter *wt;
+ unsigned int st_bits = sizeof(wt->subtype_filter[0]) * 8;
+ unsigned int st_index = n->subtype / st_bits;
+ unsigned int st_bit = 1U << (n->subtype % st_bits);
+ int i;
+
+ if (!test_bit(n->type, wf->type_filter))
+ return false;
+
+ for (i = 0; i < wf->nr_filters; i++) {
+ wt = &wf->filters[i];
+ if (n->type == wt->type &&
+ (wt->subtype_filter[st_index] & st_bit) &&
+ (n->info & wt->info_mask) == wt->info_filter)
+ return true;
+ }
+
+ return false; /* If there is a filter, the default is to reject. */
+}
+
+/**
+ * __post_watch_notification - Post an event notification
+ * @wlist: The watch list to post the event to.
+ * @n: The notification record to post.
+ * @cred: The creds of the process that triggered the notification.
+ * @id: The ID to match on the watch.
+ *
+ * Post a notification of an event into a set of watch queues and let the users
+ * know.
+ *
+ * The size of the notification should be set in n->info & WATCH_INFO_LENGTH and
+ * should be in units of sizeof(*n).
+ */
+void __post_watch_notification(struct watch_list *wlist,
+ struct watch_notification *n,
+ const struct cred *cred,
+ u64 id)
+{
+ const struct watch_filter *wf;
+ struct watch_queue *wqueue;
+ struct watch *watch;
+
+ 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);
+
+/*
+ * Allocate sufficient pages to preallocation for the requested number of
+ * notifications.
+ */
+long watch_queue_set_size(struct pipe_inode_info *pipe, unsigned int nr_notes)
+{
+ struct watch_queue *wqueue = pipe->watch_queue;
+ struct page **pages;
+ unsigned long *bitmap;
+ unsigned long user_bufs;
+ unsigned int bmsize;
+ int ret, 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;
+ goto error;
+ }
+
+ ret = pipe_resize_ring(pipe, nr_notes);
+ if (ret < 0)
+ goto error;
+
+ pages = kcalloc(sizeof(struct page *), nr_pages, GFP_KERNEL);
+ if (!pages)
+ goto error;
+
+ for (i = 0; i < nr_pages; i++) {
+ pages[i] = alloc_page(GFP_KERNEL);
+ if (!pages[i])
+ goto error_p;
+ pages[i]->index = i * WATCH_QUEUE_NOTES_PER_PAGE;
+ }
+
+ bmsize = (nr_notes + BITS_PER_LONG - 1) / BITS_PER_LONG;
+ bmsize *= sizeof(unsigned long);
+ bitmap = kmalloc(bmsize, GFP_KERNEL);
+ if (!bitmap)
+ goto error_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;
+ return 0;
+
+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);
+ return ret;
+}
+
+/*
+ * Set the filter on a watch queue.
+ */
+long watch_queue_set_filter(struct pipe_inode_info *pipe,
+ struct watch_notification_filter __user *_filter)
+{
+ struct watch_notification_type_filter *tf;
+ struct watch_notification_filter filter;
+ struct watch_type_filter *q;
+ struct watch_filter *wfilter;
+ struct watch_queue *wqueue = pipe->watch_queue;
+ int ret, nr_filter = 0, i;
+
+ if (!wqueue)
+ return -ENODEV;
+
+ if (!_filter) {
+ /* Remove the old filter */
+ wfilter = NULL;
+ goto set;
+ }
+
+ /* Grab the user's filter specification */
+ if (copy_from_user(&filter, _filter, sizeof(filter)) != 0)
+ return -EFAULT;
+ if (filter.nr_filters == 0 ||
+ filter.nr_filters > 16 ||
+ filter.__reserved != 0)
+ return -EINVAL;
+
+ tf = memdup_user(_filter->filters, filter.nr_filters * sizeof(*tf));
+ if (IS_ERR(tf))
+ return PTR_ERR(tf);
+
+ ret = -EINVAL;
+ for (i = 0; i < filter.nr_filters; i++) {
+ if ((tf[i].info_filter & ~tf[i].info_mask) ||
+ tf[i].info_mask & WATCH_INFO_LENGTH)
+ goto err_filter;
+ /* Ignore any unknown types */
+ if (tf[i].type >= sizeof(wfilter->type_filter) * 8)
+ continue;
+ nr_filter++;
+ }
+
+ /* Now we need to build the internal filter from only the relevant
+ * user-specified filters.
+ */
+ ret = -ENOMEM;
+ wfilter = kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL);
+ if (!wfilter)
+ goto err_filter;
+ wfilter->nr_filters = nr_filter;
+
+ q = wfilter->filters;
+ for (i = 0; i < filter.nr_filters; i++) {
+ if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG)
+ continue;
+
+ q->type = tf[i].type;
+ q->info_filter = tf[i].info_filter;
+ q->info_mask = tf[i].info_mask;
+ q->subtype_filter[0] = tf[i].subtype_filter[0];
+ __set_bit(q->type, wfilter->type_filter);
+ q++;
+ }
+
+ kfree(tf);
+set:
+ pipe_lock(pipe);
+ rcu_swap_protected(wqueue->filter, wfilter,
+ lockdep_is_held(&pipe->mutex));
+ pipe_unlock(pipe);
+ if (wfilter)
+ kfree_rcu(wfilter, rcu);
+ return 0;
+
+err_filter:
+ kfree(tf);
+ return ret;
+}
+
+static void __put_watch_queue(struct kref *kref)
+{
+ struct watch_queue *wqueue =
+ container_of(kref, struct watch_queue, usage);
+ struct watch_filter *wfilter;
+ int i;
+
+ 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 - Dispose of a ref on a watchqueue.
+ * @wqueue: The watch queue to unref.
+ */
+void put_watch_queue(struct watch_queue *wqueue)
+{
+ kref_put(&wqueue->usage, __put_watch_queue);
+}
+EXPORT_SYMBOL(put_watch_queue);
+
+static void free_watch(struct rcu_head *rcu)
+{
+ struct watch *watch = container_of(rcu, struct watch, rcu);
+
+ put_watch_queue(rcu_access_pointer(watch->queue));
+ put_cred(watch->cred);
+}
+
+static void __put_watch(struct kref *kref)
+{
+ struct watch *watch = container_of(kref, struct watch, usage);
+
+ call_rcu(&watch->rcu, free_watch);
+}
+
+/*
+ * Discard a watch.
+ */
+static void put_watch(struct watch *watch)
+{
+ kref_put(&watch->usage, __put_watch);
+}
+
+/**
+ * init_watch_queue - Initialise a watch
+ * @watch: The watch to initialise.
+ * @wqueue: The queue to assign.
+ *
+ * Initialise a watch and set the watch queue.
+ */
+void init_watch(struct watch *watch, struct watch_queue *wqueue)
+{
+ kref_init(&watch->usage);
+ INIT_HLIST_NODE(&watch->list_node);
+ INIT_HLIST_NODE(&watch->queue_node);
+ rcu_assign_pointer(watch->queue, wqueue);
+}
+
+/**
+ * add_watch_to_object - Add a watch on an object to a watch list
+ * @watch: The watch to add
+ * @wlist: The watch list to add to
+ *
+ * @watch->queue must have been set to point to the queue to post notifications
+ * to and the watch list of the object to be watched. @watch->cred must also
+ * have been set to the appropriate credentials and a ref taken on them.
+ *
+ * The caller must pin the queue and the list both and must hold the list
+ * locked against racing watch additions/removals.
+ */
+int add_watch_to_object(struct watch *watch, struct watch_list *wlist)
+{
+ struct watch_queue *wqueue = rcu_access_pointer(watch->queue);
+ struct watch *w;
+
+ hlist_for_each_entry(w, &wlist->watchers, list_node) {
+ struct watch_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);
+ hlist_add_head(&watch->queue_node, &wqueue->watches);
+ spin_unlock_bh(&wqueue->lock);
+
+ hlist_add_head(&watch->list_node, &wlist->watchers);
+ return 0;
+}
+EXPORT_SYMBOL(add_watch_to_object);
+
+/**
+ * remove_watch_from_object - Remove a watch or all watches from an object.
+ * @wlist: The watch list to remove from
+ * @wq: The watch queue of interest (ignored if @all is true)
+ * @id: The ID of the watch to remove (ignored if @all is true)
+ * @all: True to remove all objects
+ *
+ * Remove a specific watch or all watches from an object. A notification is
+ * sent to the watcher to tell them that this happened.
+ */
+int remove_watch_from_object(struct watch_list *wlist, struct watch_queue *wq,
+ u64 id, bool all)
+{
+ struct watch_notification_removal n;
+ struct watch_queue *wqueue;
+ struct watch *watch;
+ int ret = -EBADSLT;
+
+ rcu_read_lock();
+
+again:
+ spin_lock(&wlist->lock);
+ hlist_for_each_entry(watch, &wlist->watchers, list_node) {
+ if (all ||
+ (watch->id == id && rcu_access_pointer(watch->queue) == wq))
+ goto found;
+ }
+ spin_unlock(&wlist->lock);
+ goto out;
+
+found:
+ ret = 0;
+ hlist_del_init_rcu(&watch->list_node);
+ rcu_assign_pointer(watch->watch_list, NULL);
+ spin_unlock(&wlist->lock);
+
+ /* We now own the reference on watch that used to belong to wlist. */
+
+ n.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 *wqueue from deallocation.
+ */
+ 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)(struct watch *);
+
+ release_watch = wlist->release_watch;
+ rcu_read_unlock();
+ (*release_watch)(watch);
+ rcu_read_lock();
+ }
+ put_watch(watch);
+
+ if (all && !hlist_empty(&wlist->watchers))
+ goto again;
+out:
+ rcu_read_unlock();
+ return ret;
+}
+EXPORT_SYMBOL(remove_watch_from_object);
+
+/*
+ * Remove all the watches that are contributory to a queue. This has the
+ * potential to race with removal of the watches by the destruction of the
+ * objects being watched or with the distribution of notifications.
+ */
+void watch_queue_clear(struct watch_queue *wqueue)
+{
+ struct watch_list *wlist;
+ struct watch *watch;
+ bool release;
+
+ rcu_read_lock();
+ spin_lock_bh(&wqueue->lock);
+
+ /* Prevent new additions and prevent notifications from happening */
+ wqueue->defunct = true;
+
+ while (!hlist_empty(&wqueue->watches)) {
+ watch = hlist_entry(wqueue->watches.first, struct watch, queue_node);
+ hlist_del_init_rcu(&watch->queue_node);
+ /* We now own a ref on the watch. */
+ spin_unlock_bh(&wqueue->lock);
+
+ /* We can't do the next bit under the queue lock as we need to
+ * get the list lock - which would cause a deadlock if someone
+ * was removing from the opposite direction at the same time or
+ * posting a notification.
+ */
+ wlist = rcu_dereference(watch->watch_list);
+ if (wlist) {
+ void (*release_watch)(struct watch *);
+
+ spin_lock(&wlist->lock);
+
+ release = !hlist_unhashed(&watch->list_node);
+ if (release) {
+ hlist_del_init_rcu(&watch->list_node);
+ rcu_assign_pointer(watch->watch_list, NULL);
+
+ /* We now own a second ref on the watch. */
+ }
+
+ release_watch = wlist->release_watch;
+ spin_unlock(&wlist->lock);
+
+ if (release) {
+ if (release_watch) {
+ rcu_read_unlock();
+ /* This might need to call dput(), so
+ * we have to drop all the locks.
+ */
+ (*release_watch)(watch);
+ rcu_read_lock();
+ }
+ put_watch(watch);
+ }
+ }
+
+ put_watch(watch);
+ spin_lock_bh(&wqueue->lock);
+ }
+
+ spin_unlock_bh(&wqueue->lock);
+ rcu_read_unlock();
+}
+
+/**
+ * get_watch_queue - Get a watch queue from its file descriptor.
+ * @fd: The fd to query.
+ */
+struct watch_queue *get_watch_queue(int fd)
+{
+ struct pipe_inode_info *pipe;
+ struct watch_queue *wqueue = ERR_PTR(-EINVAL);
+ struct fd f;
+
+ 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);
+ }
+
+ return wqueue;
+}
+EXPORT_SYMBOL(get_watch_queue);
+
+/*
+ * Initialise a watch queue
+ */
+int watch_queue_init(struct pipe_inode_info *pipe)
+{
+ struct watch_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;
+ return 0;
+}
^ permalink raw reply related
* [RFC PATCH 06/14] keys: Add a notification facility [ver #2]
From: David Howells @ 2019-11-07 13:36 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
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(-)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index d6d8b0b756b6..957179f8cea9 100644
--- a/Documentation/security/keys/core.rst
+++ 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,
@@ -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
===============
diff --git a/include/linux/key.h b/include/linux/key.h
index 6cf8e71cf8b7..b99b40db08fc 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -176,6 +176,9 @@ struct key {
struct list_head graveyard_link;
struct rb_node serial_node;
};
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ struct watch_list *watchers; /* Entities watching this key for changes */
+#endif
struct rw_semaphore sem; /* change vs change sem */
struct key_user *user; /* owner of this key */
void *security; /* security data for this key */
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index ed3d5893830d..4c8884eea808 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -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 */
struct keyctl_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 */
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 3a5790f1f05d..c3d8320b5d3a 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -13,7 +13,8 @@
enum watch_notification_type {
WATCH_TYPE_META = 0, /* Special record */
- WATCH_TYPE__NR = 1
+ WATCH_TYPE_KEY_NOTIFY = 1, /* Key change event notification */
+ WATCH_TYPE__NR = 2
};
enum watch_meta_notification_subtype {
@@ -75,4 +76,29 @@ struct watch_notification_removal {
__u64 id; /* Type-dependent identifier */
};
+/*
+ * Type of key/keyring change notification.
+ */
+enum key_notification_subtype {
+ NOTIFY_KEY_INSTANTIATED = 0, /* Key was instantiated (aux is error code) */
+ NOTIFY_KEY_UPDATED = 1, /* Key was updated */
+ NOTIFY_KEY_LINKED = 2, /* Key (aux) was added to watched keyring */
+ NOTIFY_KEY_UNLINKED = 3, /* Key (aux) was removed from watched keyring */
+ NOTIFY_KEY_CLEARED = 4, /* Keyring was cleared */
+ NOTIFY_KEY_REVOKED = 5, /* Key was revoked */
+ NOTIFY_KEY_INVALIDATED = 6, /* Key was invalidated */
+ NOTIFY_KEY_SETATTR = 7, /* Key's attributes got changed */
+};
+
+/*
+ * Key/keyring notification record.
+ * - watch.type = WATCH_TYPE_KEY_NOTIFY
+ * - watch.subtype = enum key_notification_type
+ */
+struct key_notification {
+ struct watch_notification watch;
+ __u32 key_id; /* The key/keyring affected */
+ __u32 aux; /* Per-type auxiliary data */
+};
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index dd313438fecf..20791a556b58 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -120,3 +120,12 @@ config KEY_DH_OPERATIONS
in the kernel.
If you are unsure as to whether this is required, answer N.
+
+config KEY_NOTIFICATIONS
+ bool "Provide key/keyring change notifications"
+ depends on KEYS && WATCH_QUEUE
+ help
+ This option provides support for getting change notifications on keys
+ and keyrings on which the caller has View permission. This makes use
+ of the /dev/watch_queue misc device to handle the notification
+ buffer and provides KEYCTL_WATCH_KEY to enable/disable watches.
diff --git a/security/keys/compat.c b/security/keys/compat.c
index 9bcc404131aa..ac5a4fd0d7ea 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -161,6 +161,9 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
case KEYCTL_CAPABILITIES:
return keyctl_capabilities(compat_ptr(arg2), arg3);
+ case KEYCTL_WATCH_KEY:
+ return keyctl_watch_key(arg2, arg3, arg4);
+
default:
return -EOPNOTSUPP;
}
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 671dd730ecfc..3c90807476eb 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -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);
diff --git a/security/keys/internal.h b/security/keys/internal.h
index c039373488bd..240f55c7b4a2 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -15,6 +15,7 @@
#include <linux/task_work.h>
#include <linux/keyctl.h>
#include <linux/refcount.h>
+#include <linux/watch_queue.h>
#include <linux/compat.h>
struct iovec;
@@ -97,7 +98,8 @@ extern int __key_link_begin(struct key *keyring,
const struct keyring_index_key *index_key,
struct assoc_array_edit **_edit);
extern int __key_link_check_live_key(struct key *keyring, struct key *key);
-extern void __key_link(struct key *key, struct assoc_array_edit **_edit);
+extern void __key_link(struct key *keyring, struct key *key,
+ struct assoc_array_edit **_edit);
extern void __key_link_end(struct key *keyring,
const struct keyring_index_key *index_key,
struct assoc_array_edit *edit);
@@ -181,6 +183,23 @@ extern int key_task_permission(const key_ref_t key_ref,
const struct cred *cred,
key_perm_t perm);
+static inline void notify_key(struct key *key,
+ enum key_notification_subtype subtype, u32 aux)
+{
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ struct key_notification n = {
+ .watch.type = WATCH_TYPE_KEY_NOTIFY,
+ .watch.subtype = subtype,
+ .watch.info = watch_sizeof(n),
+ .key_id = key_serial(key),
+ .aux = aux,
+ };
+
+ post_watch_notification(key->watchers, &n.watch, current_cred(),
+ n.key_id);
+#endif
+}
+
/*
* Check to see whether permission is granted to use a key in the desired way.
*/
@@ -331,6 +350,15 @@ static inline long keyctl_pkey_e_d_s(int op,
extern long keyctl_capabilities(unsigned char __user *_buffer, size_t buflen);
+#ifdef CONFIG_KEY_NOTIFICATIONS
+extern long keyctl_watch_key(key_serial_t, int, int);
+#else
+static inline long keyctl_watch_key(key_serial_t key_id, int watch_fd, int watch_id)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
/*
* Debugging key validation
*/
diff --git a/security/keys/key.c b/security/keys/key.c
index 764f4c57913e..83e8d7c4bb6f 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -443,6 +443,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;
@@ -452,7 +453,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 */
@@ -600,6 +601,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);
@@ -610,7 +612,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)
@@ -763,9 +765,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);
@@ -1013,9 +1017,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);
@@ -1047,15 +1053,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);
@@ -1077,8 +1085,10 @@ void key_invalidate(struct key *key)
if (!test_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
down_write_nested(&key->sem, 1);
- if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags))
+ if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
+ notify_key(key, NOTIFY_KEY_INVALIDATED, 0);
key_schedule_gc_links();
+ }
up_write(&key->sem);
}
}
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 9b898c969558..6610649514fb 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -37,7 +37,9 @@ static const unsigned char keyrings_capabilities[2] = {
KEYCTL_CAPS0_MOVE
),
[1] = (KEYCTL_CAPS1_NS_KEYRING_NAME |
- KEYCTL_CAPS1_NS_KEY_TAG),
+ KEYCTL_CAPS1_NS_KEY_TAG |
+ (IS_ENABLED(CONFIG_KEY_NOTIFICATIONS) ? KEYCTL_CAPS1_NOTIFICATIONS : 0)
+ ),
};
static int key_get_type_from_user(char *type,
@@ -970,6 +972,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
if (group != (gid_t) -1)
key->gid = gid;
+ notify_key(key, NOTIFY_KEY_SETATTR, 0);
ret = 0;
error_put:
@@ -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:
@@ -1688,6 +1694,90 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
return ret;
}
+#ifdef CONFIG_KEY_NOTIFICATIONS
+/*
+ * Watch for changes to a key.
+ *
+ * The caller must have View permission to watch a key or keyring.
+ */
+long keyctl_watch_key(key_serial_t id, int watch_queue_fd, int watch_id)
+{
+ struct watch_queue *wqueue;
+ struct watch_list *wlist = NULL;
+ struct watch *watch = NULL;
+ struct key *key;
+ key_ref_t key_ref;
+ long ret;
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+
+ key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE, KEY_NEED_VIEW);
+ if (IS_ERR(key_ref))
+ return PTR_ERR(key_ref);
+ key = key_ref_to_ptr(key_ref);
+
+ wqueue = get_watch_queue(watch_queue_fd);
+ if (IS_ERR(wqueue)) {
+ ret = PTR_ERR(wqueue);
+ goto err_key;
+ }
+
+ if (watch_id >= 0) {
+ ret = -ENOMEM;
+ if (!key->watchers) {
+ wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+ if (!wlist)
+ goto err_wqueue;
+ init_watch_list(wlist, NULL);
+ }
+
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wlist;
+
+ init_watch(watch, wqueue);
+ watch->id = key->serial;
+ watch->info_id = (u32)watch_id << WATCH_INFO_ID__SHIFT;
+
+ ret = security_watch_key(key);
+ if (ret < 0)
+ goto err_watch;
+
+ down_write(&key->sem);
+ if (!key->watchers) {
+ key->watchers = wlist;
+ wlist = NULL;
+ }
+
+ ret = add_watch_to_object(watch, key->watchers);
+ up_write(&key->sem);
+
+ if (ret == 0)
+ watch = NULL;
+ } else {
+ ret = -EBADSLT;
+ if (key->watchers) {
+ down_write(&key->sem);
+ ret = remove_watch_from_object(key->watchers,
+ wqueue, key_serial(key),
+ false);
+ up_write(&key->sem);
+ }
+ }
+
+err_watch:
+ kfree(watch);
+err_wlist:
+ kfree(wlist);
+err_wqueue:
+ put_watch_queue(wqueue);
+err_key:
+ key_put(key);
+ return ret;
+}
+#endif /* CONFIG_KEY_NOTIFICATIONS */
+
/*
* Get keyrings subsystem capabilities.
*/
@@ -1857,6 +1947,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
case KEYCTL_CAPABILITIES:
return keyctl_capabilities((unsigned char __user *)arg2, (size_t)arg3);
+ case KEYCTL_WATCH_KEY:
+ return keyctl_watch_key((key_serial_t)arg2, (int)arg3, (int)arg4);
+
default:
return -EOPNOTSUPP;
}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index febf36c6ddc5..40a0dcdfda44 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -1060,12 +1060,14 @@ int keyring_restrict(key_ref_t keyring_ref, const char *type,
down_write(&keyring->sem);
down_write(&keyring_serialise_restrict_sem);
- if (keyring->restrict_link)
+ if (keyring->restrict_link) {
ret = -EEXIST;
- else if (keyring_detect_restriction_cycle(keyring, restrict_link))
+ } else if (keyring_detect_restriction_cycle(keyring, restrict_link)) {
ret = -EDEADLK;
- else
+ } else {
keyring->restrict_link = restrict_link;
+ notify_key(keyring, NOTIFY_KEY_SETATTR, 0);
+ }
up_write(&keyring_serialise_restrict_sem);
up_write(&keyring->sem);
@@ -1366,12 +1368,14 @@ int __key_link_check_live_key(struct key *keyring, struct key *key)
* holds at most one link to any given key of a particular type+description
* combination.
*/
-void __key_link(struct key *key, struct assoc_array_edit **_edit)
+void __key_link(struct key *keyring, struct key *key,
+ struct assoc_array_edit **_edit)
{
__key_get(key);
assoc_array_insert_set_object(*_edit, keyring_key_to_ptr(key));
assoc_array_apply_edit(*_edit);
*_edit = NULL;
+ notify_key(keyring, NOTIFY_KEY_LINKED, key_serial(key));
}
/*
@@ -1455,7 +1459,7 @@ int key_link(struct key *keyring, struct key *key)
if (ret == 0)
ret = __key_link_check_live_key(keyring, key);
if (ret == 0)
- __key_link(key, &edit);
+ __key_link(keyring, key, &edit);
error_end:
__key_link_end(keyring, &key->index_key, edit);
@@ -1487,7 +1491,7 @@ static int __key_unlink_begin(struct key *keyring, struct key *key,
struct assoc_array_edit *edit;
BUG_ON(*_edit != NULL);
-
+
edit = assoc_array_delete(&keyring->keys, &keyring_assoc_array_ops,
&key->index_key);
if (IS_ERR(edit))
@@ -1507,6 +1511,7 @@ static void __key_unlink(struct key *keyring, struct key *key,
struct assoc_array_edit **_edit)
{
assoc_array_apply_edit(*_edit);
+ notify_key(keyring, NOTIFY_KEY_UNLINKED, key_serial(key));
*_edit = NULL;
key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES);
}
@@ -1625,7 +1630,7 @@ int key_move(struct key *key,
goto error;
__key_unlink(from_keyring, key, &from_edit);
- __key_link(key, &to_edit);
+ __key_link(to_keyring, key, &to_edit);
error:
__key_link_end(to_keyring, &key->index_key, to_edit);
__key_unlink_end(from_keyring, key, from_edit);
@@ -1659,6 +1664,7 @@ int keyring_clear(struct key *keyring)
} else {
if (edit)
assoc_array_apply_edit(edit);
+ notify_key(keyring, NOTIFY_KEY_CLEARED, 0);
key_payload_reserve(keyring, 0);
ret = 0;
}
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 957b9e3e1492..e1b9f1a80676 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -418,7 +418,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
goto key_already_present;
if (dest_keyring)
- __key_link(key, &edit);
+ __key_link(dest_keyring, key, &edit);
mutex_unlock(&key_construction_mutex);
if (dest_keyring)
@@ -437,7 +437,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
if (dest_keyring) {
ret = __key_link_check_live_key(dest_keyring, key);
if (ret == 0)
- __key_link(key, &edit);
+ __key_link(dest_keyring, key, &edit);
__key_link_end(dest_keyring, &ctx->index_key, edit);
if (ret < 0)
goto link_check_failed;
^ permalink raw reply related
* [RFC PATCH 07/14] Add sample notification program [ver #2]
From: David Howells @ 2019-11-07 13:36 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
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 | 7 +
samples/Makefile | 1
samples/watch_queue/Makefile | 7 +
samples/watch_queue/watch_test.c | 183 ++++++++++++++++++++++++++++++++++++++
4 files changed, 198 insertions(+)
create mode 100644 samples/watch_queue/Makefile
create mode 100644 samples/watch_queue/watch_test.c
diff --git a/samples/Kconfig b/samples/Kconfig
index c8dacb4dda80..d0761f29ccb0 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -169,4 +169,11 @@ config SAMPLE_VFS
as mount API and statx(). Note that this is restricted to the x86
arch whilst it accesses system calls that aren't yet in all arches.
+config SAMPLE_WATCH_QUEUE
+ bool "Build example /dev/watch_queue notification consumer"
+ depends on HEADERS_INSTALL
+ help
+ Build example userspace program to use the new mount_notify(),
+ sb_notify() syscalls and the KEYCTL_WATCH_KEY keyctl() function.
+
endif # SAMPLES
diff --git a/samples/Makefile b/samples/Makefile
index 7d6e4ca28d69..a61a39047d02 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -20,3 +20,4 @@ obj-$(CONFIG_SAMPLE_TRACE_PRINTK) += trace_printk/
obj-$(CONFIG_VIDEO_PCI_SKELETON) += v4l/
obj-y += vfio-mdev/
subdir-$(CONFIG_SAMPLE_VFS) += vfs
+subdir-$(CONFIG_SAMPLE_WATCH_QUEUE) += watch_queue
diff --git a/samples/watch_queue/Makefile b/samples/watch_queue/Makefile
new file mode 100644
index 000000000000..eec00dd0a8df
--- /dev/null
+++ b/samples/watch_queue/Makefile
@@ -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
diff --git a/samples/watch_queue/watch_test.c b/samples/watch_queue/watch_test.c
new file mode 100644
index 000000000000..8925593bdb9b
--- /dev/null
+++ b/samples/watch_queue/watch_test.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Use /dev/watch_queue to watch for notifications.
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#define _GNU_SOURCE
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <limits.h>
+#include <linux/watch_queue.h>
+#include <linux/unistd.h>
+#include <linux/keyctl.h>
+
+#ifndef KEYCTL_WATCH_KEY
+#define KEYCTL_WATCH_KEY -1
+#endif
+#ifndef __NR_watch_devices
+#define __NR_watch_devices -1
+#endif
+
+#define BUF_SIZE 256
+
+static long keyctl_watch_key(int key, int watch_fd, int watch_id)
+{
+ return syscall(__NR_keyctl, KEYCTL_WATCH_KEY, key, watch_fd, watch_id);
+}
+
+static const char *key_subtypes[256] = {
+ [NOTIFY_KEY_INSTANTIATED] = "instantiated",
+ [NOTIFY_KEY_UPDATED] = "updated",
+ [NOTIFY_KEY_LINKED] = "linked",
+ [NOTIFY_KEY_UNLINKED] = "unlinked",
+ [NOTIFY_KEY_CLEARED] = "cleared",
+ [NOTIFY_KEY_REVOKED] = "revoked",
+ [NOTIFY_KEY_INVALIDATED] = "invalidated",
+ [NOTIFY_KEY_SETATTR] = "setattr",
+};
+
+static void saw_key_change(struct watch_notification *n, size_t len)
+{
+ struct key_notification *k = (struct key_notification *)n;
+
+ if (len != sizeof(struct key_notification)) {
+ fprintf(stderr, "Incorrect key message length\n");
+ return;
+ }
+
+ printf("KEY %08x change=%u[%s] aux=%u\n",
+ k->key_id, n->subtype, key_subtypes[n->subtype], k->aux);
+}
+
+/*
+ * Consume and display events.
+ */
+static void consumer(int fd)
+{
+ unsigned char buffer[4096], *p, *end;
+ union {
+ struct watch_notification n;
+ unsigned char buf1[128];
+ } n;
+ ssize_t buf_len;
+
+ for (;;) {
+ buf_len = read(fd, buffer, sizeof(buffer));
+ if (buf_len == -1) {
+ perror("read");
+ exit(1);
+ }
+
+ if (buf_len == 0) {
+ printf("-- END --\n");
+ return;
+ }
+
+ if (buf_len > sizeof(buffer)) {
+ fprintf(stderr, "Read buffer overrun: %zd\n", buf_len);
+ return;
+ }
+
+ printf("read() = %zd\n", buf_len);
+
+ p = buffer;
+ end = buffer + buf_len;
+ while (p < end) {
+ size_t largest, len;
+
+ largest = end - p;
+ if (largest > 128)
+ largest = 128;
+ if (largest < sizeof(struct watch_notification)) {
+ fprintf(stderr, "Short message header: %zu\n", largest);
+ return;
+ }
+ memcpy(&n, p, largest);
+
+ printf("NOTIFY[%03zx]: ty=%06x sy=%02x i=%08x\n",
+ p - buffer, n.n.type, n.n.subtype, n.n.info);
+
+ len = n.n.info & WATCH_INFO_LENGTH;
+ if (len < sizeof(n.n) || len > largest) {
+ fprintf(stderr, "Bad message length: %zu/%zu\n", len, largest);
+ exit(1);
+ }
+
+ switch (n.n.type) {
+ case WATCH_TYPE_META:
+ switch (n.n.subtype) {
+ case WATCH_META_REMOVAL_NOTIFICATION:
+ printf("REMOVAL of watchpoint %08x\n",
+ (n.n.info & WATCH_INFO_ID) >>
+ WATCH_INFO_ID__SHIFT);
+ break;
+ default:
+ printf("other meta record\n");
+ break;
+ }
+ break;
+ case WATCH_TYPE_KEY_NOTIFY:
+ saw_key_change(&n.n, len);
+ break;
+ default:
+ printf("other type\n");
+ break;
+ }
+
+ p += len;
+ }
+ }
+}
+
+static struct watch_notification_filter filter = {
+ .nr_filters = 1,
+ .filters = {
+ [0] = {
+ .type = WATCH_TYPE_KEY_NOTIFY,
+ .subtype_filter[0] = UINT_MAX,
+ },
+ },
+};
+
+int main(int argc, char **argv)
+{
+ int pipefd[2], fd;
+
+ if (pipe2(pipefd, O_NOTIFICATION_PIPE) == -1) {
+ perror("pipe2");
+ exit(1);
+ }
+ fd = pipefd[0];
+
+ if (ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE) == -1) {
+ perror("/dev/watch_queue(size)");
+ exit(1);
+ }
+
+ if (ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) == -1) {
+ perror("/dev/watch_queue(filter)");
+ exit(1);
+ }
+
+ if (keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01) == -1) {
+ perror("keyctl");
+ exit(1);
+ }
+
+ if (keyctl_watch_key(KEY_SPEC_USER_KEYRING, fd, 0x02) == -1) {
+ perror("keyctl");
+ exit(1);
+ }
+
+ consumer(fd);
+ exit(0);
+}
^ permalink raw reply related
* [RFC PATCH 08/14] pipe: Allow buffers to be marked read-whole-or-error for notifications [ver #2]
From: David Howells @ 2019-11-07 13:36 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
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(-)
diff --git a/fs/pipe.c b/fs/pipe.c
index 099bf4b657dd..588355c24ee0 100644
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -298,8 +298,14 @@ pipe_read(struct kiocb *iocb, struct iov_iter *to)
size_t written;
int error;
- if (chars > total_len)
+ if (chars > total_len) {
+ if (buf->flags & PIPE_BUF_FLAG_WHOLE) {
+ if (ret == 0)
+ ret = -ENOBUFS;
+ break;
+ }
chars = total_len;
+ }
error = pipe_buf_confirm(pipe, buf);
if (error) {
diff --git a/include/linux/pipe_fs_i.h b/include/linux/pipe_fs_i.h
index 816bc0d3aecd..a19d60a06869 100644
--- a/include/linux/pipe_fs_i.h
+++ b/include/linux/pipe_fs_i.h
@@ -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 */
/**
* struct pipe_buffer - a linux kernel pipe buffer
diff --git a/kernel/watch_queue.c b/kernel/watch_queue.c
index b73fbfe0d467..b1d30296e29b 100644
--- a/kernel/watch_queue.c
+++ b/kernel/watch_queue.c
@@ -115,7 +115,7 @@ static bool post_one_notification(struct watch_queue *wqueue,
buf->ops = &watch_queue_pipe_buf_ops;
buf->offset = offset;
buf->len = len;
- buf->flags = 0;
+ buf->flags = PIPE_BUF_FLAG_WHOLE;
pipe->head = head + 1;
if (!test_and_clear_bit(note, wqueue->notes_bitmap)) {
diff --git a/samples/watch_queue/watch_test.c b/samples/watch_queue/watch_test.c
index 8925593bdb9b..f6fb6e813551 100644
--- a/samples/watch_queue/watch_test.c
+++ b/samples/watch_queue/watch_test.c
@@ -63,7 +63,7 @@ static void saw_key_change(struct watch_notification *n, size_t len)
*/
static void consumer(int fd)
{
- unsigned char buffer[4096], *p, *end;
+ unsigned char buffer[433], *p, *end;
union {
struct watch_notification n;
unsigned char buf1[128];
^ permalink raw reply related
* [RFC PATCH 09/14] pipe: Add notification lossage handling [ver #2]
From: David Howells @ 2019-11-07 13:36 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
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(+)
diff --git a/fs/pipe.c b/fs/pipe.c
index 588355c24ee0..955a4ec1dc50 100644
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -292,6 +292,30 @@ pipe_read(struct kiocb *iocb, struct iov_iter *to)
unsigned int tail = pipe->tail;
unsigned int mask = pipe->ring_size - 1;
+#ifdef CONFIG_WATCH_QUEUE
+ if (pipe->note_loss) {
+ struct watch_notification n;
+
+ if (total_len < 8) {
+ if (ret == 0)
+ ret = -ENOBUFS;
+ break;
+ }
+
+ n.type = WATCH_TYPE_META;
+ n.subtype = WATCH_META_LOSS_NOTIFICATION;
+ n.info = watch_sizeof(n);
+ if (copy_to_iter(&n, sizeof(n), to) != sizeof(n)) {
+ if (ret == 0)
+ ret = -EFAULT;
+ break;
+ }
+ ret += sizeof(n);
+ total_len -= sizeof(n);
+ pipe->note_loss = false;
+ }
+#endif
+
if (!pipe_empty(head, tail)) {
struct pipe_buffer *buf = &pipe->bufs[tail & mask];
size_t chars = buf->len;
@@ -334,6 +358,10 @@ pipe_read(struct kiocb *iocb, struct iov_iter *to)
bool wake;
pipe_buf_release(pipe, buf);
spin_lock_irq(&pipe->wait.lock);
+#ifdef CONFIG_WATCH_QUEUE
+ if (buf->flags & PIPE_BUF_FLAG_LOSS)
+ pipe->note_loss = true;
+#endif
tail++;
pipe->tail = tail;
do_wakeup = 1;
diff --git a/include/linux/pipe_fs_i.h b/include/linux/pipe_fs_i.h
index a19d60a06869..5be6fdf5134d 100644
--- a/include/linux/pipe_fs_i.h
+++ b/include/linux/pipe_fs_i.h
@@ -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
/**
* struct pipe_buffer - a linux kernel pipe buffer
@@ -33,6 +36,7 @@ struct pipe_buffer {
* @wait: reader/writer wait point in case of empty/full pipe
* @head: The point of buffer production
* @tail: The point of buffer consumption
+ * @note_loss: The next read() should insert a data-lost message
* @max_usage: The maximum number of slots that may be used in the ring
* @ring_size: total number of buffers (should be a power of 2)
* @nr_accounted: The amount this pipe accounts for in user->pipe_bufs
@@ -56,6 +60,9 @@ struct pipe_inode_info {
unsigned int tail;
unsigned int max_usage;
unsigned int ring_size;
+#ifdef CONFIG_WATCH_QUEUE
+ bool note_loss;
+#endif
unsigned int nr_accounted;
unsigned int readers;
unsigned int writers;
diff --git a/kernel/watch_queue.c b/kernel/watch_queue.c
index b1d30296e29b..b34ad1b170a4 100644
--- a/kernel/watch_queue.c
+++ b/kernel/watch_queue.c
@@ -132,6 +132,8 @@ static bool post_one_notification(struct watch_queue *wqueue,
return done;
lost:
+ buf = &pipe->bufs[(head - 1) & mask];
+ buf->flags |= PIPE_BUF_FLAG_LOSS;
goto out;
}
diff --git a/samples/watch_queue/watch_test.c b/samples/watch_queue/watch_test.c
index f6fb6e813551..58e826109ecc 100644
--- a/samples/watch_queue/watch_test.c
+++ b/samples/watch_queue/watch_test.c
@@ -120,6 +120,9 @@ static void consumer(int fd)
(n.n.info & WATCH_INFO_ID) >>
WATCH_INFO_ID__SHIFT);
break;
+ case WATCH_META_LOSS_NOTIFICATION:
+ printf("-- LOSS --\n");
+ break;
default:
printf("other meta record\n");
break;
^ permalink raw reply related
* [RFC PATCH 10/14] Add a general, global device notification watch list [ver #2]
From: David Howells @ 2019-11-07 13:36 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
Create a general, global watch list that can be used for the posting of
device notification events, for such things as device attachment,
detachment and errors on sources such as block devices and USB devices.
This can be enabled with:
CONFIG_DEVICE_NOTIFICATIONS
To add a watch on this list, an event queue must be created and configured:
pipe2(fds, O_NOTIFICATION_PIPE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
and then a watch can be placed upon it using a system call:
watch_devices(fds[1], 12, 0);
Unless the application wants to receive all events, it should employ
appropriate filters. For example, to receive just USB notifications, it
could do:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_USB_NOTIFY,
.subtype_filter[0] = UINT_MAX;
},
},
};
ioctl(fds[1], IOC_WATCH_QUEUE_SET_FILTER, &filter);
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/watch_queue.rst | 22 ++++++-
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
drivers/base/Kconfig | 9 +++
drivers/base/Makefile | 1
drivers/base/watch.c | 90 +++++++++++++++++++++++++++
include/linux/device.h | 7 ++
include/linux/syscalls.h | 1
include/uapi/asm-generic/unistd.h | 4 +
kernel/sys_ni.c | 1
26 files changed, 152 insertions(+), 3 deletions(-)
create mode 100644 drivers/base/watch.c
diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
index d8f70282d247..ed592700be0e 100644
--- a/Documentation/watch_queue.rst
+++ b/Documentation/watch_queue.rst
@@ -223,6 +223,25 @@ The ``id`` is the ID of the source object (such as the serial number on a key).
Only watches that have the same ID set in them will see this notification.
+Global Device Watch List
+========================
+
+There is a global watch list that hardware generated events, such as device
+connection, disconnection, failure and error can be posted upon. It must be
+enabled using::
+
+ CONFIG_DEVICE_NOTIFICATIONS
+
+Watchpoints are set in userspace using the device_notify(2) system call.
+Within the kernel events are posted upon it using::
+
+ void post_device_notification(struct watch_notification *n, u64 id);
+
+where ``n`` is the formatted notification record to post. ``id`` is an
+identifier that can be used to direct to specific watches, but it should be 0
+for general use on this queue.
+
+
Watch Sources
=============
@@ -238,7 +257,8 @@ Any particular buffer can be fed from multiple sources. Sources include:
* WATCH_TYPE_BLOCK_NOTIFY
Notifications of this type indicate block layer events, such as I/O errors
- or temporary link loss. Watches of this type are set on a global queue.
+ or temporary link loss. Watches of this type are set on the global device
+ watch list.
Event Filtering
diff --git a/arch/alpha/kernel/syscalls/syscall.tbl b/arch/alpha/kernel/syscalls/syscall.tbl
index 728fe028c02c..8e841d8e4c22 100644
--- a/arch/alpha/kernel/syscalls/syscall.tbl
+++ b/arch/alpha/kernel/syscalls/syscall.tbl
@@ -475,3 +475,4 @@
543 common fspick sys_fspick
544 common pidfd_open sys_pidfd_open
# 545 reserved for clone3
+546 common watch_devices sys_watch_devices
diff --git a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl
index 6da7dc4d79cc..0f080cf44cc9 100644
--- a/arch/arm/tools/syscall.tbl
+++ b/arch/arm/tools/syscall.tbl
@@ -449,3 +449,4 @@
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
435 common clone3 sys_clone3
+436 common watch_devices sys_watch_devices
diff --git a/arch/arm64/include/asm/unistd.h b/arch/arm64/include/asm/unistd.h
index 2629a68b8724..368761302768 100644
--- a/arch/arm64/include/asm/unistd.h
+++ b/arch/arm64/include/asm/unistd.h
@@ -38,7 +38,7 @@
#define __ARM_NR_compat_set_tls (__ARM_NR_COMPAT_BASE + 5)
#define __ARM_NR_COMPAT_END (__ARM_NR_COMPAT_BASE + 0x800)
-#define __NR_compat_syscalls 436
+#define __NR_compat_syscalls 437
#endif
#define __ARCH_WANT_SYS_CLONE
diff --git a/arch/arm64/include/asm/unistd32.h b/arch/arm64/include/asm/unistd32.h
index 94ab29cf4f00..b5310789ce7a 100644
--- a/arch/arm64/include/asm/unistd32.h
+++ b/arch/arm64/include/asm/unistd32.h
@@ -879,6 +879,8 @@ __SYSCALL(__NR_fspick, sys_fspick)
__SYSCALL(__NR_pidfd_open, sys_pidfd_open)
#define __NR_clone3 435
__SYSCALL(__NR_clone3, sys_clone3)
+#define __NR_watch_devices 436
+__SYSCALL(__NR_watch_devices, sys_watch_devices)
/*
* Please add new compat syscalls above this comment and update
diff --git a/arch/ia64/kernel/syscalls/syscall.tbl b/arch/ia64/kernel/syscalls/syscall.tbl
index 36d5faf4c86c..2f33f5db2fed 100644
--- a/arch/ia64/kernel/syscalls/syscall.tbl
+++ b/arch/ia64/kernel/syscalls/syscall.tbl
@@ -356,3 +356,4 @@
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
# 435 reserved for clone3
+436 common watch_devices sys_watch_devices
diff --git a/arch/m68k/kernel/syscalls/syscall.tbl b/arch/m68k/kernel/syscalls/syscall.tbl
index a88a285a0e5f..83e4e8784b88 100644
--- a/arch/m68k/kernel/syscalls/syscall.tbl
+++ b/arch/m68k/kernel/syscalls/syscall.tbl
@@ -435,3 +435,4 @@
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
# 435 reserved for clone3
+436 common watch_devices sys_watch_devices
diff --git a/arch/microblaze/kernel/syscalls/syscall.tbl b/arch/microblaze/kernel/syscalls/syscall.tbl
index 09b0cd7dab0a..9a70a3be3b7b 100644
--- a/arch/microblaze/kernel/syscalls/syscall.tbl
+++ b/arch/microblaze/kernel/syscalls/syscall.tbl
@@ -441,3 +441,4 @@
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
435 common clone3 sys_clone3
+436 common watch_devices sys_watch_devices
diff --git a/arch/mips/kernel/syscalls/syscall_n32.tbl b/arch/mips/kernel/syscalls/syscall_n32.tbl
index e7c5ab38e403..b39527fc32c9 100644
--- a/arch/mips/kernel/syscalls/syscall_n32.tbl
+++ b/arch/mips/kernel/syscalls/syscall_n32.tbl
@@ -374,3 +374,4 @@
433 n32 fspick sys_fspick
434 n32 pidfd_open sys_pidfd_open
435 n32 clone3 __sys_clone3
+436 n32 watch_devices sys_watch_devices
diff --git a/arch/mips/kernel/syscalls/syscall_n64.tbl b/arch/mips/kernel/syscalls/syscall_n64.tbl
index 13cd66581f3b..a7f0c5e71768 100644
--- a/arch/mips/kernel/syscalls/syscall_n64.tbl
+++ b/arch/mips/kernel/syscalls/syscall_n64.tbl
@@ -350,3 +350,4 @@
433 n64 fspick sys_fspick
434 n64 pidfd_open sys_pidfd_open
435 n64 clone3 __sys_clone3
+436 n64 watch_devices sys_watch_devices
diff --git a/arch/mips/kernel/syscalls/syscall_o32.tbl b/arch/mips/kernel/syscalls/syscall_o32.tbl
index 353539ea4140..6f378288598c 100644
--- a/arch/mips/kernel/syscalls/syscall_o32.tbl
+++ b/arch/mips/kernel/syscalls/syscall_o32.tbl
@@ -423,3 +423,4 @@
433 o32 fspick sys_fspick
434 o32 pidfd_open sys_pidfd_open
435 o32 clone3 __sys_clone3
+436 o32 watch_devices sys_watch_devices
diff --git a/arch/parisc/kernel/syscalls/syscall.tbl b/arch/parisc/kernel/syscalls/syscall.tbl
index 285ff516150c..b64bbafa5919 100644
--- a/arch/parisc/kernel/syscalls/syscall.tbl
+++ b/arch/parisc/kernel/syscalls/syscall.tbl
@@ -433,3 +433,4 @@
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
435 common clone3 sys_clone3_wrapper
+436 common watch_devices sys_watch_devices
diff --git a/arch/powerpc/kernel/syscalls/syscall.tbl b/arch/powerpc/kernel/syscalls/syscall.tbl
index 43f736ed47f2..0a503239ab5c 100644
--- a/arch/powerpc/kernel/syscalls/syscall.tbl
+++ b/arch/powerpc/kernel/syscalls/syscall.tbl
@@ -517,3 +517,4 @@
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
435 nospu clone3 ppc_clone3
+436 common watch_devices sys_watch_devices
diff --git a/arch/s390/kernel/syscalls/syscall.tbl b/arch/s390/kernel/syscalls/syscall.tbl
index 3054e9c035a3..19b43c0d928a 100644
--- a/arch/s390/kernel/syscalls/syscall.tbl
+++ b/arch/s390/kernel/syscalls/syscall.tbl
@@ -438,3 +438,4 @@
433 common fspick sys_fspick sys_fspick
434 common pidfd_open sys_pidfd_open sys_pidfd_open
435 common clone3 sys_clone3 sys_clone3
+436 common watch_devices sys_watch_devices sys_watch_devices
diff --git a/arch/sh/kernel/syscalls/syscall.tbl b/arch/sh/kernel/syscalls/syscall.tbl
index b5ed26c4c005..b454e07c9372 100644
--- a/arch/sh/kernel/syscalls/syscall.tbl
+++ b/arch/sh/kernel/syscalls/syscall.tbl
@@ -438,3 +438,4 @@
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
# 435 reserved for clone3
+436 common watch_devices sys_watch_devices
diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl
index 8c8cc7537fb2..8ef43c27457e 100644
--- a/arch/sparc/kernel/syscalls/syscall.tbl
+++ b/arch/sparc/kernel/syscalls/syscall.tbl
@@ -481,3 +481,4 @@
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
# 435 reserved for clone3
+436 common watch_devices sys_watch_devices
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index 3fe02546aed3..9b225c0d5240 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -440,3 +440,4 @@
433 i386 fspick sys_fspick __ia32_sys_fspick
434 i386 pidfd_open sys_pidfd_open __ia32_sys_pidfd_open
435 i386 clone3 sys_clone3 __ia32_sys_clone3
+436 i386 watch_devices sys_watch_devices __ia32_sys_watch_devices
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index c29976eca4a8..29293d103829 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -357,6 +357,7 @@
433 common fspick __x64_sys_fspick
434 common pidfd_open __x64_sys_pidfd_open
435 common clone3 __x64_sys_clone3/ptregs
+436 common watch_devices __x64_sys_watch_devices
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/arch/xtensa/kernel/syscalls/syscall.tbl b/arch/xtensa/kernel/syscalls/syscall.tbl
index 25f4de729a6d..243fa18b8d1e 100644
--- a/arch/xtensa/kernel/syscalls/syscall.tbl
+++ b/arch/xtensa/kernel/syscalls/syscall.tbl
@@ -406,3 +406,4 @@
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
435 common clone3 sys_clone3
+436 common watch_devices sys_watch_devices
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index 28b92e3cc570..e37d37684132 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -1,6 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
menu "Generic Driver Options"
+config DEVICE_NOTIFICATIONS
+ bool "Provide device event notifications"
+ depends on WATCH_QUEUE
+ help
+ This option provides support for getting hardware event notifications
+ on devices, buses and interfaces. This makes use of the
+ /dev/watch_queue misc device to handle the notification buffer.
+ device_notify(2) is used to set/remove watches.
+
config UEVENT_HELPER
bool "Support for uevent helper"
help
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 157452080f3d..4db2e8f1a1f4 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -7,6 +7,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \
attribute_container.o transport_class.o \
topology.o container.o property.o cacheinfo.o \
devcon.o swnode.o
+obj-$(CONFIG_DEVICE_NOTIFICATIONS) += watch.o
obj-$(CONFIG_DEVTMPFS) += devtmpfs.o
obj-y += power/
obj-$(CONFIG_ISA_BUS_API) += isa.o
diff --git a/drivers/base/watch.c b/drivers/base/watch.c
new file mode 100644
index 000000000000..725aaa24275b
--- /dev/null
+++ b/drivers/base/watch.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Event notifications.
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#include <linux/device.h>
+#include <linux/watch_queue.h>
+#include <linux/syscalls.h>
+#include <linux/init_task.h>
+#include <linux/security.h>
+
+/*
+ * Global queue for watching for device layer events.
+ */
+static struct watch_list device_watchers = {
+ .watchers = HLIST_HEAD_INIT,
+ .lock = __SPIN_LOCK_UNLOCKED(&device_watchers.lock),
+};
+
+static DEFINE_SPINLOCK(device_watchers_lock);
+
+/**
+ * post_device_notification - Post notification of a device event
+ * @n - The notification to post
+ * @id - The device ID
+ *
+ * Note that there's only a global queue to which all events are posted. Might
+ * want to provide per-dev queues also.
+ */
+void post_device_notification(struct watch_notification *n, u64 id)
+{
+ post_watch_notification(&device_watchers, n, &init_cred, id);
+}
+EXPORT_SYMBOL(post_device_notification);
+
+/**
+ * sys_watch_devices - Watch for device events.
+ * @watch_fd: The watch queue to send notifications to.
+ * @watch_id: The watch ID to be placed in the notification (-1 to remove watch)
+ * @flags: Flags (reserved for future)
+ */
+SYSCALL_DEFINE3(watch_devices, int, watch_fd, int, watch_id, unsigned int, flags)
+{
+ struct watch_queue *wqueue;
+ struct watch *watch = NULL;
+ long ret = -ENOMEM;
+
+ if (watch_id < -1 || watch_id > 0xff || flags)
+ return -EINVAL;
+
+ wqueue = get_watch_queue(watch_fd);
+ if (IS_ERR(wqueue)) {
+ ret = PTR_ERR(wqueue);
+ goto err;
+ }
+
+ if (watch_id >= 0) {
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wqueue;
+
+ init_watch(watch, wqueue);
+ watch->info_id = (u32)watch_id << WATCH_INFO_ID__SHIFT;
+
+ ret = security_watch_devices();
+ if (ret < 0)
+ goto err_watch;
+
+ spin_lock(&device_watchers_lock);
+ ret = add_watch_to_object(watch, &device_watchers);
+ spin_unlock(&device_watchers_lock);
+ if (ret == 0)
+ watch = NULL;
+ } else {
+ spin_lock(&device_watchers_lock);
+ ret = remove_watch_from_object(&device_watchers, wqueue, 0,
+ false);
+ spin_unlock(&device_watchers_lock);
+ }
+
+err_watch:
+ kfree(watch);
+err_wqueue:
+ put_watch_queue(wqueue);
+err:
+ return ret;
+}
diff --git a/include/linux/device.h b/include/linux/device.h
index 297239a08bb7..f30e80185825 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -43,6 +43,7 @@ struct iommu_group;
struct iommu_fwspec;
struct dev_pin_info;
struct iommu_param;
+struct watch_notification;
struct bus_attribute {
struct attribute attr;
@@ -1654,6 +1655,12 @@ struct device_link *device_link_add(struct device *consumer,
void device_link_del(struct device_link *link);
void device_link_remove(void *consumer, struct device *supplier);
+#ifdef CONFIG_DEVICE_NOTIFICATIONS
+extern void post_device_notification(struct watch_notification *n, u64 id);
+#else
+static inline void post_device_notification(struct watch_notification *n, u64 id) {}
+#endif
+
#ifndef dev_fmt
#define dev_fmt(fmt) fmt
#endif
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index f7c561c4dcdd..565f033a61bc 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1000,6 +1000,7 @@ asmlinkage long sys_fspick(int dfd, const char __user *path, unsigned int flags)
asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,
siginfo_t __user *info,
unsigned int flags);
+asmlinkage long sys_watch_devices(int watch_fd, int watch_id, unsigned int flags);
/*
* Architecture-specific system calls
diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
index 1fc8faa6e973..4794d3c2afd7 100644
--- a/include/uapi/asm-generic/unistd.h
+++ b/include/uapi/asm-generic/unistd.h
@@ -850,9 +850,11 @@ __SYSCALL(__NR_pidfd_open, sys_pidfd_open)
#define __NR_clone3 435
__SYSCALL(__NR_clone3, sys_clone3)
#endif
+#define __NR_watch_devices 436
+__SYSCALL(__NR_watch_devices, sys_watch_devices)
#undef __NR_syscalls
-#define __NR_syscalls 436
+#define __NR_syscalls 437
/*
* 32 bit systems traditionally used different
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 34b76895b81e..184ad68c087f 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -51,6 +51,7 @@ COND_SYSCALL_COMPAT(io_pgetevents);
COND_SYSCALL(io_uring_setup);
COND_SYSCALL(io_uring_enter);
COND_SYSCALL(io_uring_register);
+COND_SYSCALL(watch_devices);
/* fs/xattr.c */
^ permalink raw reply related
* Re: Continuing the UAPI split
From: Christian Brauner @ 2019-11-07 13:36 UTC (permalink / raw)
To: Florian Weimer; +Cc: Elichai Turkel, linux-api, libc-alpha
In-Reply-To: <87zhh7hlbl.fsf@oldenburg2.str.redhat.com>
On Thu, Nov 07, 2019 at 02:23:26PM +0100, Florian Weimer wrote:
> * Elichai Turkel:
>
> > Thanks for the response,
> > I'm not sure what are you suggesting exactly.
> > A rename to the structs/types so they won't collide with libc?
>
> Or use namespaces. I mean, we have had proper technical solutions for
> this issue for more than 20 years now. I know that this isn't going to
> happen, though.
>
> > Prioritizing POSIX conformance in the kernel(I think that ship has long sailed)?
>
> That doesn't really work anyway if userspace has different versions of
> the type depending on feature macros. Examples are struct stat or soon
> struct timespec on 32-bit architectures.
>
> > Or just giving up and telling users they can't just directly include
> > both libc headers and kernel headers?
>
> That's what I've been telling users if they encounter fringe cases I
> can't fix on the glibc side.
>
> It might also help if top-level types for end user use would be in
> separate headers. This is what we started doing internally in glibc, to
> disentangle our own headers and eliminate cyclic dependencies.
>
> > (I'm actually not sure how it works now. because there are definitely
> > collision and if we are using ifdefs and undefs black magic you still
> > end up with a single declaration in the end that might not be
> > compatible with both libc and kernel)
>
> Officially, it's supposed to work today. We have one glibc developer
> who says that it's easy to solve, but I disagree strongly. The fact
> that the problems are not fixed promptly suggests that it's anything but
> simple.
>
> What I've been doing instead is to include UAPI headers directly from
> glibc headers if it's technically feasible. (It's not possible in POSIX
A problem I recently ran into that is related are problems with
sys/wait.h and linux/wait.h.
How P_{PID,PGID,PIDFD} used by the waitid() syscall are defined is
different for the two headers.
linux/wait.h uses #define for P_{PID,PGID,PIDFD} whereas sys/wait.h
uses an enum.
The problem is that if I simply don't rely on sys/wait.h and just do
#ifndef P_PID
#define P_PID <value>
#endif
where value is what the syscall expects then technically I need to call
the waitid() syscall directly since it's not at all guaranteed - afaict
- that the P_PID enum == P_PID define that glibc uses for its waitid()
syscall wrapper.
So I'm now in a scenario where I can't call the glibc wrapper for
waitid() with the linux/wait.h defines and I can't call the syscall
directly (e.g. because I want to make use of the syscall's rusage
argument that glibc doesn't expose) with sys/wait.h's P_PID enum.
I'm not sure what the right solution is here...
Christian
^ permalink raw reply
* [RFC PATCH 11/14] block: Add block layer notifications [ver #2]
From: David Howells @ 2019-11-07 13:37 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
Add a block layer notification mechanism whereby notifications about
block-layer events such as I/O errors, can be reported to a monitoring
process asynchronously.
Firstly, an event queue needs to be created:
pipe2(fds, O_NOTIFICATION_PIPE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
then a notification can be set up to report block notifications via that
queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_BLOCK_NOTIFY,
.subtype_filter[0] = UINT_MAX;
},
},
};
ioctl(fds[1], IOC_WATCH_QUEUE_SET_FILTER, &filter);
watch_devices(fds[1], 12);
After that, records will be placed into the queue when, for example, errors
occur on a block device. Records are of the following format:
struct block_notification {
struct watch_notification watch;
__u64 dev;
__u64 sector;
} *n;
Where:
n->watch.type will be WATCH_TYPE_BLOCK_NOTIFY
n->watch.subtype will be the type of notification, such as
NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the second argument to
watch_devices(), shifted.
n->dev will be the device numbers munged together.
n->sector will indicate the affected sector (if appropriate for the
event).
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/watch_queue.rst | 4 +++-
block/Kconfig | 9 +++++++++
block/blk-core.c | 29 ++++++++++++++++++++++++++++
include/linux/blkdev.h | 15 ++++++++++++++
include/uapi/linux/watch_queue.h | 30 ++++++++++++++++++++++++++++-
samples/watch_queue/watch_test.c | 40 +++++++++++++++++++++++++++++++++++++-
6 files changed, 124 insertions(+), 3 deletions(-)
diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
index ed592700be0e..f2299f631ae8 100644
--- a/Documentation/watch_queue.rst
+++ b/Documentation/watch_queue.rst
@@ -8,7 +8,9 @@ opened by userspace. This can be used in conjunction with::
* Key/keyring notifications
- * General device event notifications
+ * General device event notifications, including::
+
+ * Block layer event notifications
The notifications buffers can be enabled by:
diff --git a/block/Kconfig b/block/Kconfig
index 41c0917ce622..0906227a9431 100644
--- a/block/Kconfig
+++ b/block/Kconfig
@@ -177,6 +177,15 @@ config BLK_SED_OPAL
Enabling this option enables users to setup/unlock/lock
Locking ranges for SED devices using the Opal protocol.
+config BLK_NOTIFICATIONS
+ bool "Block layer event notifications"
+ depends on DEVICE_NOTIFICATIONS
+ help
+ This option provides support for getting block layer event
+ notifications. This makes use of the /dev/watch_queue misc device to
+ handle the notification buffer and provides the device_notify() system
+ call to enable/disable watches.
+
menu "Partition Types"
source "block/partitions/Kconfig"
diff --git a/block/blk-core.c b/block/blk-core.c
index d5e668ec751b..08e9b12ff5a5 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -184,6 +184,22 @@ static const struct {
[BLK_STS_IOERR] = { -EIO, "I/O" },
};
+#ifdef CONFIG_BLK_NOTIFICATIONS
+static const
+enum block_notification_type blk_notifications[ARRAY_SIZE(blk_errors)] = {
+ [BLK_STS_TIMEOUT] = NOTIFY_BLOCK_ERROR_TIMEOUT,
+ [BLK_STS_NOSPC] = NOTIFY_BLOCK_ERROR_NO_SPACE,
+ [BLK_STS_TRANSPORT] = NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT,
+ [BLK_STS_TARGET] = NOTIFY_BLOCK_ERROR_CRITICAL_TARGET,
+ [BLK_STS_NEXUS] = NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS,
+ [BLK_STS_MEDIUM] = NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM,
+ [BLK_STS_PROTECTION] = NOTIFY_BLOCK_ERROR_PROTECTION,
+ [BLK_STS_RESOURCE] = NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE,
+ [BLK_STS_DEV_RESOURCE] = NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE,
+ [BLK_STS_IOERR] = NOTIFY_BLOCK_ERROR_IO,
+};
+#endif
+
blk_status_t errno_to_blk_status(int errno)
{
int i;
@@ -224,6 +240,19 @@ static void print_req_error(struct request *req, blk_status_t status,
req->cmd_flags & ~REQ_OP_MASK,
req->nr_phys_segments,
IOPRIO_PRIO_CLASS(req->ioprio));
+
+#ifdef CONFIG_BLK_NOTIFICATIONS
+ if (blk_notifications[idx]) {
+ struct block_notification n = {
+ .watch.type = WATCH_TYPE_BLOCK_NOTIFY,
+ .watch.subtype = blk_notifications[idx],
+ .watch.info = watch_sizeof(n),
+ .dev = req->rq_disk ? disk_devt(req->rq_disk) : 0,
+ .sector = blk_rq_pos(req),
+ };
+ post_block_notification(&n);
+ }
+#endif
}
static void req_bio_endio(struct request *rq, struct bio *bio,
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index f3ea78b0c91c..477472c11815 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -27,6 +27,7 @@
#include <linux/percpu-refcount.h>
#include <linux/scatterlist.h>
#include <linux/blkzoned.h>
+#include <linux/watch_queue.h>
struct module;
struct scsi_ioctl_command;
@@ -1773,6 +1774,20 @@ static inline bool blk_req_can_dispatch_to_zone(struct request *rq)
}
#endif /* CONFIG_BLK_DEV_ZONED */
+#ifdef CONFIG_BLK_NOTIFICATIONS
+static inline void post_block_notification(struct block_notification *n)
+{
+ u64 id = 0; /* Might want to allow dev# here. */
+
+ post_device_notification(&n->watch, id);
+}
+#else
+static inline void post_block_notification(struct block_notification *n)
+{
+}
+#endif
+
+
#else /* CONFIG_BLOCK */
struct block_device;
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index c3d8320b5d3a..557771413242 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -14,7 +14,8 @@
enum watch_notification_type {
WATCH_TYPE_META = 0, /* Special record */
WATCH_TYPE_KEY_NOTIFY = 1, /* Key change event notification */
- WATCH_TYPE__NR = 2
+ WATCH_TYPE_BLOCK_NOTIFY = 2, /* Block layer event notification */
+ WATCH_TYPE__NR = 3
};
enum watch_meta_notification_subtype {
@@ -101,4 +102,31 @@ struct key_notification {
__u32 aux; /* Per-type auxiliary data */
};
+/*
+ * Type of block layer notification.
+ */
+enum block_notification_type {
+ NOTIFY_BLOCK_ERROR_TIMEOUT = 1, /* Timeout error */
+ NOTIFY_BLOCK_ERROR_NO_SPACE = 2, /* Critical space allocation error */
+ NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT = 3, /* Recoverable transport error */
+ NOTIFY_BLOCK_ERROR_CRITICAL_TARGET = 4, /* Critical target error */
+ NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS = 5, /* Critical nexus error */
+ NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM = 6, /* Critical medium error */
+ NOTIFY_BLOCK_ERROR_PROTECTION = 7, /* Protection error */
+ NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE = 8, /* Kernel resource error */
+ NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE = 9, /* Device resource error */
+ NOTIFY_BLOCK_ERROR_IO = 10, /* Other I/O error */
+};
+
+/*
+ * Block layer notification record.
+ * - watch.type = WATCH_TYPE_BLOCK_NOTIFY
+ * - watch.subtype = enum block_notification_type
+ */
+struct block_notification {
+ struct watch_notification watch; /* WATCH_TYPE_BLOCK_NOTIFY */
+ __u64 dev; /* Device number */
+ __u64 sector; /* Affected sector */
+};
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/samples/watch_queue/watch_test.c b/samples/watch_queue/watch_test.c
index 58e826109ecc..55879531a7d5 100644
--- a/samples/watch_queue/watch_test.c
+++ b/samples/watch_queue/watch_test.c
@@ -58,6 +58,32 @@ static void saw_key_change(struct watch_notification *n, size_t len)
k->key_id, n->subtype, key_subtypes[n->subtype], k->aux);
}
+static const char *block_subtypes[256] = {
+ [NOTIFY_BLOCK_ERROR_TIMEOUT] = "timeout",
+ [NOTIFY_BLOCK_ERROR_NO_SPACE] = "critical space allocation",
+ [NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT] = "recoverable transport",
+ [NOTIFY_BLOCK_ERROR_CRITICAL_TARGET] = "critical target",
+ [NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS] = "critical nexus",
+ [NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM] = "critical medium",
+ [NOTIFY_BLOCK_ERROR_PROTECTION] = "protection",
+ [NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE] = "kernel resource",
+ [NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE] = "device resource",
+ [NOTIFY_BLOCK_ERROR_IO] = "I/O",
+};
+
+static void saw_block_change(struct watch_notification *n, size_t len)
+{
+ struct block_notification *b = (struct block_notification *)n;
+
+ if (len < sizeof(struct block_notification))
+ return;
+
+ printf("BLOCK %08llx e=%u[%s] s=%llx\n",
+ (unsigned long long)b->dev,
+ n->subtype, block_subtypes[n->subtype],
+ (unsigned long long)b->sector);
+}
+
/*
* Consume and display events.
*/
@@ -131,6 +157,9 @@ static void consumer(int fd)
case WATCH_TYPE_KEY_NOTIFY:
saw_key_change(&n.n, len);
break;
+ case WATCH_TYPE_BLOCK_NOTIFY:
+ saw_block_change(&n.n, len);
+ break;
default:
printf("other type\n");
break;
@@ -142,12 +171,16 @@ static void consumer(int fd)
}
static struct watch_notification_filter filter = {
- .nr_filters = 1,
+ .nr_filters = 2,
.filters = {
[0] = {
.type = WATCH_TYPE_KEY_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
+ [1] = {
+ .type = WATCH_TYPE_BLOCK_NOTIFY,
+ .subtype_filter[0] = UINT_MAX,
+ },
},
};
@@ -181,6 +214,11 @@ int main(int argc, char **argv)
exit(1);
}
+ if (syscall(__NR_watch_devices, fd, 0x04, 0) == -1) {
+ perror("watch_devices");
+ exit(1);
+ }
+
consumer(fd);
exit(0);
}
^ permalink raw reply related
* [RFC PATCH 12/14] usb: Add USB subsystem notifications [ver #2]
From: David Howells @ 2019-11-07 13:37 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
Add a USB subsystem notification mechanism whereby notifications about
hardware events such as device connection, disconnection, reset and I/O
errors, can be reported to a monitoring process asynchronously.
Firstly, an event queue needs to be created:
pipe2(fds, O_NOTIFICATION_PIPE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
then a notification can be set up to report USB notifications via that
queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_USB_NOTIFY,
.subtype_filter[0] = UINT_MAX;
},
},
};
ioctl(fds[1], IOC_WATCH_QUEUE_SET_FILTER, &filter);
notify_devices(fds[1], 12);
After that, messages will be placed into the queue when events occur on a
USB device or bus. Messages are of the following format:
struct usb_notification {
struct watch_notification watch;
__u32 error;
__u32 reserved;
__u8 name_len;
__u8 name[0];
} *n;
Where:
n->watch.type will be WATCH_TYPE_USB_NOTIFY
n->watch.subtype will be the type of notification, such as
NOTIFY_USB_DEVICE_ADD.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
message.
n->watch.info & WATCH_INFO_ID will be the second argument to
device_notify(), shifted.
n->error and n->reserved are intended to convey information such as
error codes, but are currently not used
n->name_len and n->name convey the USB device name as an
unterminated string. This may be truncated - it is currently
limited to a maximum 63 chars.
Note that it is permissible for messages to be of variable length - or, at
least, the length may be dependent on the subtype.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
cc: linux-usb@vger.kernel.org
---
Documentation/watch_queue.rst | 9 +++++++
drivers/usb/core/Kconfig | 9 +++++++
drivers/usb/core/devio.c | 47 ++++++++++++++++++++++++++++++++++++++
drivers/usb/core/hub.c | 4 +++
include/linux/usb.h | 18 +++++++++++++++
include/uapi/linux/watch_queue.h | 28 ++++++++++++++++++++++-
samples/watch_queue/watch_test.c | 29 +++++++++++++++++++++++
7 files changed, 142 insertions(+), 2 deletions(-)
diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
index f2299f631ae8..5321a9cb1ab2 100644
--- a/Documentation/watch_queue.rst
+++ b/Documentation/watch_queue.rst
@@ -12,6 +12,8 @@ opened by userspace. This can be used in conjunction with::
* Block layer event notifications
+ * USB subsystem event notifications
+
The notifications buffers can be enabled by:
@@ -262,6 +264,13 @@ Any particular buffer can be fed from multiple sources. Sources include:
or temporary link loss. Watches of this type are set on the global device
watch list.
+ * WATCH_TYPE_USB_NOTIFY
+
+ Notifications of this type indicate USB subsystem events, such as
+ attachment, removal, reset and I/O errors. Separate events are generated
+ for buses and devices. Watchpoints of this type are set on the global
+ device watch list.
+
Event Filtering
===============
diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig
index ecaacc8ed311..57e7b649e48b 100644
--- a/drivers/usb/core/Kconfig
+++ b/drivers/usb/core/Kconfig
@@ -102,3 +102,12 @@ config USB_AUTOSUSPEND_DELAY
The default value Linux has always had is 2 seconds. Change
this value if you want a different delay and cannot modify
the command line or module parameter.
+
+config USB_NOTIFICATIONS
+ bool "Provide USB hardware event notifications"
+ depends on USB && DEVICE_NOTIFICATIONS
+ help
+ This option provides support for getting hardware event notifications
+ on USB devices and interfaces. This makes use of the
+ /dev/watch_queue misc device to handle the notification buffer.
+ device_notify(2) is used to set/remove watches.
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index 3f899552f6e3..693a5657dba3 100644
--- a/drivers/usb/core/devio.c
+++ b/drivers/usb/core/devio.c
@@ -41,6 +41,7 @@
#include <linux/dma-mapping.h>
#include <asm/byteorder.h>
#include <linux/moduleparam.h>
+#include <linux/watch_queue.h>
#include "usb.h"
@@ -2748,13 +2749,59 @@ static void usbdev_remove(struct usb_device *udev)
mutex_unlock(&usbfs_mutex);
}
+#ifdef CONFIG_USB_NOTIFICATIONS
+static noinline void post_usb_notification(const char *devname,
+ enum usb_notification_type subtype,
+ u32 error)
+{
+ unsigned int name_len, n_len;
+ u64 id = 0; /* We can put a device ID here for separate dev watches */
+
+ struct {
+ struct usb_notification n;
+ char more_name[USB_NOTIFICATION_MAX_NAME_LEN -
+ (sizeof(struct usb_notification) -
+ offsetof(struct usb_notification, name))];
+ } n;
+
+ name_len = strlen(devname);
+ name_len = min_t(size_t, name_len, USB_NOTIFICATION_MAX_NAME_LEN);
+ n_len = offsetof(struct usb_notification, name) + name_len;
+
+ memset(&n, 0, sizeof(n));
+ memcpy(n.n.name, devname, n_len);
+
+ n.n.watch.type = WATCH_TYPE_USB_NOTIFY;
+ n.n.watch.subtype = subtype;
+ n.n.watch.info = n_len;
+ n.n.error = error;
+ n.n.name_len = name_len;
+
+ post_device_notification(&n.n.watch, id);
+}
+
+void post_usb_device_notification(const struct usb_device *udev,
+ enum usb_notification_type subtype, u32 error)
+{
+ post_usb_notification(dev_name(&udev->dev), subtype, error);
+}
+
+void post_usb_bus_notification(const struct usb_bus *ubus,
+ enum usb_notification_type subtype, u32 error)
+{
+ post_usb_notification(ubus->bus_name, subtype, error);
+}
+#endif
+
static int usbdev_notify(struct notifier_block *self,
unsigned long action, void *dev)
{
switch (action) {
case USB_DEVICE_ADD:
+ post_usb_device_notification(dev, NOTIFY_USB_DEVICE_ADD, 0);
break;
case USB_DEVICE_REMOVE:
+ post_usb_device_notification(dev, NOTIFY_USB_DEVICE_REMOVE, 0);
usbdev_remove(dev);
break;
}
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 236313f41f4a..e8ebacc15a32 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -29,6 +29,7 @@
#include <linux/random.h>
#include <linux/pm_qos.h>
#include <linux/kobject.h>
+#include <linux/watch_queue.h>
#include <linux/uaccess.h>
#include <asm/byteorder.h>
@@ -4605,6 +4606,9 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
(udev->config) ? "reset" : "new", speed,
devnum, driver_name);
+ if (udev->config)
+ post_usb_device_notification(udev, NOTIFY_USB_DEVICE_RESET, 0);
+
/* Set up TT records, if needed */
if (hdev->tt) {
udev->tt = hdev->tt;
diff --git a/include/linux/usb.h b/include/linux/usb.h
index e656e7b4b1e4..93fa0666f95a 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -26,6 +26,7 @@
struct usb_device;
struct usb_driver;
struct wusb_dev;
+enum usb_notification_type;
/*-------------------------------------------------------------------------*/
@@ -2015,6 +2016,23 @@ extern void usb_led_activity(enum usb_led_event ev);
static inline void usb_led_activity(enum usb_led_event ev) {}
#endif
+/*
+ * Notification functions.
+ */
+#ifdef CONFIG_USB_NOTIFICATIONS
+extern void post_usb_device_notification(const struct usb_device *udev,
+ enum usb_notification_type subtype,
+ u32 error);
+extern void post_usb_bus_notification(const struct usb_bus *ubus,
+ enum usb_notification_type subtype,
+ u32 error);
+#else
+static inline void post_usb_device_notification(const struct usb_device *udev,
+ unsigned int subtype, u32 error) {}
+static inline void post_usb_bus_notification(const struct usb_bus *ubus,
+ unsigned int subtype, u32 error) {}
+#endif
+
#endif /* __KERNEL__ */
#endif
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 557771413242..ad1ae229674a 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -15,7 +15,8 @@ enum watch_notification_type {
WATCH_TYPE_META = 0, /* Special record */
WATCH_TYPE_KEY_NOTIFY = 1, /* Key change event notification */
WATCH_TYPE_BLOCK_NOTIFY = 2, /* Block layer event notification */
- WATCH_TYPE__NR = 3
+ WATCH_TYPE_USB_NOTIFY = 3, /* USB subsystem event notification */
+ WATCH_TYPE__NR = 4
};
enum watch_meta_notification_subtype {
@@ -129,4 +130,29 @@ struct block_notification {
__u64 sector; /* Affected sector */
};
+/*
+ * Type of USB layer notification.
+ */
+enum usb_notification_type {
+ NOTIFY_USB_DEVICE_ADD = 0, /* USB device added */
+ NOTIFY_USB_DEVICE_REMOVE = 1, /* USB device removed */
+ NOTIFY_USB_DEVICE_RESET = 2, /* USB device reset */
+ NOTIFY_USB_DEVICE_ERROR = 3, /* USB device error */
+};
+
+/*
+ * USB subsystem notification record.
+ * - watch.type = WATCH_TYPE_USB_NOTIFY
+ * - watch.subtype = enum usb_notification_type
+ */
+struct usb_notification {
+ struct watch_notification watch; /* WATCH_TYPE_USB_NOTIFY */
+ __u32 error;
+ __u32 reserved;
+ __u8 name_len; /* Length of device name */
+ __u8 name[0]; /* Device name (padded to __u64, truncated at 63 chars) */
+};
+
+#define USB_NOTIFICATION_MAX_NAME_LEN 63
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/samples/watch_queue/watch_test.c b/samples/watch_queue/watch_test.c
index 55879531a7d5..37461295e825 100644
--- a/samples/watch_queue/watch_test.c
+++ b/samples/watch_queue/watch_test.c
@@ -84,6 +84,26 @@ static void saw_block_change(struct watch_notification *n, size_t len)
(unsigned long long)b->sector);
}
+static const char *usb_subtypes[256] = {
+ [NOTIFY_USB_DEVICE_ADD] = "dev-add",
+ [NOTIFY_USB_DEVICE_REMOVE] = "dev-remove",
+ [NOTIFY_USB_DEVICE_RESET] = "dev-reset",
+ [NOTIFY_USB_DEVICE_ERROR] = "dev-error",
+};
+
+static void saw_usb_event(struct watch_notification *n, size_t len)
+{
+ struct usb_notification *u = (struct usb_notification *)n;
+
+ if (len < sizeof(struct usb_notification))
+ return;
+
+ printf("USB %*.*s %s e=%x r=%x\n",
+ u->name_len, u->name_len, u->name,
+ usb_subtypes[n->subtype],
+ u->error, u->reserved);
+}
+
/*
* Consume and display events.
*/
@@ -160,6 +180,9 @@ static void consumer(int fd)
case WATCH_TYPE_BLOCK_NOTIFY:
saw_block_change(&n.n, len);
break;
+ case WATCH_TYPE_USB_NOTIFY:
+ saw_usb_event(&n.n, len);
+ break;
default:
printf("other type\n");
break;
@@ -171,7 +194,7 @@ static void consumer(int fd)
}
static struct watch_notification_filter filter = {
- .nr_filters = 2,
+ .nr_filters = 3,
.filters = {
[0] = {
.type = WATCH_TYPE_KEY_NOTIFY,
@@ -181,6 +204,10 @@ static struct watch_notification_filter filter = {
.type = WATCH_TYPE_BLOCK_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
+ [2] = {
+ .type = WATCH_TYPE_USB_NOTIFY,
+ .subtype_filter[0] = UINT_MAX,
+ },
},
};
^ permalink raw reply related
* [RFC PATCH 13/14] selinux: Implement the watch_key security hook [ver #2]
From: David Howells @ 2019-11-07 13:37 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
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 <sds@tycho.nsa.gov>
---
security/selinux/hooks.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 9625b99e677f..53637dccee00 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6579,6 +6579,17 @@ static int selinux_key_getsecurity(struct key *key, char **_buffer)
*_buffer = context;
return rc;
}
+
+#ifdef CONFIG_KEY_NOTIFICATIONS
+static int selinux_watch_key(struct key *key)
+{
+ struct key_security_struct *ksec = key->security;
+ u32 sid = current_sid();
+
+ return avc_has_perm(&selinux_state,
+ sid, ksec->sid, SECCLASS_KEY, KEY_NEED_VIEW, NULL);
+}
+#endif
#endif
#ifdef CONFIG_SECURITY_INFINIBAND
@@ -7012,6 +7023,9 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(key_free, selinux_key_free),
LSM_HOOK_INIT(key_permission, selinux_key_permission),
LSM_HOOK_INIT(key_getsecurity, selinux_key_getsecurity),
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ LSM_HOOK_INIT(watch_key, selinux_watch_key),
+#endif
#endif
#ifdef CONFIG_AUDIT
^ permalink raw reply related
* [RFC PATCH 14/14] smack: Implement the watch_key and post_notification hooks [ver #2]
From: David Howells @ 2019-11-07 13:37 UTC (permalink / raw)
To: torvalds
Cc: dhowells, Greg Kroah-Hartman, Casey Schaufler, Stephen Smalley,
nicolas.dichtel, raven, Christian Brauner
In-Reply-To: <157313371694.29677.15388731274912671071.stgit@warthog.procyon.org.uk>
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 | 82 +++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 82 insertions(+), 1 deletion(-)
diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index 915330abf6e5..734d67889826 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -74,6 +74,7 @@ struct common_audit_data {
#define LSM_AUDIT_DATA_FILE 12
#define LSM_AUDIT_DATA_IBPKEY 13
#define LSM_AUDIT_DATA_IBENDPORT 14
+#define LSM_AUDIT_DATA_NOTIFICATION 15
union {
struct path path;
struct dentry *dentry;
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index ecea41ce919b..71b6f37d49c1 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -4273,7 +4273,7 @@ static int smack_key_permission(key_ref_t key_ref,
if (tkp == NULL)
return -EACCES;
- if (smack_privileged_cred(CAP_MAC_OVERRIDE, cred))
+ if (smack_privileged(CAP_MAC_OVERRIDE))
return 0;
#ifdef CONFIG_AUDIT
@@ -4319,8 +4319,81 @@ static int smack_key_getsecurity(struct key *key, char **_buffer)
return length;
}
+
+#ifdef CONFIG_KEY_NOTIFICATIONS
+/**
+ * smack_watch_key - Smack access to watch a key for notifications.
+ * @key: The key to be watched
+ *
+ * Return 0 if the @watch->cred has permission to read from the key object and
+ * an error otherwise.
+ */
+static int smack_watch_key(struct key *key)
+{
+ struct smk_audit_info ad;
+ struct smack_known *tkp = smk_of_current();
+ int rc;
+
+ if (key == NULL)
+ return -EINVAL;
+ /*
+ * If the key hasn't been initialized give it access so that
+ * it may do so.
+ */
+ if (key->security == NULL)
+ return 0;
+ /*
+ * This should not occur
+ */
+ if (tkp == NULL)
+ return -EACCES;
+
+ if (smack_privileged_cred(CAP_MAC_OVERRIDE, current_cred()))
+ return 0;
+
+#ifdef CONFIG_AUDIT
+ smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_KEY);
+ ad.a.u.key_struct.key = key->serial;
+ ad.a.u.key_struct.key_desc = key->description;
+#endif
+ rc = smk_access(tkp, key->security, MAY_READ, &ad);
+ rc = smk_bu_note("key watch", tkp, key->security, MAY_READ, rc);
+ return rc;
+}
+#endif /* CONFIG_KEY_NOTIFICATIONS */
#endif /* CONFIG_KEYS */
+#ifdef CONFIG_WATCH_QUEUE
+/**
+ * smack_post_notification - Smack access to post a notification to a queue
+ * @w_cred: The credentials of the watcher.
+ * @cred: The credentials of the event source (may be NULL).
+ * @n: The notification message to be posted.
+ */
+static int smack_post_notification(const struct cred *w_cred,
+ const struct cred *cred,
+ struct watch_notification *n)
+{
+ struct smk_audit_info ad;
+ struct smack_known *subj, *obj;
+ int rc;
+
+ /* Always let maintenance notifications through. */
+ if (n->type == WATCH_TYPE_META)
+ return 0;
+
+ if (!cred)
+ return 0;
+ subj = smk_of_task(smack_cred(cred));
+ obj = smk_of_task(smack_cred(w_cred));
+
+ smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NOTIFICATION);
+ rc = smk_access(subj, obj, MAY_WRITE, &ad);
+ rc = smk_bu_note("notification", subj, obj, MAY_WRITE, rc);
+ return rc;
+}
+#endif /* CONFIG_WATCH_QUEUE */
+
/*
* Smack Audit hooks
*
@@ -4709,8 +4782,15 @@ static struct security_hook_list smack_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(key_free, smack_key_free),
LSM_HOOK_INIT(key_permission, smack_key_permission),
LSM_HOOK_INIT(key_getsecurity, smack_key_getsecurity),
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ LSM_HOOK_INIT(watch_key, smack_watch_key),
+#endif
#endif /* CONFIG_KEYS */
+#ifdef CONFIG_WATCH_QUEUE
+ LSM_HOOK_INIT(post_notification, smack_post_notification),
+#endif
+
/* Audit hooks */
#ifdef CONFIG_AUDIT
LSM_HOOK_INIT(audit_rule_init, smack_audit_rule_init),
^ permalink raw reply related
* Re: Continuing the UAPI split
From: Florian Weimer @ 2019-11-07 13:47 UTC (permalink / raw)
To: Christian Brauner; +Cc: Elichai Turkel, linux-api, libc-alpha
In-Reply-To: <20191107133652.lqp5cqcdtwu22ibd@wittgenstein>
* Christian Brauner:
> A problem I recently ran into that is related are problems with
> sys/wait.h and linux/wait.h.
> How P_{PID,PGID,PIDFD} used by the waitid() syscall are defined is
> different for the two headers.
> linux/wait.h uses #define for P_{PID,PGID,PIDFD} whereas sys/wait.h
> uses an enum.
> The problem is that if I simply don't rely on sys/wait.h and just do
> #ifndef P_PID
> #define P_PID <value>
> #endif
> where value is what the syscall expects then technically I need to call
> the waitid() syscall directly since it's not at all guaranteed - afaict
> - that the P_PID enum == P_PID define that glibc uses for its waitid()
> syscall wrapper.
Right, and this is where POSIX mandates that there is a type idtype_t
which is an enum, an that it has members P_PID etc.
What we could do is:
typedef enum
{
P_ALL, /* Wait for any child. */
#define P_ALL P_ALL
P_PID, /* Wait for specified process. */
#define P_PID P_PID
P_PGID /* Wait for members of process group. */
#define P_PGID P_PGID
} idtype_t;
The other header can then use #ifdef. You'll see that pattern in some
cases already.
But that will only work if you include glibc headers first. The generic
approach uses some shared macro for the coordination so that things work
both ways.
The other issue here is that it gets rather iffy from a language point
of view if the kernel wants to add additional constants and glibc has
still the old idtype_t definition.
> So I'm now in a scenario where I can't call the glibc wrapper for
> waitid() with the linux/wait.h defines and I can't call the syscall
> directly (e.g. because I want to make use of the syscall's rusage
> argument that glibc doesn't expose) with sys/wait.h's P_PID enum.
> I'm not sure what the right solution is here...
Yes, it's a hard problem. waitid is particularly annoying because POSIX
and the kernel have such different function prototypes. We could
perhaps expose the actual waitid system call under a different name, and
use int for the ID type parameter. But that needs someone to write a
patch. (My efforts to add syscall wrappers have stalled unfortunately.)
Thanks,
Florian
^ permalink raw reply
* [PATCH v7 0/1] ns: introduce binfmt_misc namespace
From: Laurent Vivier @ 2019-11-07 14:03 UTC (permalink / raw)
To: linux-kernel
Cc: Dmitry Safonov, Henning Schild, linux-fsdevel, James Bottomley,
Eric Biederman, linux-api, Andrei Vagin, Cédric Le Goater,
Greg Kurz, Jann Horn, containers, Alexander Viro, Jan Kiszka,
Laurent Vivier
v7: Use the new mount API
Replace
static struct dentry *bm_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
struct user_namespace *ns = current_user_ns();
return mount_ns(fs_type, flags, data, ns, ns,
bm_fill_super);
}
by
static void bm_free(struct fs_context *fc)
{
if (fc->s_fs_info)
put_user_ns(fc->s_fs_info);
}
static int bm_get_tree(struct fs_context *fc)
{
return get_tree_keyed(fc, bm_fill_super, get_user_ns(fc->user_ns));
}
static const struct fs_context_operations bm_context_ops = {
.free = bm_free,
.get_tree = bm_get_tree,
};
static int bm_init_fs_context(struct fs_context *fc)
{
fc->ops = &bm_context_ops;
return 0;
}
v6: Return &init_binfmt_ns instead of NULL in binfmt_ns()
This should never happen, but to stay safe return a
value we can use.
change subject from "RFC" to "PATCH"
v5: Use READ_ONCE()/WRITE_ONCE()
move mount pointer struct init to bm_fill_super() and add smp_wmb()
remove useless NULL value init
add WARN_ON_ONCE()
v4: first user namespace is initialized with &init_binfmt_ns,
all new user namespaces are initialized with a NULL and use
the one of the first parent that is not NULL. The pointer
is initialized to a valid value the first time the binfmt_misc
fs is mounted in the current user namespace.
This allows to not change the way it was working before:
new ns inherits values from its parent, and if parent value is modified
(or parent creates its own binfmt entry by mounting the fs) child
inherits it (unless it has itself mounted the fs).
v3: create a structure to store binfmt_misc data,
add a pointer to this structure in the user_namespace structure,
in init_user_ns structure this pointer points to an init_binfmt_ns
structure. And all new user namespaces point to this init structure.
A new binfmt namespace structure is allocated if the binfmt_misc
filesystem is mounted in a user namespace that is not the initial
one but its binfmt namespace pointer points to the initial one.
add override_creds()/revert_creds() around open_exec() in
bm_register_write()
v2: no new namespace, binfmt_misc data are now part of
the mount namespace
I put this in mount namespace instead of user namespace
because the mount namespace is already needed and
I don't want to force to have the user namespace for that.
As this is a filesystem, it seems logic to have it here.
This allows to define a new interpreter for each new container.
But the main goal is to be able to chroot to a directory
using a binfmt_misc interpreter without being root.
I have a modified version of unshare at:
https://github.com/vivier/util-linux.git branch unshare-chroot
with some new options to unshare binfmt_misc namespace and to chroot
to a directory.
If you have a directory /chroot/powerpc/jessie containing debian for powerpc
binaries and a qemu-ppc interpreter, you can do for instance:
$ uname -a
Linux fedora28-wor-2 4.19.0-rc5+ #18 SMP Mon Oct 1 00:32:34 CEST 2018 x86_64 x86_64 x86_64 GNU/Linux
$ ./unshare --map-root-user --fork --pid \
--load-interp ":qemu-ppc:M::\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff:/qemu-ppc:OC" \
--root=/chroot/powerpc/jessie /bin/bash -l
# uname -a
Linux fedora28-wor-2 4.19.0-rc5+ #18 SMP Mon Oct 1 00:32:34 CEST 2018 ppc GNU/Linux
# id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
# ls -l
total 5940
drwxr-xr-x. 2 nobody nogroup 4096 Aug 12 00:58 bin
drwxr-xr-x. 2 nobody nogroup 4096 Jun 17 20:26 boot
drwxr-xr-x. 4 nobody nogroup 4096 Aug 12 00:08 dev
drwxr-xr-x. 42 nobody nogroup 4096 Sep 28 07:25 etc
drwxr-xr-x. 3 nobody nogroup 4096 Sep 28 07:25 home
drwxr-xr-x. 9 nobody nogroup 4096 Aug 12 00:58 lib
drwxr-xr-x. 2 nobody nogroup 4096 Aug 12 00:08 media
drwxr-xr-x. 2 nobody nogroup 4096 Aug 12 00:08 mnt
drwxr-xr-x. 3 nobody nogroup 4096 Aug 12 13:09 opt
dr-xr-xr-x. 143 nobody nogroup 0 Sep 30 23:02 proc
-rwxr-xr-x. 1 nobody nogroup 6009712 Sep 28 07:22 qemu-ppc
drwx------. 3 nobody nogroup 4096 Aug 12 12:54 root
drwxr-xr-x. 3 nobody nogroup 4096 Aug 12 00:08 run
drwxr-xr-x. 2 nobody nogroup 4096 Aug 12 00:58 sbin
drwxr-xr-x. 2 nobody nogroup 4096 Aug 12 00:08 srv
drwxr-xr-x. 2 nobody nogroup 4096 Apr 6 2015 sys
drwxrwxrwt. 2 nobody nogroup 4096 Sep 28 10:31 tmp
drwxr-xr-x. 10 nobody nogroup 4096 Aug 12 00:08 usr
drwxr-xr-x. 11 nobody nogroup 4096 Aug 12 00:08 var
If you want to use the qemu binary provided by your distro, you can use
--load-interp ":qemu-ppc:M::\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff:/bin/qemu-ppc-static:OCF"
With the 'F' flag, qemu-ppc-static will be then loaded from the main root
filesystem before switching to the chroot.
Another example is to use the 'P' flag in one chroot and not in another one (useful in a test
environment to test different configurations of the same interpreter):
./unshare --fork --pid --mount-proc --map-root-user --load-interp ":qemu-ppc:M::\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff://usr/bin/qemu-ppc-noargv0:OCF" --root=/chroot/powerpc/jessie /bin/bash -l
root@localhost:/# sh -c 'echo $0'
/bin/sh
./unshare --fork --pid --mount-proc --map-root-user --load-interp ":qemu-ppc:M::\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff://usr/bin/qemu-ppc-argv0:OCFP" --root=/chroot/powerpc/jessie /bin/bash -l
root@localhost:/# sh -c 'echo $0'
sh
Laurent Vivier (1):
ns: add binfmt_misc to the user namespace
fs/binfmt_misc.c | 115 +++++++++++++++++++++++++--------
include/linux/user_namespace.h | 15 +++++
kernel/user.c | 14 ++++
kernel/user_namespace.c | 3 +
4 files changed, 119 insertions(+), 28 deletions(-)
--
2.21.0
^ permalink raw reply
* [PATCH v7 1/1] ns: add binfmt_misc to the user namespace
From: Laurent Vivier @ 2019-11-07 14:03 UTC (permalink / raw)
To: linux-kernel
Cc: Dmitry Safonov, Henning Schild, linux-fsdevel, James Bottomley,
Eric Biederman, linux-api, Andrei Vagin, Cédric Le Goater,
Greg Kurz, Jann Horn, containers, Alexander Viro, Jan Kiszka,
Laurent Vivier
In-Reply-To: <20191107140304.8426-1-laurent@vivier.eu>
This patch allows to have a different binfmt_misc configuration
for each new user namespace. By default, the binfmt_misc configuration
is the one of the previous level, but if the binfmt_misc filesystem is
mounted in the new namespace a new empty binfmt instance is created and
used in this namespace.
For instance, using "unshare" we can start a chroot of another
architecture and configure the binfmt_misc interpreter without being root
to run the binaries in this chroot.
Signed-off-by: Laurent Vivier <laurent@vivier.eu>
Acked-by: Andrei Vagin <avagin@gmail.com>
---
fs/binfmt_misc.c | 115 +++++++++++++++++++++++++--------
include/linux/user_namespace.h | 15 +++++
kernel/user.c | 14 ++++
kernel/user_namespace.c | 3 +
4 files changed, 119 insertions(+), 28 deletions(-)
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
index cdb45829354d..ba5f0d2ade96 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -40,9 +40,6 @@ enum {
VERBOSE_STATUS = 1 /* make it zero to save 400 bytes kernel memory */
};
-static LIST_HEAD(entries);
-static int enabled = 1;
-
enum {Enabled, Magic};
#define MISC_FMT_PRESERVE_ARGV0 (1 << 31)
#define MISC_FMT_OPEN_BINARY (1 << 30)
@@ -62,10 +59,7 @@ typedef struct {
struct file *interp_file;
} Node;
-static DEFINE_RWLOCK(entries_lock);
static struct file_system_type bm_fs_type;
-static struct vfsmount *bm_mnt;
-static int entry_count;
/*
* Max length of the register string. Determined by:
@@ -82,18 +76,37 @@ static int entry_count;
*/
#define MAX_REGISTER_LENGTH 1920
+static struct binfmt_namespace *binfmt_ns(struct user_namespace *ns)
+{
+ struct binfmt_namespace *b_ns;
+
+ while (ns) {
+ b_ns = READ_ONCE(ns->binfmt_ns);
+ if (b_ns)
+ return b_ns;
+ ns = ns->parent;
+ }
+ /* as the first user namespace is initialized with
+ * &init_binfmt_ns we should never come here
+ * but we try to stay safe by logging a warning
+ * and returning a sane value
+ */
+ WARN_ON_ONCE(1);
+ return &init_binfmt_ns;
+}
+
/*
* Check if we support the binfmt
* if we do, return the node, else NULL
* locking is done in load_misc_binary
*/
-static Node *check_file(struct linux_binprm *bprm)
+static Node *check_file(struct binfmt_namespace *ns, struct linux_binprm *bprm)
{
char *p = strrchr(bprm->interp, '.');
struct list_head *l;
/* Walk all the registered handlers. */
- list_for_each(l, &entries) {
+ list_for_each(l, &ns->entries) {
Node *e = list_entry(l, Node, list);
char *s;
int j;
@@ -135,17 +148,18 @@ static int load_misc_binary(struct linux_binprm *bprm)
struct file *interp_file = NULL;
int retval;
int fd_binary = -1;
+ struct binfmt_namespace *ns = binfmt_ns(current_user_ns());
retval = -ENOEXEC;
- if (!enabled)
+ if (!ns->enabled)
return retval;
/* to keep locking time low, we copy the interpreter string */
- read_lock(&entries_lock);
- fmt = check_file(bprm);
+ read_lock(&ns->entries_lock);
+ fmt = check_file(ns, bprm);
if (fmt)
dget(fmt->dentry);
- read_unlock(&entries_lock);
+ read_unlock(&ns->entries_lock);
if (!fmt)
return retval;
@@ -611,19 +625,19 @@ static void bm_evict_inode(struct inode *inode)
kfree(e);
}
-static void kill_node(Node *e)
+static void kill_node(struct binfmt_namespace *ns, Node *e)
{
struct dentry *dentry;
- write_lock(&entries_lock);
+ write_lock(&ns->entries_lock);
list_del_init(&e->list);
- write_unlock(&entries_lock);
+ write_unlock(&ns->entries_lock);
dentry = e->dentry;
drop_nlink(d_inode(dentry));
d_drop(dentry);
dput(dentry);
- simple_release_fs(&bm_mnt, &entry_count);
+ simple_release_fs(&ns->bm_mnt, &ns->entry_count);
}
/* /<entry> */
@@ -653,6 +667,9 @@ static ssize_t bm_entry_write(struct file *file, const char __user *buffer,
struct dentry *root;
Node *e = file_inode(file)->i_private;
int res = parse_command(buffer, count);
+ struct binfmt_namespace *ns;
+
+ ns = binfmt_ns(file->f_path.dentry->d_sb->s_user_ns);
switch (res) {
case 1:
@@ -669,7 +686,7 @@ static ssize_t bm_entry_write(struct file *file, const char __user *buffer,
inode_lock(d_inode(root));
if (!list_empty(&e->list))
- kill_node(e);
+ kill_node(ns, e);
inode_unlock(d_inode(root));
break;
@@ -695,6 +712,7 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,
struct inode *inode;
struct super_block *sb = file_inode(file)->i_sb;
struct dentry *root = sb->s_root, *dentry;
+ struct binfmt_namespace *ns;
int err = 0;
e = create_entry(buffer, count);
@@ -718,7 +736,9 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,
if (!inode)
goto out2;
- err = simple_pin_fs(&bm_fs_type, &bm_mnt, &entry_count);
+ ns = binfmt_ns(file->f_path.dentry->d_sb->s_user_ns);
+ err = simple_pin_fs(&bm_fs_type, &ns->bm_mnt,
+ &ns->entry_count);
if (err) {
iput(inode);
inode = NULL;
@@ -727,12 +747,16 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,
if (e->flags & MISC_FMT_OPEN_FILE) {
struct file *f;
+ const struct cred *old_cred;
+ old_cred = override_creds(file->f_cred);
f = open_exec(e->interpreter);
+ revert_creds(old_cred);
if (IS_ERR(f)) {
err = PTR_ERR(f);
pr_notice("register: failed to install interpreter file %s\n", e->interpreter);
- simple_release_fs(&bm_mnt, &entry_count);
+ simple_release_fs(&ns->bm_mnt,
+ &ns->entry_count);
iput(inode);
inode = NULL;
goto out2;
@@ -745,9 +769,9 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,
inode->i_fop = &bm_entry_operations;
d_instantiate(dentry, inode);
- write_lock(&entries_lock);
- list_add(&e->list, &entries);
- write_unlock(&entries_lock);
+ write_lock(&ns->entries_lock);
+ list_add(&e->list, &ns->entries);
+ write_unlock(&ns->entries_lock);
err = 0;
out2:
@@ -772,7 +796,9 @@ static const struct file_operations bm_register_operations = {
static ssize_t
bm_status_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
- char *s = enabled ? "enabled\n" : "disabled\n";
+ struct binfmt_namespace *ns =
+ binfmt_ns(file->f_path.dentry->d_sb->s_user_ns);
+ char *s = ns->enabled ? "enabled\n" : "disabled\n";
return simple_read_from_buffer(buf, nbytes, ppos, s, strlen(s));
}
@@ -780,25 +806,28 @@ bm_status_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
static ssize_t bm_status_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
+ struct binfmt_namespace *ns;
int res = parse_command(buffer, count);
struct dentry *root;
+ ns = binfmt_ns(file->f_path.dentry->d_sb->s_user_ns);
switch (res) {
case 1:
/* Disable all handlers. */
- enabled = 0;
+ ns->enabled = 0;
break;
case 2:
/* Enable all handlers. */
- enabled = 1;
+ ns->enabled = 1;
break;
case 3:
/* Delete all handlers. */
root = file_inode(file)->i_sb->s_root;
inode_lock(d_inode(root));
- while (!list_empty(&entries))
- kill_node(list_first_entry(&entries, Node, list));
+ while (!list_empty(&ns->entries))
+ kill_node(ns, list_first_entry(&ns->entries,
+ Node, list));
inode_unlock(d_inode(root));
break;
@@ -825,24 +854,53 @@ static const struct super_operations s_ops = {
static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
{
int err;
+ struct user_namespace *ns = sb->s_user_ns;
static const struct tree_descr bm_files[] = {
[2] = {"status", &bm_status_operations, S_IWUSR|S_IRUGO},
[3] = {"register", &bm_register_operations, S_IWUSR},
/* last one */ {""}
};
+ /* create a new binfmt namespace
+ * if we are not in the first user namespace
+ * but the binfmt namespace is the first one
+ */
+ if (READ_ONCE(ns->binfmt_ns) == NULL) {
+ struct binfmt_namespace *new_ns;
+
+ new_ns = kmalloc(sizeof(struct binfmt_namespace),
+ GFP_KERNEL);
+ if (new_ns == NULL)
+ return -ENOMEM;
+ INIT_LIST_HEAD(&new_ns->entries);
+ new_ns->enabled = 1;
+ rwlock_init(&new_ns->entries_lock);
+ new_ns->bm_mnt = NULL;
+ new_ns->entry_count = 0;
+ /* ensure new_ns is completely initialized before sharing it */
+ smp_wmb();
+ WRITE_ONCE(ns->binfmt_ns, new_ns);
+ }
+
err = simple_fill_super(sb, BINFMTFS_MAGIC, bm_files);
if (!err)
sb->s_op = &s_ops;
return err;
}
+static void bm_free(struct fs_context *fc)
+{
+ if (fc->s_fs_info)
+ put_user_ns(fc->s_fs_info);
+}
+
static int bm_get_tree(struct fs_context *fc)
{
- return get_tree_single(fc, bm_fill_super);
+ return get_tree_keyed(fc, bm_fill_super, get_user_ns(fc->user_ns));
}
static const struct fs_context_operations bm_context_ops = {
+ .free = bm_free,
.get_tree = bm_get_tree,
};
@@ -861,6 +919,7 @@ static struct file_system_type bm_fs_type = {
.owner = THIS_MODULE,
.name = "binfmt_misc",
.init_fs_context = bm_init_fs_context,
+ .fs_flags = FS_USERNS_MOUNT,
.kill_sb = kill_litter_super,
};
MODULE_ALIAS_FS("binfmt_misc");
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index fb9f4f799554..16e6f3a97a01 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -52,6 +52,18 @@ enum ucount_type {
UCOUNT_COUNTS,
};
+#if IS_ENABLED(CONFIG_BINFMT_MISC)
+struct binfmt_namespace {
+ struct list_head entries;
+ rwlock_t entries_lock;
+ int enabled;
+ struct vfsmount *bm_mnt;
+ int entry_count;
+} __randomize_layout;
+
+extern struct binfmt_namespace init_binfmt_ns;
+#endif
+
struct user_namespace {
struct uid_gid_map uid_map;
struct uid_gid_map gid_map;
@@ -86,6 +98,9 @@ struct user_namespace {
#endif
struct ucounts *ucounts;
int ucount_max[UCOUNT_COUNTS];
+#if IS_ENABLED(CONFIG_BINFMT_MISC)
+ struct binfmt_namespace *binfmt_ns;
+#endif
} __randomize_layout;
struct ucounts {
diff --git a/kernel/user.c b/kernel/user.c
index 5235d7f49982..092b2b4d47a6 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -20,6 +20,17 @@
#include <linux/user_namespace.h>
#include <linux/proc_ns.h>
+#if IS_ENABLED(CONFIG_BINFMT_MISC)
+struct binfmt_namespace init_binfmt_ns = {
+ .entries = LIST_HEAD_INIT(init_binfmt_ns.entries),
+ .enabled = 1,
+ .entries_lock = __RW_LOCK_UNLOCKED(init_binfmt_ns.entries_lock),
+ .bm_mnt = NULL,
+ .entry_count = 0,
+};
+EXPORT_SYMBOL_GPL(init_binfmt_ns);
+#endif
+
/*
* userns count is 1 for root user, 1 for init_uts_ns,
* and 1 for... ?
@@ -67,6 +78,9 @@ struct user_namespace init_user_ns = {
.keyring_name_list = LIST_HEAD_INIT(init_user_ns.keyring_name_list),
.keyring_sem = __RWSEM_INITIALIZER(init_user_ns.keyring_sem),
#endif
+#if IS_ENABLED(CONFIG_BINFMT_MISC)
+ .binfmt_ns = &init_binfmt_ns,
+#endif
};
EXPORT_SYMBOL_GPL(init_user_ns);
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 8eadadc478f9..f42c32269e20 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -191,6 +191,9 @@ static void free_user_ns(struct work_struct *work)
kfree(ns->projid_map.forward);
kfree(ns->projid_map.reverse);
}
+#if IS_ENABLED(CONFIG_BINFMT_MISC)
+ kfree(ns->binfmt_ns);
+#endif
retire_userns_sysctls(ns);
key_free_user_ns(ns);
ns_free_inum(&ns->ns);
--
2.21.0
^ permalink raw reply related
* Re: Continuing the UAPI split
From: Christian Brauner @ 2019-11-07 14:05 UTC (permalink / raw)
To: Florian Weimer; +Cc: Elichai Turkel, linux-api, libc-alpha, dhowells
In-Reply-To: <87v9rvhk6t.fsf@oldenburg2.str.redhat.com>
On Thu, Nov 07, 2019 at 02:47:54PM +0100, Florian Weimer wrote:
> * Christian Brauner:
>
> > A problem I recently ran into that is related are problems with
> > sys/wait.h and linux/wait.h.
> > How P_{PID,PGID,PIDFD} used by the waitid() syscall are defined is
> > different for the two headers.
> > linux/wait.h uses #define for P_{PID,PGID,PIDFD} whereas sys/wait.h
> > uses an enum.
> > The problem is that if I simply don't rely on sys/wait.h and just do
> > #ifndef P_PID
> > #define P_PID <value>
> > #endif
> > where value is what the syscall expects then technically I need to call
> > the waitid() syscall directly since it's not at all guaranteed - afaict
> > - that the P_PID enum == P_PID define that glibc uses for its waitid()
> > syscall wrapper.
>
> Right, and this is where POSIX mandates that there is a type idtype_t
> which is an enum, an that it has members P_PID etc.
>
> What we could do is:
>
> typedef enum
> {
> P_ALL, /* Wait for any child. */
> #define P_ALL P_ALL
> P_PID, /* Wait for specified process. */
> #define P_PID P_PID
> P_PGID /* Wait for members of process group. */
> #define P_PGID P_PGID
> } idtype_t;
Right, that sounds feasible.
(Nit/slightly off-topic: David (forgot to Cc him...) once reminded me
that we do prefer to explicitly set the enum value so that there are no
accidental changes, e.g.:
typedef enum
{
P_ALL = 0, /* Wait for any child. */
#define P_ALL P_ALL
P_PID = 1, /* Wait for specified process. */
#define P_PID P_PID
P_PGID = 2, /* Wait for members of process group. */
#define P_PGID P_PGID
P_PIDFD = 3, /* Wait via pidfds. */
#define P_PIDFD P_PIDFD
} idtype_t;
)
>
> The other header can then use #ifdef. You'll see that pattern in some
> cases already.
>
> But that will only work if you include glibc headers first. The generic
> approach uses some shared macro for the coordination so that things work
> both ways.
We saw a conflict at least on Fedora for the pidfd-tests with the new
P_PIDFD type defined in linux/wait.h but not yet in sys/wait.h and it
was exactly caused by wrong inclusion order. :)
>
> The other issue here is that it gets rather iffy from a language point
> of view if the kernel wants to add additional constants and glibc has
> still the old idtype_t definition.
>
> > So I'm now in a scenario where I can't call the glibc wrapper for
> > waitid() with the linux/wait.h defines and I can't call the syscall
> > directly (e.g. because I want to make use of the syscall's rusage
> > argument that glibc doesn't expose) with sys/wait.h's P_PID enum.
> > I'm not sure what the right solution is here...
>
> Yes, it's a hard problem. waitid is particularly annoying because POSIX
> and the kernel have such different function prototypes. We could
> perhaps expose the actual waitid system call under a different name, and
Wouldn't be the worst idea.
> use int for the ID type parameter. But that needs someone to write a
> patch. (My efforts to add syscall wrappers have stalled unfortunately.)
This could be a good Google Summer of Code or Outreachy project or
something tbh. In general, some more noise around this would probably
help. Especially with the ability to use Gerrit to submit patches that
might make it easier for people to contribute...
Do we officially document somewhere how to add glibc syscall wrappers?
Christian
^ permalink raw reply
* Re: For review: documentation of clone3() system call
From: Christian Brauner @ 2019-11-07 15:19 UTC (permalink / raw)
To: Michael Kerrisk (man-pages), Florian Weimer
Cc: Christian Brauner, lkml, linux-man, Kees Cook, Oleg Nesterov,
Arnd Bergmann, David Howells, Pavel Emelyanov, Andrew Morton,
Adrian Reber, Andrei Vagin, Linux API, Jann Horn
In-Reply-To: <CAKgNAkjo2WHq+zESU1iuCHJJ0x-fTNrakS9-d1+BjzUuV2uf2Q@mail.gmail.com>
On Fri, Oct 25, 2019 at 06:59:31PM +0200, Michael Kerrisk (man-pages) wrote:
> Hello Christian and all,
>
> I've made a first shot at adding documentation for clone3(). You can
> see the diff here:
> https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/commit/?id=faa0e55ae9e490d71c826546bbdef954a1800969
>
> In the end, I decided that the most straightforward approach was to
> add the documentation as part of the existing clone(2) page. This has
> the advantage of avoiding duplication of information across two pages,
> and perhaps also makes it easier to see the commonality of the two
> APIs.
>
> Because the new text is integrated into the existing page, I think it
> makes most sense to just show that page text for review purposes. I
> welcome input on the below.
>
> The notable changes are:
> * In the first part of the page, up to and including the paragraph
> with the subheading "The flags bit mask"
> * Minor changes in the description of CLONE_CHILD_CLEARTID,
> CLONE_CHILD_SETTID, CLONE_PARENT_SETTID, and CLONE_PIDFD, to reflect
> the argument differences between clone() and clone2()
(Fyi, I think you meant to write clone3()here. clone2() is specific to ia64.)
>
> Most of the resy of page is unchanged.
>
> I welcome fixes, suggestions for improvements, etc.
>
> Thanks,
>
> Michael
>
> CLONE(2) Linux Programmer's Manual CLONE(2)
>
> NAME
> clone, __clone2 - create a child process
Should this include clone3()?
>
> SYNOPSIS
> /* Prototype for the glibc wrapper function */
>
> #define _GNU_SOURCE
> #include <sched.h>
>
> int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
> /* pid_t *parent_tid, void *tls, pid_t *child_tid */ );
I've always been confused by the "..." for the glibc wrapper. The glibc
prototype in bits/sched.h also looks like this:
extern int clone (int (*__fn) (void *__arg), void *__child_stack, int __flags, void *__arg, ...) __THROW;
The additionl args parent_tid, tls, and child_tid are present in _all_
clone version in the same order. In fact the glibc wrapper here give the
illusion that it's parent_tid, tls, child_tid. The underlying syscall
has a different order parent_tidptr, child_tidptr, tls.
Florian, can you advise what prototype we should mention for the glibc
clone() wrapper here. I'd like it to be as simple as possible and get
rid of the ...
Architectural differences are explained in detail below anyway.
>
> /* For the prototype of the raw clone() system call, see NOTES */
>
> long clone3(struct clone_args *cl_args, size_t size);
>
> Note: There is not yet a glibc wrapper for clone3(); see NOTES.
>
> DESCRIPTION
> These system calls create a new process, in a manner similar to
> fork(2).
>
> Unlike fork(2), these system calls allow the child process to
> share parts of its execution context with the calling process,
Hm, sharing part of the execution context is not the only thing that
clone{3}() does. Maybe something like:
Unlike fork(2), these system calls allow to create a child process with
different properties than its parent. For example, these syscalls allow
the child to share various parts of the execution context with the
calling process such as [...]. They also allow placing the process in a
new set of namespaces.
Just a thought.
> such as the virtual address space, the table of file descriptors,
> and the table of signal handlers. (Note that on this manual page,
> "calling process" normally corresponds to "parent process". But
> see the description of CLONE_PARENT below.)
>
> This page describes the following interfaces:
>
> * The glibc clone() wrapper function and the underlying system
> call on which it is based. The main text describes the wrapper
> function; the differences for the raw system call are described
> toward the end of this page.
>
> * The newer clone3() system call.
>
> The clone() wrapper function
> When the child process is created with the clone() wrapper func‐
> tion, it commences execution by calling the function pointed to by
> the argument fn. (This differs from fork(2), where execution con‐
> tinues in the child from the point of the fork(2) call.) The arg
> argument is passed as the argument of the function fn.
>
> When the fn(arg) function returns, the child process terminates.
> The integer returned by fn is the exit status for the child
> process. The child process may also terminate explicitly by call‐
> ing exit(2) or after receiving a fatal signal.
>
> The stack argument specifies the location of the stack used by the
> child process. Since the child and calling process may share mem‐
> ory, it is not possible for the child process to execute in the
> same stack as the calling process. The calling process must
> therefore set up memory space for the child stack and pass a
> pointer to this space to clone(). Stacks grow downward on all
It might be a good idea to advise people to use mmap() to create a
stack. The "canonical" way of doing this would usually be something like
#define DEFAULT_STACK_SIZE (4 * 1024 * 1024) /* 8 MB usually on Linux */
void *stack = mmap(NULL, DEFAULT_STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
(Yes, the MAP_STACK is usally a noop but people should always include it
in case some arch will have weird alignment requirement in which case
this flag can be changed to actually do something...)
> processors that run Linux (except the HP PA processors), so stack
> usually points to the topmost address of the memory space set up
> for the child stack. Note that clone() does not provide a means
> whereby the caller can inform the kernel of the size of the stack
> area.
>
> The remaining arguments to clone() are discussed below.
>
> clone3()
> The clone3() system call provides a superset of the functionality
> of the older clone() interface. It also provides a number of API
Technically, clone3() currently provides the same functionality as
clone() it just has (hopefully) saner semantics, i.e. where as clone()
_silently_ ignores unknown options clone3() will reject them with
EINVAL (e.g. CSIGNAL and CLONE_DETACHED).
But it's good enough and will be true with v5.5
> improvements, including: space for additional flags bits; cleaner
> separation in the use of various arguments; and the ability to
> specify the size of the child's stack area.
>
> As with fork(2), clone3() returns in both the parent and the
> child. It returns 0 in the child process and returns the PID of
> the child in the parent.
>
> The cl_args argument of clone3() is a structure of the following
> form:
>
> struct clone_args {
> u64 flags; /* Flags bit mask */
> u64 pidfd; /* Where to store PID file descriptor
> (int *) */
> u64 child_tid; /* Where to store child TID,
> in child's memory (int *) */
> u64 parent_tid; /* Where to store child TID,
> in parent's memory (int *) */
> u64 exit_signal; /* Signal to deliver to parent on
> child termination */
> u64 stack; /* Pointer to lowest byte of stack */
> u64 stack_size; /* Size of stack */
> u64 tls; /* Location of new TLS */
> };
>
> The size argument that is supplied to clone3() should be initial‐
> ized to the size of this structure. (The existence of the size
> argument permits future extensions to the clone_args structure.)
>
> The stack for the child process is specified via cl_args.stack,
> which points to the lowest byte of the stack area, and
> cl_args.stack_size, which specifies the size of the stack in
> bytes. In the case where the CLONE_VM flag (see below) is speci‐
This is now actually true. :)
> fied, a stack must be explicitly allocated and specified. Other‐
> wise, these two fields can be specified as NULL and 0, which
> causes the child to use the same stack area as the parent (in the
> child's own virtual address space).
>
> The remaining fields in the cl_args argument are discussed below.
>
> Equivalence between clone() and clone3() arguments
> Unlike the older clone() interface, where arguments are passed
> individually, in the newer clone3() interface the arguments are
> packaged into the clone_args structure shown above. This struc‐
> ture allows for a superset of the information passed via the
> clone() arguments.
>
> The following table shows the equivalence between the arguments of
> clone() and the fields in the clone_args argument supplied to
> clone3():
>
> clone() clone(3) Notes
> cl_args field
> flags & ~0xff flags
CLONE_DETACHED doesn't work.
> parent_tid pidfd See CLONE_PIDFD
> child_tid child_tid See CLONE_CHILD_SETTID
> parent_tid parent_tid See CLONE_PARENT_SETTID
> flags & 0xff exit_signal
> stack stack
>
> --- stack_size
posterity: Apart from microblaze and ia64's clone2() which both have a
stack_size argument. :)
> tls tls See CLONE_SETTLS
>
> The child termination signal
> When the child process terminates, a signal may be sent to the
> parent. The termination signal is specified in the low byte of
> flags (clone()) or in cl_args.exit_signal (clone3()). If this
> signal is specified as anything other than SIGCHLD, then the par‐
> ent process must specify the __WALL or __WCLONE options when wait‐
> ing for the child with wait(2). If no signal (i.e., zero) is
> specified, then the parent process is not signaled when the child
> terminates.
>
> The flags bit mask
> Both clone() and clone3() allow a flags bit mask that modifies
> their behavior and allows the caller to specify what is shared
> between the calling process and the child process. This bit mask
> is specified as a bitwise-OR of zero or more of the constants
> listed below. Except as otherwise noted below, these flags are
> available (and have the same effect) in both clone() and clone3().
>
> CLONE_CHILD_CLEARTID (since Linux 2.5.49)
> Clear (zero) the child thread ID at the location pointed to
> by child_tid (clone()) or cl_args.child_tid (clone3()) in
> child memory when the child exits, and do a wakeup on the
> futex at that address. The address involved may be changed
> by the set_tid_address(2) system call. This is used by
> threading libraries.
>
> CLONE_CHILD_SETTID (since Linux 2.5.49)
> Store the child thread ID at the location pointed to by
> child_tid (clone()) or cl_args.child_tid (clone3()) in the
> child's memory. The store operation completes before
> clone() returns control to user space in the child process.
> (Note that the store operation may not have completed
> before clone() returns in the parent process, which will be
> relevant if the CLONE_VM flag is also employed.)
>
> CLONE_FILES (since Linux 2.0)
> If CLONE_FILES is set, the calling process and the child
> process share the same file descriptor table. Any file
> descriptor created by the calling process or by the child
> process is also valid in the other process. Similarly, if
> one of the processes closes a file descriptor, or changes
> its associated flags (using the fcntl(2) F_SETFD opera‐
> tion), the other process is also affected. If a process
> sharing a file descriptor table calls execve(2), its file
> descriptor table is duplicated (unshared).
>
> If CLONE_FILES is not set, the child process inherits a
> copy of all file descriptors opened in the calling process
> at the time of clone(). Subsequent operations that open or
> close file descriptors, or change file descriptor flags,
> performed by either the calling process or the child
> process do not affect the other process. Note, however,
> that the duplicated file descriptors in the child refer to
> the same open file descriptions as the corresponding file
> descriptors in the calling process, and thus share file
> offsets and file status flags (see open(2)).
>
> CLONE_FS (since Linux 2.0)
> If CLONE_FS is set, the caller and the child process share
> the same filesystem information. This includes the root of
> the filesystem, the current working directory, and the
> umask. Any call to chroot(2), chdir(2), or umask(2) per‐
> formed by the calling process or the child process also
> affects the other process.
>
> If CLONE_FS is not set, the child process works on a copy
> of the filesystem information of the calling process at the
> time of the clone() call. Calls to chroot(2), chdir(2), or
> umask(2) performed later by one of the processes do not
> affect the other process.
>
> CLONE_IO (since Linux 2.6.25)
> If CLONE_IO is set, then the new process shares an I/O con‐
> text with the calling process. If this flag is not set,
> then (as with fork(2)) the new process has its own I/O con‐
> text.
>
> The I/O context is the I/O scope of the disk scheduler
> (i.e., what the I/O scheduler uses to model scheduling of a
> process's I/O). If processes share the same I/O context,
> they are treated as one by the I/O scheduler. As a conse‐
> quence, they get to share disk time. For some I/O sched‐
> ulers, if two processes share an I/O context, they will be
> allowed to interleave their disk access. If several
> threads are doing I/O on behalf of the same process
> (aio_read(3), for instance), they should employ CLONE_IO to
> get better I/O performance.
>
> If the kernel is not configured with the CONFIG_BLOCK
> option, this flag is a no-op.
>
> CLONE_NEWCGROUP (since Linux 4.6)
> Create the process in a new cgroup namespace. If this flag
> is not set, then (as with fork(2)) the process is created
> in the same cgroup namespaces as the calling process. This
> flag is intended for the implementation of containers.
>
> For further information on cgroup namespaces, see
> cgroup_namespaces(7).
>
> Only a privileged process (CAP_SYS_ADMIN) can employ
> CLONE_NEWCGROUP.
>
> CLONE_NEWIPC (since Linux 2.6.19)
> If CLONE_NEWIPC is set, then create the process in a new
> IPC namespace. If this flag is not set, then (as with
> fork(2)), the process is created in the same IPC namespace
> as the calling process. This flag is intended for the
> implementation of containers.
>
> An IPC namespace provides an isolated view of System V IPC
> objects (see sysvipc(7)) and (since Linux 2.6.30) POSIX
> message queues (see mq_overview(7)). The common character‐
> istic of these IPC mechanisms is that IPC objects are iden‐
> tified by mechanisms other than filesystem pathnames.
>
> Objects created in an IPC namespace are visible to all
> other processes that are members of that namespace, but are
> not visible to processes in other IPC namespaces.
>
> When an IPC namespace is destroyed (i.e., when the last
> process that is a member of the namespace terminates), all
> IPC objects in the namespace are automatically destroyed.
>
> Only a privileged process (CAP_SYS_ADMIN) can employ
> CLONE_NEWIPC. This flag can't be specified in conjunction
> with CLONE_SYSVSEM.
>
> For further information on IPC namespaces, see names‐
> paces(7).
>
> CLONE_NEWNET (since Linux 2.6.24)
> (The implementation of this flag was completed only by
> about kernel version 2.6.29.)
>
> If CLONE_NEWNET is set, then create the process in a new
> network namespace. If this flag is not set, then (as with
> fork(2)) the process is created in the same network names‐
> pace as the calling process. This flag is intended for the
> implementation of containers.
>
> A network namespace provides an isolated view of the net‐
> working stack (network device interfaces, IPv4 and IPv6
> protocol stacks, IP routing tables, firewall rules, the
> /proc/net and /sys/class/net directory trees, sockets,
> etc.). A physical network device can live in exactly one
> network namespace. A virtual network (veth(4)) device pair
> provides a pipe-like abstraction that can be used to create
> tunnels between network namespaces, and can be used to cre‐
> ate a bridge to a physical network device in another names‐
> pace.
>
> When a network namespace is freed (i.e., when the last
> process in the namespace terminates), its physical network
> devices are moved back to the initial network namespace
> (not to the parent of the process). For further informa‐
> tion on network namespaces, see namespaces(7).
That's a lot of network namespace specific information, no? Maybe just
point to man network_namespaces?
>
> Only a privileged process (CAP_SYS_ADMIN) can employ
> CLONE_NEWNET.
>
> CLONE_NEWNS (since Linux 2.4.19)
> If CLONE_NEWNS is set, the cloned child is started in a new
> mount namespace, initialized with a copy of the namespace
> of the parent. If CLONE_NEWNS is not set, the child lives
> in the same mount namespace as the parent.
>
> Only a privileged process (CAP_SYS_ADMIN) can employ
> CLONE_NEWNS. It is not permitted to specify both
> CLONE_NEWNS and CLONE_FS in the same clone() call.
Wait, I just realized that CLONE_FS has __different__ semantics in
clone(2) than in unshare(2). That's crazy.
unshare(2)'s basically ~CLONE_FS for clone2()...
That deserves a big fat warning imho. At leats it's mentioned in the
unshare(2) manpage.
>
> For further information on mount namespaces, see names‐
> paces(7) and mount_namespaces(7).
>
> CLONE_NEWPID (since Linux 2.6.24)
> If CLONE_NEWPID is set, then create the process in a new
> PID namespace. If this flag is not set, then (as with
> fork(2)) the process is created in the same PID namespace
> as the calling process. This flag is intended for the
> implementation of containers.
>
> For further information on PID namespaces, see names‐
> paces(7) and pid_namespaces(7).
>
> Only a privileged process (CAP_SYS_ADMIN) can employ
> CLONE_NEWPID. This flag can't be specified in conjunction
> with CLONE_THREAD or CLONE_PARENT.
>
> CLONE_NEWUSER
> (This flag first became meaningful for clone() in Linux
> 2.6.23, the current clone() semantics were merged in Linux
> 3.5, and the final pieces to make the user namespaces com‐
> pletely usable were merged in Linux 3.8.)
>
> If CLONE_NEWUSER is set, then create the process in a new
> user namespace. If this flag is not set, then (as with
> fork(2)) the process is created in the same user namespace
> as the calling process.
>
> Before Linux 3.8, use of CLONE_NEWUSER required that the
> caller have three capabilities: CAP_SYS_ADMIN, CAP_SETUID,
> and CAP_SETGID. Starting with Linux 3.8, no privileges are
> needed to create a user namespace.
>
> This flag can't be specified in conjunction with
> CLONE_THREAD or CLONE_PARENT. For security reasons,
> CLONE_NEWUSER cannot be specified in conjunction with
> CLONE_FS.
>
> For further information on user namespaces, see names‐
> paces(7) and user_namespaces(7).
>
> CLONE_NEWUTS (since Linux 2.6.19)
> If CLONE_NEWUTS is set, then create the process in a new
> UTS namespace, whose identifiers are initialized by dupli‐
> cating the identifiers from the UTS namespace of the call‐
> ing process. If this flag is not set, then (as with
> fork(2)) the process is created in the same UTS namespace
> as the calling process. This flag is intended for the
> implementation of containers.
>
> A UTS namespace is the set of identifiers returned by
> uname(2); among these, the domain name and the hostname can
> be modified by setdomainname(2) and sethostname(2), respec‐
> tively. Changes made to the identifiers in a UTS namespace
> are visible to all other processes in the same namespace,
> but are not visible to processes in other UTS namespaces.
Might again be a little too detailed but that's just my opinion. :)
>
> Only a privileged process (CAP_SYS_ADMIN) can employ
> CLONE_NEWUTS.
>
> For further information on UTS namespaces, see names‐
> paces(7).
>
> CLONE_PARENT (since Linux 2.3.12)
> If CLONE_PARENT is set, then the parent of the new child
> (as returned by getppid(2)) will be the same as that of the
> calling process.
>
> If CLONE_PARENT is not set, then (as with fork(2)) the
> child's parent is the calling process.
>
> Note that it is the parent process, as returned by getp‐
> pid(2), which is signaled when the child terminates, so
> that if CLONE_PARENT is set, then the parent of the calling
> process, rather than the calling process itself, will be
> signaled.
>
> CLONE_PARENT_SETTID (since Linux 2.5.49)
> Store the child thread ID at the location pointed to by
> parent_tid (clone()) or cl_args.child_tid (clone3()) in the
> parent's memory. (In Linux 2.5.32-2.5.48 there was a flag
> CLONE_SETTID that did this.) The store operation completes
> before clone() returns control to user space.
>
> CLONE_PID (Linux 2.0 to 2.5.15)
> If CLONE_PID is set, the child process is created with the
> same process ID as the calling process. This is good for
> hacking the system, but otherwise of not much use. From
> Linux 2.3.21 onward, this flag could be specified only by
> the system boot process (PID 0). The flag disappeared com‐
> pletely from the kernel sources in Linux 2.5.16. Since
> then, the kernel silently ignores this bit if it is speci‐
> fied in flags.
He, not true anymore. :)
If Thomas' history tree is not lying to me than CLONE_PID used to be:
#define CLONE_PID 0x00001000 /* set if pid shared */
which then got replaced with
#define CLONE_IDLETASK 0x00001000 /* set if new pid should be 0
in 27568369be8c ("[PATCH] Hotplug CPU prep")
CLONE_IDLETASK itself got removed in f4205a53c8f5 ("[PATCH] sched: consolidate CLONE_IDLETASK masking")
And then CLONE_PIDFD took that bit. :)
>
> CLONE_PIDFD (since Linux 5.2)
> If this flag is specified, a PID file descriptor referring
> to the child process is allocated and placed at a specified
> location in the parent's memory. The close-on-exec flag is
> set on this new file descriptor. PID file descriptors can
> be used for the purposes described in pidfd_open(2).
>
> * When using clone3(), the PID file descriptor is placed
> at the location pointed to by cl_args.pidfd.
>
> * When using clone(), the PID file descriptor is placed at
> the location pointed to by parent_tid. Since the par‐
> ent_tid argument is used to return the PID file descrip‐
> tor, CLONE_PIDFD cannot be used with CLONE_PARENT_SETTID
> when calling clone().
>
> It is currently not possible to use this flag together with
> CLONE_THREAD. This means that the process identified by
> the PID file descriptor will always be a thread-group
> leader.
>
> For a while there was a CLONE_DETACHED flag. This flag is
> usually ignored when passed along with other flags. How‐
> ever, when passed alongside CLONE_PIDFD, an error is
> returned. This ensures that this flag can be reused for
> further PID file descriptor features in the future.
This section only applies to legacy clone(), i.e. legacy clone EINVALs
you with CLONE_DETACHED | CLONE_PIDFD whereas clone3() EINVALS you for
CLONE_DETACHED by itself.
>
> CLONE_PTRACE (since Linux 2.2)
> If CLONE_PTRACE is specified, and the calling process is
> being traced, then trace the child also (see ptrace(2)).
>
> CLONE_SETTLS (since Linux 2.5.32)
> The TLS (Thread Local Storage) descriptor is set to tls.
>
> The interpretation of tls and the resulting effect is
> architecture dependent. On x86, tls is interpreted as a
> struct user_desc * (see set_thread_area(2)). On x86-64 it
> is the new value to be set for the %fs base register (see
> the ARCH_SET_FS argument to arch_prctl(2)). On architec‐
> tures with a dedicated TLS register, it is the new value of
> that register.
Probably a gentle warning that this is a very advanced option and
usually should not be used by callers other than libraries implementing
threading or with specific use cases directly.
>
> CLONE_SIGHAND (since Linux 2.0)
> If CLONE_SIGHAND is set, the calling process and the child
> process share the same table of signal handlers. If the
> calling process or child process calls sigaction(2) to
> change the behavior associated with a signal, the behavior
> is changed in the other process as well. However, the
> calling process and child processes still have distinct
> signal masks and sets of pending signals. So, one of them
> may block or unblock signals using sigprocmask(2) without
> affecting the other process.
>
> If CLONE_SIGHAND is not set, the child process inherits a
> copy of the signal handlers of the calling process at the
> time clone() is called. Calls to sigaction(2) performed
> later by one of the processes have no effect on the other
> process.
>
> Since Linux 2.6.0, flags must also include CLONE_VM if
> CLONE_SIGHAND is specified
>
> CLONE_STOPPED (since Linux 2.6.0)
> If CLONE_STOPPED is set, then the child is initially
> stopped (as though it was sent a SIGSTOP signal), and must
> be resumed by sending it a SIGCONT signal.
>
> This flag was deprecated from Linux 2.6.25 onward, and was
> removed altogether in Linux 2.6.38. Since then, the kernel
> silently ignores it without error. Starting with Linux
> 4.6, the same bit was reused for the CLONE_NEWCGROUP flag.
>
> CLONE_SYSVSEM (since Linux 2.5.10)
> If CLONE_SYSVSEM is set, then the child and the calling
> process share a single list of System V semaphore adjust‐
> ment (semadj) values (see semop(2)). In this case, the
> shared list accumulates semadj values across all processes
> sharing the list, and semaphore adjustments are performed
> only when the last process that is sharing the list termi‐
> nates (or ceases sharing the list using unshare(2)). If
> this flag is not set, then the child has a separate semadj
> list that is initially empty.
>
> CLONE_THREAD (since Linux 2.4.0)
> If CLONE_THREAD is set, the child is placed in the same
> thread group as the calling process. To make the remainder
> of the discussion of CLONE_THREAD more readable, the term
> "thread" is used to refer to the processes within a thread
> group.
>
> Thread groups were a feature added in Linux 2.4 to support
> the POSIX threads notion of a set of threads that share a
> single PID. Internally, this shared PID is the so-called
> thread group identifier (TGID) for the thread group. Since
> Linux 2.4, calls to getpid(2) return the TGID of the call‐
> er.
>
> The threads within a group can be distinguished by their
> (system-wide) unique thread IDs (TID). A new thread's TID
> is available as the function result returned to the caller
> of clone(), and a thread can obtain its own TID using get‐
> tid(2).
>
> When a call is made to clone() without specifying
> CLONE_THREAD, then the resulting thread is placed in a new
> thread group whose TGID is the same as the thread's TID.
> This thread is the leader of the new thread group.
>
> A new thread created with CLONE_THREAD has the same parent
> process as the caller of clone() (i.e., like CLONE_PARENT),
Nit: s/i.e.,/i.e./?
> so that calls to getppid(2) return the same value for all
> of the threads in a thread group. When a CLONE_THREAD
> thread terminates, the thread that created it using clone()
> is not sent a SIGCHLD (or other termination) signal; nor
> can the status of such a thread be obtained using wait(2).
> (The thread is said to be detached.)
>
> After all of the threads in a thread group terminate the
> parent process of the thread group is sent a SIGCHLD (or
> other termination) signal.
>
> If any of the threads in a thread group performs an
> execve(2), then all threads other than the thread group
> leader are terminated, and the new program is executed in
s/is executed in/becomes the/?
> the thread group leader.
>
> If one of the threads in a thread group creates a child
> using fork(2), then any thread in the group can wait(2) for
> that child.
>
> Since Linux 2.5.35, flags must also include CLONE_SIGHAND
> if CLONE_THREAD is specified (and note that, since Linux
> 2.6.0, CLONE_SIGHAND also requires CLONE_VM to be
> included).
>
> Signal dispositions and actions are process-wide: if an
> unhandled signal is delivered to a thread, then it will
> affect (terminate, stop, continue, be ignored in) all mem‐
> bers of the thread group.
>
> Each thread has its own signal mask, as set by sigproc‐
> mask(2).
>
> A signal may be process-directed or thread-directed. A
> process-directed signal is targeted at a thread group
> (i.e., a TGID), and is delivered to an arbitrarily selected
> thread from among those that are not blocking the signal.
> A signal may be process-directed because it was generated
> by the kernel for reasons other than a hardware exception,
> or because it was sent using kill(2) or sigqueue(3). A
> thread-directed signal is targeted at (i.e., delivered to)
> a specific thread. A signal may be thread directed because
> it was sent using tgkill(2) or pthread_sigqueue(3), or
> because the thread executed a machine language instruction
> that triggered a hardware exception (e.g., invalid memory
> access triggering SIGSEGV or a floating-point exception
> triggering SIGFPE).
>
> A call to sigpending(2) returns a signal set that is the
> union of the pending process-directed signals and the sig‐
> nals that are pending for the calling thread.
>
> If a process-directed signal is delivered to a thread
> group, and the thread group has installed a handler for the
> signal, then the handler will be invoked in exactly one,
> arbitrarily selected member of the thread group that has
> not blocked the signal. If multiple threads in a group are
> waiting to accept the same signal using sigwaitinfo(2), the
> kernel will arbitrarily select one of these threads to
> receive the signal.
I won't do a deep review of the thread section now but you might want to
mention that fatal signals always take down the whole thread-group, i.e.
SIGKILL, SIGSEGV, etc...
>
> CLONE_UNTRACED (since Linux 2.5.46)
> If CLONE_UNTRACED is specified, then a tracing process can‐
> not force CLONE_PTRACE on this child process.
>
> CLONE_VFORK (since Linux 2.2)
> If CLONE_VFORK is set, the execution of the calling process
> is suspended until the child releases its virtual memory
> resources via a call to execve(2) or _exit(2) (as with
> vfork(2)).
>
> If CLONE_VFORK is not set, then both the calling process
> and the child are schedulable after the call, and an appli‐
> cation should not rely on execution occurring in any par‐
> ticular order.
>
> CLONE_VM (since Linux 2.0)
> If CLONE_VM is set, the calling process and the child
> process run in the same memory space. In particular, mem‐
> ory writes performed by the calling process or by the child
> process are also visible in the other process. Moreover,
> any memory mapping or unmapping performed with mmap(2) or
> munmap(2) by the child or calling process also affects the
> other process.
>
> If CLONE_VM is not set, the child process runs in a sepa‐
> rate copy of the memory space of the calling process at the
> time of clone(). Memory writes or file mappings/unmappings
> performed by one of the processes do not affect the other,
> as with fork(2).
>
> NOTES
> One use of these systems calls is to implement threads: multiple
> flows of control in a program that run concurrently in a shared
> address space.
>
> Glibc does not provide a wrapper for clone(3); call it using
s/clone(3)/clone(2)/?
> syscall(2).
>
> Note that the glibc clone() wrapper function makes some changes in
> the memory pointed to by stack (changes required to set the stack
> up correctly for the child) before invoking the clone() system
In essence, you can't really use the clone{3}() syscall with a stack
argument directly without having to do some assembly. User needing to
mess with stacks are well-advised to use the glibc wrapper or need to
really know what they are doing for _each_ arch they are using the
syscall on.
> call. So, in cases where clone() is used to recursively create
> children, do not use the buffer employed for the parent's stack as
> the stack of the child.
>
> C library/kernel differences
> The raw clone() system call corresponds more closely to fork(2) in
> that execution in the child continues from the point of the call.
> As such, the fn and arg arguments of the clone() wrapper function
> are omitted.
>
> Another difference for the raw clone() system call is that the
> stack argument may be NULL, in which case the child uses a dupli‐
> cate of the parent's stack. (Copy-on-write semantics ensure that
That reads misleading, I think. It seems to me what you want to say is
that the raw syscall is perfectly happy to accept a NULL stack argument
for both clone() and clone3() but that the glibc wrapper does not allow
that. So this should probably read:
In contrast to the glibc wrapper the raw clone() system call
accepts NULL as stack argument. In this case the child uses a dupli‐
cate of the parent's stack. (Copy-on-write semantics ensure that
or something similar. :)
> the child gets separate copies of stack pages when either process
> modifies the stack.) In this case, for correct operation, the
> CLONE_VM option should not be specified. (If the child shares the
> parent's memory because of the use of the CLONE_VM flag, then no
> copy-on-write duplication occurs and chaos is likely to result.)
+1 on this. This is very important to mention!
>
> The order of the arguments also differs in the raw system call,
> and there are variations in the arguments across architectures, as
> detailed in the following paragraphs.
_sigh_ don't remind me...
>
> The raw system call interface on x86-64 and some other architec‐
> tures (including sh, tile, ia-64, and alpha) is:
>
> long clone(unsigned long flags, void *stack,
> int *parent_tid, int *child_tid,
> unsigned long tls);
I wouldn't even mention clone() for ia64 anymore. It will _not_ work
correctly at all. ia64 requires stack_size as it expects the stack to be
passed pointing to the lowest address but the clone() version for ia64
does not have a stack_size argument... So the only way to get clone() to
work on ia64 is by using the ia64 specific clone2().
>
> On x86-32, and several other common architectures (including
> score, ARM, ARM 64, PA-RISC, arc, Power PC, xtensa, and MIPS), the
> order of the last two arguments is reversed:
>
> long clone(unsigned long flags, void *stack,
> int *parent_tid, unsigned long tls,
> int *child_tid);
>
> On the cris and s390 architectures, the order of the first two
> arguments is reversed:
>
> long clone(void *stack, unsigned long flags,
> int *parent_tid, int *child_tid,
> unsigned long tls);
>
> On the microblaze architecture, an additional argument is sup‐
> plied:
>
> long clone(unsigned long flags, void *stack,
> int stack_size, /* Size of stack */
> int *parent_tid, int *child_tid,
> unsigned long tls);
The additional argument is stack_size and contrary to what one would
expect _ignored_. I.e. on microblaze one still needs to pass stack
pointing to the top of the stack.
>
> blackfin, m68k, and sparc
> The argument-passing conventions on blackfin, m68k, and sparc are
> different from the descriptions above. For details, see the ker‐
> nel (and glibc) source.
>
> ia64
> On ia64, a different interface is used:
>
> int __clone2(int (*fn)(void *),
> void *stack_base, size_t stack_size,
> int flags, void *arg, ...
> /* pid_t *parent_tid, struct user_desc *tls,
> pid_t *child_tid */ );
>
> The prototype shown above is for the glibc wrapper function; for
> the system call itself, the prototype can be described as follows
> (it is identical to the clone() prototype on microblaze):
>
> long clone2(unsigned long flags, void *stack_base,
> int stack_size, /* Size of stack */
> int *parent_tid, int *child_tid,
> unsigned long tls);
>
> __clone2() operates in the same way as clone(), except that
> stack_base points to the lowest address of the child's stack area,
> and stack_size specifies the size of the stack pointed to by
> stack_base.
>
> Linux 2.4 and earlier
> In Linux 2.4 and earlier, clone() does not take arguments par‐
> ent_tid, tls, and child_tid.
>
> RETURN VALUE
> On success, the thread ID of the child process is returned in the
> caller's thread of execution. On failure, -1 is returned in the
> caller's context, no child process will be created, and errno will
> be set appropriately.
>
> ERRORS
> EAGAIN Too many processes are already running; see fork(2).
>
> EINVAL CLONE_SIGHAND was specified, but CLONE_VM was not. (Since
> Linux 2.6.0.)
>
> EINVAL CLONE_THREAD was specified, but CLONE_SIGHAND was not.
> (Since Linux 2.5.35.)
>
> EINVAL CLONE_THREAD was specified, but the current process previ‐
> ously called unshare(2) with the CLONE_NEWPID flag or used
> setns(2) to reassociate itself with a PID namespace.
>
> EINVAL Both CLONE_FS and CLONE_NEWNS were specified in flags.
>
> EINVAL (since Linux 3.9)
> Both CLONE_NEWUSER and CLONE_FS were specified in flags.
>
> EINVAL Both CLONE_NEWIPC and CLONE_SYSVSEM were specified in
> flags.
>
> EINVAL One (or both) of CLONE_NEWPID or CLONE_NEWUSER and one (or
> both) of CLONE_THREAD or CLONE_PARENT were specified in
> flags.
>
> EINVAL Returned by the glibc clone() wrapper function when fn or
> stack is specified as NULL.
>
> EINVAL CLONE_NEWIPC was specified in flags, but the kernel was not
> configured with the CONFIG_SYSVIPC and CONFIG_IPC_NS
> options.
>
> EINVAL CLONE_NEWNET was specified in flags, but the kernel was not
> configured with the CONFIG_NET_NS option.
>
> EINVAL CLONE_NEWPID was specified in flags, but the kernel was not
> configured with the CONFIG_PID_NS option.
>
> EINVAL CLONE_NEWUSER was specified in flags, but the kernel was
> not configured with the CONFIG_USER_NS option.
>
> EINVAL CLONE_NEWUTS was specified in flags, but the kernel was not
> configured with the CONFIG_UTS_NS option.
>
> EINVAL stack is not aligned to a suitable boundary for this archi‐
> tecture. For example, on aarch64, stack must be a multiple
> of 16.
If the stack was created with mmap(NULL, ...) as outlined above this
should be taken care of, I think.
>
> EINVAL CLONE_PIDFD was specified together with CLONE_DETACHED.
Should be:
EINVAL (clone3() only)
CLONE_DETACHED was specified (only with clone3()).
EINVAL (clone() only)
CLONE_PIDFD was specified together with CLONE_DETACHED
>
> EINVAL CLONE_PIDFD was specified together with CLONE_THREAD.
>
> EINVAL (clone() only)
> CLONE_PIDFD was specified together with CLONE_PARENT_SET‐
> TID.
>
> ENOMEM Cannot allocate sufficient memory to allocate a task struc‐
> ture for the child, or to copy those parts of the caller's
> context that need to be copied.
>
> ENOSPC (since Linux 3.7)
> CLONE_NEWPID was specified in flags, but the limit on the
> nesting depth of PID namespaces would have been exceeded;
> see pid_namespaces(7).
>
> ENOSPC (since Linux 4.9; beforehand EUSERS)
> CLONE_NEWUSER was specified in flags, and the call would
> cause the limit on the number of nested user namespaces to
> be exceeded. See user_namespaces(7).
>
> From Linux 3.11 to Linux 4.8, the error diagnosed in this
> case was EUSERS.
>
> ENOSPC (since Linux 4.9)
> One of the values in flags specified the creation of a new
> user namespace, but doing so would have caused the limit
> defined by the corresponding file in /proc/sys/user to be
> exceeded. For further details, see namespaces(7).
>
> EPERM CLONE_NEWCGROUP, CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS,
> CLONE_NEWPID, or CLONE_NEWUTS was specified by an unprivi‐
> leged process (process without CAP_SYS_ADMIN).
>
> EPERM CLONE_PID was specified by a process other than process 0.
> (This error occurs only on Linux 2.5.15 and earlier.)
>
> EPERM CLONE_NEWUSER was specified in flags, but either the effec‐
> tive user ID or the effective group ID of the caller does
> not have a mapping in the parent namespace (see user_names‐
> paces(7)).
>
> EPERM (since Linux 3.9)
> CLONE_NEWUSER was specified in flags and the caller is in a
> chroot environment (i.e., the caller's root directory does
> not match the root directory of the mount namespace in
> which it resides).
>
> ERESTARTNOINTR (since Linux 2.6.17)
> System call was interrupted by a signal and will be
> restarted. (This can be seen only during a trace.)
>
> EUSERS (Linux 3.11 to Linux 4.8)
> CLONE_NEWUSER was specified in flags, and the limit on the
> number of nested user namespaces would be exceeded. See
> the discussion of the ENOSPC error above.
>
> VERSIONS
> The clone3() system call first appeared in Linux 5.3.
>
> CONFORMING TO
> These system calls are Linux-specific and should not be used in
> programs intended to be portable.
>
> NOTES
> The kcmp(2) system call can be used to test whether two processes
> share various resources such as a file descriptor table, System V
> semaphore undo operations, or a virtual address space.
>
> Handlers registered using pthread_atfork(3) are not executed dur‐
> ing a call to clone().
>
> In the Linux 2.4.x series, CLONE_THREAD generally does not make
> the parent of the new thread the same as the parent of the calling
> process. However, for kernel versions 2.4.7 to 2.4.18 the
> CLONE_THREAD flag implied the CLONE_PARENT flag (as in Linux 2.6.0
> and later).
>
> For a while there was CLONE_DETACHED (introduced in 2.5.32): par‐
> ent wants no child-exit signal. In Linux 2.6.2, the need to give
> this flag together with CLONE_THREAD disappeared. This flag is
> still defined, but has no effect.
This is clone() specific and not true when passed together with
CLONE_PIDFD. clone3() will EINVAL all instances where CLONE_DETACHED is
passed.
>
> On i386, clone() should not be called through vsyscall, but
> directly through int $0x80.
>
> BUGS
> GNU C library versions 2.3.4 up to and including 2.24 contained a
> wrapper function for getpid(2) that performed caching of PIDs.
> This caching relied on support in the glibc wrapper for clone(),
> but limitations in the implementation meant that the cache was not
> up to date in some circumstances. In particular, if a signal was
> delivered to the child immediately after the clone() call, then a
> call to getpid(2) in a handler for the signal could return the PID
> of the calling process ("the parent"), if the clone wrapper had
> not yet had a chance to update the PID cache in the child. (This
> discussion ignores the case where the child was created using
> CLONE_THREAD, when getpid(2) should return the same value in the
> child and in the process that called clone(), since the caller and
> the child are in the same thread group. The stale-cache problem
> also does not occur if the flags argument includes CLONE_VM.) To
> get the truth, it was sometimes necessary to use code such as the
> following:
>
> #include <syscall.h>
>
> pid_t mypid;
>
> mypid = syscall(SYS_getpid);
>
> Because of the stale-cache problem, as well as other problems
> noted in getpid(2), the PID caching feature was removed in glibc
> 2.25.
>
> EXAMPLE
> The following program demonstrates the use of clone() to create a
> child process that executes in a separate UTS namespace. The
> child changes the hostname in its UTS namespace. Both parent and
> child then display the system hostname, making it possible to see
> that the hostname differs in the UTS namespaces of the parent and
> child. For an example of the use of this program, see setns(2).
>
> Program source
> #define _GNU_SOURCE
> #include <sys/wait.h>
> #include <sys/utsname.h>
> #include <sched.h>
> #include <string.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <unistd.h>
>
> #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
> } while (0)
>
> static int /* Start function for cloned child */
> childFunc(void *arg)
> {
> struct utsname uts;
>
> /* Change hostname in UTS namespace of child */
>
> if (sethostname(arg, strlen(arg)) == -1)
> errExit("sethostname");
>
> /* Retrieve and display hostname */
>
> if (uname(&uts) == -1)
> errExit("uname");
> printf("uts.nodename in child: %s\n", uts.nodename);
>
> /* Keep the namespace open for a while, by sleeping.
> This allows some experimentation--for example, another
> process might join the namespace. */
>
> sleep(200);
>
> return 0; /* Child terminates now */
> }
>
> #define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */
>
> int
> main(int argc, char *argv[])
> {
> char *stack; /* Start of stack buffer */
> char *stackTop; /* End of stack buffer */
> pid_t pid;
> struct utsname uts;
>
> if (argc < 2) {
> fprintf(stderr, "Usage: %s <child-hostname>\n", argv[0]);
> exit(EXIT_SUCCESS);
> }
>
> /* Allocate stack for child */
>
> stack = malloc(STACK_SIZE);
I'd really change this to mmap() since it makes some of the requirements
more obvious including the MAP_STACK flag.
Christian
^ permalink raw reply
* Re: [PATCH 1/1] userfaultfd: require CAP_SYS_PTRACE for UFFD_FEATURE_EVENT_FORK
From: Andrea Arcangeli @ 2019-11-07 15:38 UTC (permalink / raw)
To: Daniel Colascione
Cc: Mike Rapoport, Andy Lutomirski, linux-kernel, Andrew Morton,
Jann Horn, Linus Torvalds, Lokesh Gidra, Nick Kralevich,
Nosh Minwalla, Pavel Emelyanov, Tim Murray, Linux API, linux-mm
In-Reply-To: <CAKOZuevhEXpMr49KmkBLEyMGsDz8WujKvOGCty8+p7cwVbmoXA@mail.gmail.com>
Hello,
On Thu, Nov 07, 2019 at 12:54:59AM -0800, Daniel Colascione wrote:
> On Thu, Nov 7, 2019 at 12:39 AM Mike Rapoport <rppt@linux.ibm.com> wrote:
> > On Tue, Nov 05, 2019 at 08:41:18AM -0800, Daniel Colascione wrote:
> > > On Tue, Nov 5, 2019 at 8:24 AM Andrea Arcangeli <aarcange@redhat.com> wrote:
> > > > The long term plan is to introduce UFFD_FEATURE_EVENT_FORK2 feature
> > > > flag that uses the ioctl to receive the child uffd, it'll consume more
> > > > CPU, but it wouldn't require the PTRACE privilege anymore.
> > >
> > > Why not just have callers retrieve FDs using recvmsg? This way, you
> > > retrieve the message packet and the file descriptor at the same time
> > > and you don't need any appreciable extra CPU use.
> >
> > I don't follow you here. Can you elaborate on how recvmsg would be used in
> > this case?
>
> Imagine an AF_UNIX SOCK_DGRAM socket. You call recvmsg(). You get a
> blob of regular data along with some ancillary data. The ancillary
> data may include some file descriptors or it may not. Isn't the UFFD
> message model the same thing? You'd call recvmsg() on a UFFD and get
> back a uffd_msg data structure. If that uffd_msg came with file
> descriptors, these descriptors would be in ancillary data. If you
> didn't reserve enough space for the message or enough space for its
> ancillary data, the recvmsg() call would fail cleanly with MSG_TRUNC
> or MSG_CTRUNC.
Having to check for truncation is just a slowdown doesn't sound a
feature here but just a complication and unnecessary branches. You can
already read as much as you want in multiples of the uffd size.
> The nice thing about using recvmsg() for this purpose is that there's
> tons of existing code for dealing with recvmsg()'s calling convention
> and its ancillary data. You can, for example, use recvmsg out of the
> box in a Python script. You could make an ioctl that also returned a
> data blob plus some optional file descriptors, but if recvmsg already
> does exactly that job and it's well-understood, why not just reuse the
> recvmsg interface?
uffd can't become an plain AF_UNIX because on the other end there's no
other process but the kernel. Even if it could the fact it'd
facilitate a pure python backend isn't relevant because handling page
faults is a performance critical system activity, and rust can do the
ioctl like it can do poll/epoll without mio/tokyo by just calling
glibc. We can't write kernel code in python either for the same
reason.
> How practical is it to actually support recvmsg without being a
> socket? How hard would it be to just become a socket? I don't know. My
AF_UINIX has more features than we need (credentials) and dealing with
skbs and truncation would slow down the protocol. The objective is to
get the highest performance possible out of the uffd API so that it
performs as close as possible to running page faults in the kernel.
So even if we could avoid a syscall in CRIU, but we'd be slowing down
QEMU and all other normal cooperative usages if we made uffd a
socket. So overall it would be a net loss.
> point is only that *from a userspace API* point of view, recvmsg()
> seems ideal.
Now thinking about this, the semantics of the ancillary data seems to
be per socket family. So what does prevent you to create an AF_UNIX
socket, send it to a SCM_RIGHTS receiving daemon unaware that it is
getting an AF_UNIX socket. The daemon is calling recvmsg on the fd it
receives from SCM_RIGHTS in order to receive ancillary data from
another non-AF_UNIX family instead (it is irrelevant what the
semantics of the ancillary data are but they're not AF_UNIX). So the
daemon calls recvmsg and it will not understand that the fd in the
ancillary data represents an installed "fd" in the fd space and in
turn still gets the fd involuntary installed with the exact same side
effects of what we're fixing in the uffd fork event read?
I guess there shall be something somewhere that prevents recvmsg to
run on anything but an AF_UNIX if msg_control isn't NULL and
msg_controllen > 0? Otherwise even if we implemented the uffd fork
event with recvmsg, we would be back to square one.
As a corollary this could also imply we don't need the ptrace check
after all if the same thing can happen already to SCM_RIGHTS receiving
daemon expecting to receive ancillary data from AF_SOMETHING but
getting an AF_UNIX instead through SCM_RIGHTS (just like the uffd
example was expecting to call read() on a normal fd and instead it got
an uffd).
I'm sure there's something stopping SCM_RIGHTS to have the same
pitfalls of uffd event fork and that makes recvmsg safe unlike read()
but then it's not immediately clear what it is.
Thanks,
Andrea
^ permalink raw reply
* Re: handle_exit_race && PF_EXITING
From: Oleg Nesterov @ 2019-11-07 15:51 UTC (permalink / raw)
To: Thomas Gleixner
Cc: Florian Weimer, Shawn Landden, libc-alpha, linux-api, LKML,
Arnd Bergmann, Deepa Dinamani, Andrew Morton, Catalin Marinas,
Keith Packard, Peter Zijlstra
In-Reply-To: <alpine.DEB.2.21.1911061808030.1869@nanos.tec.linutronix.de>
On 11/06, Thomas Gleixner wrote:
>
> On Wed, 6 Nov 2019, Oleg Nesterov wrote:
> >
> > I think that (with or without this fix) handle_exit_race() logic needs
> > cleanups, there is no reason for get_futex_value_locked(), we can drop
> > ->pi_lock right after we see PF_EXITPIDONE. Lets discuss this later.
>
> Which still is in atomic because the hash bucket lock is held, ergo
> get_futex_value_locked() needs to stay for now.
Indeed, you are right.
> Same explanation as before just not prosa this time:
>
> exit() lock_pi(futex2)
> exit_pi_state_list()
> lock(tsk->pi_lock)
> tsk->flags |= PF_EXITPIDONE; attach_to_pi_owner()
> ...
> // Loop unrolled for clarity
> while(!list_empty()) lock(tsk->pi_lock);
> cleanup(futex1)
> unlock(tsk->pi_lock)
^^^^^^^^^^^^^^^^^^^^
Ah! Thanks.
Hmm. In particular, exit_pi_state() drops pi_lock if refcount_inc_not_zero() fails.
Isn't this another potential source of livelock ?
Suppose that a realtime lock owner X sleeps somewhere, another task T
calls put_pi_state(), refcount_dec_and_test() succeeds.
What if, say, X is killed right after that and preempts T on the same
CPU?
Oleg.
^ permalink raw reply
* Re: For review: documentation of clone3() system call
From: Florian Weimer @ 2019-11-07 16:10 UTC (permalink / raw)
To: Christian Brauner
Cc: Michael Kerrisk (man-pages), Christian Brauner, lkml, linux-man,
Kees Cook, Oleg Nesterov, Arnd Bergmann, David Howells,
Pavel Emelyanov, Andrew Morton, Adrian Reber, Andrei Vagin,
Linux API, Jann Horn
In-Reply-To: <20191107151941.dw4gtul5lrtax4se@wittgenstein>
* Christian Brauner:
> I've always been confused by the "..." for the glibc wrapper. The glibc
> prototype in bits/sched.h also looks like this:
>
> extern int clone (int (*__fn) (void *__arg), void *__child_stack, int __flags, void *__arg, ...) __THROW;
>
> The additionl args parent_tid, tls, and child_tid are present in _all_
> clone version in the same order. In fact the glibc wrapper here give the
> illusion that it's parent_tid, tls, child_tid. The underlying syscall
> has a different order parent_tidptr, child_tidptr, tls.
>
> Florian, can you advise what prototype we should mention for the glibc
> clone() wrapper here. I'd like it to be as simple as possible and get
> rid of the ...
> Architectural differences are explained in detail below anyway.
Our header has:
/* Clone current process. */
extern int clone (int (*__fn) (void *__arg), void *__child_stack,
int __flags, void *__arg, ...) __THROW;
I have not checked all assembler implementations. In theory there could
be one that relies on the different calling convention for variadic
functions (e.g., the existence of a parameter save area on POWER). Or
that swaps arguments in some architecure-specific way. 8-(
I don't have much guidance on this matter, sorry. I expect that for
clone3, we'll provide a same-stack variant as well (for fork/vfork-like
usage), which will be much closer to the kernel interface. clone/clone2
doesn't seem very fixable to me at this point.
Thanks,
Florian
^ permalink raw reply
* Re: [PATCH 1/1] userfaultfd: require CAP_SYS_PTRACE for UFFD_FEATURE_EVENT_FORK
From: Daniel Colascione @ 2019-11-07 16:15 UTC (permalink / raw)
To: Andrea Arcangeli
Cc: Mike Rapoport, Andy Lutomirski, linux-kernel, Andrew Morton,
Jann Horn, Linus Torvalds, Lokesh Gidra, Nick Kralevich,
Nosh Minwalla, Pavel Emelyanov, Tim Murray, Linux API, linux-mm
In-Reply-To: <20191107153801.GF17896@redhat.com>
On Thu, Nov 7, 2019 at 7:38 AM Andrea Arcangeli <aarcange@redhat.com> wrote:
> On Thu, Nov 07, 2019 at 12:54:59AM -0800, Daniel Colascione wrote:
> > On Thu, Nov 7, 2019 at 12:39 AM Mike Rapoport <rppt@linux.ibm.com> wrote:
> > > On Tue, Nov 05, 2019 at 08:41:18AM -0800, Daniel Colascione wrote:
> > > > On Tue, Nov 5, 2019 at 8:24 AM Andrea Arcangeli <aarcange@redhat.com> wrote:
> > > > > The long term plan is to introduce UFFD_FEATURE_EVENT_FORK2 feature
> > > > > flag that uses the ioctl to receive the child uffd, it'll consume more
> > > > > CPU, but it wouldn't require the PTRACE privilege anymore.
> > > >
> > > > Why not just have callers retrieve FDs using recvmsg? This way, you
> > > > retrieve the message packet and the file descriptor at the same time
> > > > and you don't need any appreciable extra CPU use.
> > >
> > > I don't follow you here. Can you elaborate on how recvmsg would be used in
> > > this case?
> >
> > Imagine an AF_UNIX SOCK_DGRAM socket. You call recvmsg(). You get a
> > blob of regular data along with some ancillary data. The ancillary
> > data may include some file descriptors or it may not. Isn't the UFFD
> > message model the same thing? You'd call recvmsg() on a UFFD and get
> > back a uffd_msg data structure. If that uffd_msg came with file
> > descriptors, these descriptors would be in ancillary data. If you
> > didn't reserve enough space for the message or enough space for its
> > ancillary data, the recvmsg() call would fail cleanly with MSG_TRUNC
> > or MSG_CTRUNC.
>
> Having to check for truncation is just a slowdown doesn't sound a
> feature here but just a complication and unnecessary branches. You can
> already read as much as you want in multiples of the uffd size.
You're already paying for bounds checking. Receiving a message via a
datagram socket is basically the same thing as what UFFD's read is
doing anyway.
> > The nice thing about using recvmsg() for this purpose is that there's
> > tons of existing code for dealing with recvmsg()'s calling convention
> > and its ancillary data. You can, for example, use recvmsg out of the
> > box in a Python script. You could make an ioctl that also returned a
> > data blob plus some optional file descriptors, but if recvmsg already
> > does exactly that job and it's well-understood, why not just reuse the
> > recvmsg interface?
>
> uffd can't become an plain AF_UNIX because on the other end there's no
> other process but the kernel. Even if it could the fact it'd
> facilitate a pure python backend isn't relevant because handling page
> faults is a performance critical system activity, and rust can do the
> ioctl like it can do poll/epoll without mio/tokyo by just calling
> glibc. We can't write kernel code in python either for the same
> reason.
My point isn't "hey, you should write this in Python". (Although for
prototyping, why not?) My point is that where there's an existing
kernel interface for exactly the functionality you want, you should
use it instead of inventing some new thing, because when we use the
same interface for things have the same shape and purpose, we not only
get to reuse code, but also the knowledge in people's heads.
> > point is only that *from a userspace API* point of view, recvmsg()
> > seems ideal.
>
> Now thinking about this, the semantics of the ancillary data seems to
> be per socket family. So what does prevent you to create an AF_UNIX
> socket, send it to a SCM_RIGHTS receiving daemon unaware that it is
> getting an AF_UNIX socket. The daemon is calling recvmsg on the fd it
> receives from SCM_RIGHTS in order to receive ancillary data from
> another non-AF_UNIX family instead (it is irrelevant what the
> semantics of the ancillary data are but they're not AF_UNIX). So the
> daemon calls recvmsg and it will not understand that the fd in the
> ancillary data represents an installed "fd" in the fd space and in
> turn still gets the fd involuntary installed with the exact same side
> effects of what we're fixing in the uffd fork event read?
SCM_RIGHTS (AFAIK) is the only bit of ancillary data which indicates
that the kernel has created a file descriptor in the process doing the
recvmsg.
> I guess there shall be something somewhere that prevents recvmsg to
> run on anything but an AF_UNIX if msg_control isn't NULL and
> msg_controllen > 0? Otherwise even if we implemented the uffd fork
> event with recvmsg, we would be back to square one.
Why would we limit recvmsg to AF_UNIX? We can receive ancillary data
on other sockets, e.g., netlink. SCM_RIGHTS works only with AF_UNIX
right now, but this limitation isn't written in stone.
> As a corollary this could also imply we don't need the ptrace check
> after all if the same thing can happen already to SCM_RIGHTS receiving
> daemon expecting to receive ancillary data from AF_SOMETHING but
> getting an AF_UNIX instead through SCM_RIGHTS (just like the uffd
> example was expecting to call read() on a normal fd and instead it got
> an uffd).
Programs generally don't go calling recvmsg() on random FDs they get
from the outside world. They do call read() on those FDs, which is why
read() having unexpected side effects is terrible.
> I'm sure there's something stopping SCM_RIGHTS to have the same
> pitfalls of uffd event fork and that makes recvmsg safe unlike read()
> but then it's not immediately clear what it is.
If you call it with a non-empty ancillary data buffer, you know to
react to what you get. You're *opting into* the possibility of getting
file descriptors. Sure, it's theoretically possible that a program
calls recvmsg on random FDs it gets from unknown sources, sees
SCM_RIGHTS unexpectedly, and just the SCM_RIGHTS message and its FD
payload, but that's an outright bug, while calling read() on stdin is
no bug.
Anyway, IMHO, UFFD should be a netlink-like SOCK_DGRAM socket that
sends FDs with SCM_RIGHTS. This interface is already very efficient --
people have been optimizing the hell out of AF_UNIX for decades ---
and this interface provides exactly the right interface semantics for
what UFFD needs to do.
^ permalink raw reply
* Re: Continuing the UAPI split
From: Szabolcs Nagy @ 2019-11-07 16:21 UTC (permalink / raw)
To: Elichai Turkel, Florian Weimer
Cc: nd, Christian Brauner, linux-api@vger.kernel.org, libc-alpha
In-Reply-To: <CALN7hCJ_umFqC1L0T19CuiGiGoVwac5807NDw4LiDqSD-VJL=Q@mail.gmail.com>
On 07/11/2019 13:03, Elichai Turkel wrote:
> A rename to the structs/types so they won't collide with libc?
> Prioritizing POSIX conformance in the kernel(I think that ship has long sailed)?
hosted c code can only use linux api headers reliably
if those respect the iso c and posix rules.
linux should maintain a clean set of c headers (or
interface descriptions from which such headers can be
generated).
providing clean headers that work in both hosted and
freestanding environments is not trivial: e.g. the
former requires type definitions to use libc types,
the latter requires type definitions that don't depend
on the libc.
e.g. a possible approach is to use iso c implementation
reserved namespace for all linux api identifiers, so
it's entirely independent of libc and non-conflicting,
then libc headers can replicate declarations that it
wants to expose (with different names and libc types).
(there are other ways, but the current headers are hard
to use which affects a lot of users)
> Or just giving up and telling users they can't just directly include
> both libc headers and kernel headers?
including both libc and linux headers is fragile and
will break differently across the different linux
libc implementations.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox