* [PATCH 5/7] fsinfo: Export superblock notification counter
From: David Howells @ 2019-05-28 16:02 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155905930702.7587.7100265859075976147.stgit@warthog.procyon.org.uk>
Provide an fsinfo attribute to export the superblock notification counter
so that it can be polled in the case of a notification buffer overrun.
This is accessed with:
struct fsinfo_params params = {
.request = FSINFO_ATTR_SB_NOTIFICATIONS,
};
and returns a structure that looks like:
struct fsinfo_sb_notifications {
__u64 watch_id;
__u32 notify_counter;
__u32 __reserved[1];
};
Where watch_id is a number uniquely identifying the superblock in
notification records and notify_counter is incremented for each
superblock notification posted.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/fsinfo.c | 12 ++++++++++++
fs/super.c | 1 +
include/linux/fs.h | 1 +
include/uapi/linux/fsinfo.h | 10 ++++++++++
include/uapi/linux/watch_queue.h | 2 +-
samples/vfs/test-fsinfo.c | 13 +++++++++++++
6 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index 3ec64d3cba08..1456e26d2f7c 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -284,6 +284,16 @@ static int fsinfo_generic_param_enum(struct file_system_type *f,
return sizeof(*p);
}
+static int fsinfo_generic_sb_notifications(struct path *path,
+ struct fsinfo_sb_notifications *p)
+{
+ struct super_block *sb = path->dentry->d_sb;
+
+ p->watch_id = sb->s_unique_id;
+ p->notify_counter = atomic_read(&sb->s_notify_counter);
+ return sizeof(*p);
+}
+
static void fsinfo_insert_sb_flag_parameters(struct path *path,
struct fsinfo_kparams *params)
{
@@ -331,6 +341,7 @@ int generic_fsinfo(struct path *path, struct fsinfo_kparams *params)
case _genp(MOUNT_DEVNAME, mount_devname);
case _genp(MOUNT_CHILDREN, mount_children);
case _genp(MOUNT_SUBMOUNT, mount_submount);
+ case _gen(SB_NOTIFICATIONS, sb_notifications);
default:
return -EOPNOTSUPP;
}
@@ -606,6 +617,7 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRING_N (SERVER_NAME, server_name),
FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
FSINFO_STRING (CELL_NAME, cell_name),
+ FSINFO_STRUCT (SB_NOTIFICATIONS, sb_notifications),
};
/**
diff --git a/fs/super.c b/fs/super.c
index 991d69d9dbed..c4bd0d131ef2 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -1823,6 +1823,7 @@ EXPORT_SYMBOL(thaw_super);
*/
void post_sb_notification(struct super_block *s, struct superblock_notification *n)
{
+ atomic_inc(&s->s_notify_counter);
post_watch_notification(s->s_watchers, &n->watch, current_cred(),
s->s_unique_id);
}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 79ede28f54cc..2c00e292b92b 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1535,6 +1535,7 @@ struct super_block {
#ifdef CONFIG_SB_NOTIFICATIONS
struct watch_list *s_watchers;
#endif
+ atomic_t s_notify_counter;
} __randomize_layout;
/* Helper functions so that in most cases filesystems will
diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
index 7247088332c2..b4c9446305bb 100644
--- a/include/uapi/linux/fsinfo.h
+++ b/include/uapi/linux/fsinfo.h
@@ -39,6 +39,7 @@ enum fsinfo_attribute {
FSINFO_ATTR_SERVER_NAME = 21, /* Name of the Nth server (string) */
FSINFO_ATTR_SERVER_ADDRESS = 22, /* Mth address of the Nth server */
FSINFO_ATTR_CELL_NAME = 23, /* Cell name (string) */
+ FSINFO_ATTR_SB_NOTIFICATIONS = 24, /* sb_notify() information */
FSINFO_ATTR__NR
};
@@ -308,4 +309,13 @@ struct fsinfo_server_address {
struct __kernel_sockaddr_storage address;
};
+/*
+ * Information struct for fsinfo(FSINFO_ATTR_SB_NOTIFICATIONS).
+ */
+struct fsinfo_sb_notifications {
+ __u64 watch_id; /* Watch ID for superblock. */
+ __u32 notify_counter; /* Number of notifications. */
+ __u32 __reserved[1];
+};
+
#endif /* _UAPI_LINUX_FSINFO_H */
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 126afcc98cc6..3b5770889bba 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -145,7 +145,7 @@ enum superblock_notification_type {
*/
struct superblock_notification {
struct watch_notification watch; /* WATCH_TYPE_SB_NOTIFY */
- __u64 sb_id; /* 64-bit superblock ID [fsinfo_ids::f_sb_id] */
+ __u64 sb_id; /* 64-bit superblock ID [fsinfo_sb_notifications::watch_id] */
};
struct superblock_error_notification {
diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c
index af29da74559e..0f8f9ded0925 100644
--- a/samples/vfs/test-fsinfo.c
+++ b/samples/vfs/test-fsinfo.c
@@ -90,6 +90,7 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRING_N (SERVER_NAME, server_name),
FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
FSINFO_STRING (CELL_NAME, cell_name),
+ FSINFO_STRUCT (SB_NOTIFICATIONS, sb_notifications),
};
#define FSINFO_NAME(X,Y) [FSINFO_ATTR_##X] = #Y
@@ -118,6 +119,7 @@ static const char *fsinfo_attr_names[FSINFO_ATTR__NR] = {
FSINFO_NAME (SERVER_NAME, server_name),
FSINFO_NAME (SERVER_ADDRESS, server_address),
FSINFO_NAME (CELL_NAME, cell_name),
+ FSINFO_NAME (SB_NOTIFICATIONS, sb_notifications),
};
union reply {
@@ -133,6 +135,7 @@ union reply {
struct fsinfo_mount_info mount_info;
struct fsinfo_mount_child mount_children[1];
struct fsinfo_server_address srv_addr;
+ struct fsinfo_sb_notifications sb_notifications;
};
static void dump_hex(unsigned int *data, int from, int to)
@@ -377,6 +380,15 @@ static void dump_attr_MOUNT_CHILDREN(union reply *r, int size)
printf("\t[%u] %8x %8x\n", i++, f->mnt_id, f->notify_counter);
}
+static void dump_attr_SB_NOTIFICATIONS(union reply *r, int size)
+{
+ struct fsinfo_sb_notifications *f = &r->sb_notifications;
+
+ printf("\n");
+ printf("\twatch_id: %llx\n", (unsigned long long)f->watch_id);
+ printf("\tnotifs : %llx\n", (unsigned long long)f->notify_counter);
+}
+
/*
*
*/
@@ -395,6 +407,7 @@ static const dumper_t fsinfo_attr_dumper[FSINFO_ATTR__NR] = {
FSINFO_DUMPER(MOUNT_INFO),
FSINFO_DUMPER(MOUNT_CHILDREN),
FSINFO_DUMPER(SERVER_ADDRESS),
+ FSINFO_DUMPER(SB_NOTIFICATIONS),
};
static void dump_fsinfo(enum fsinfo_attribute attr,
^ permalink raw reply related
* [PATCH 4/7] vfs: Add superblock notifications
From: David Howells @ 2019-05-28 16:02 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155905930702.7587.7100265859075976147.stgit@warthog.procyon.org.uk>
Add a superblock event notification facility whereby notifications about
superblock events, such as I/O errors (EIO), quota limits being hit
(EDQUOT) and running out of space (ENOSPC) can be reported to a monitoring
process asynchronously. Note that this does not cover vfsmount topology
changes. mount_notify() is used for that.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_SB_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
sb_notify(AT_FDCWD, "/home/dhowells", 0, fd, 0x03);
In this case, it would let me monitor my own homedir for events. After
setting the watch, records will be placed into the queue when, for example,
as superblock switches between read-write and read-only. Records are of
the following format:
struct superblock_notification {
struct watch_notification watch;
__u64 sb_id;
} *n;
Where:
n->watch.type will be WATCH_TYPE_SB_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_SUPERBLOCK_READONLY.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the fifth argument to
sb_notify(), shifted.
n->watch.info & WATCH_INFO_FLAG_0 will be used for
NOTIFY_SUPERBLOCK_READONLY, being set if the superblock becomes
R/O, and being cleared otherwise.
n->sb_id will be the ID of the superblock, as can be retrieved with
the fsinfo() syscall, as part of the fsinfo_sb_notifications
attribute in the the watch_id field.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also that
the queue can be shared between multiple notifications of various types.
[*] QUESTION: Does this want to be per-sb, per-mount_namespace,
per-some-new-notify-ns or per-system? Or do multiple options make
sense?
[*] QUESTION: I've done it this way so that anyone could theoretically
monitor the superblock of any filesystem they can pathwalk to, but do
we need other security controls?
[*] QUESTION: Should the LSM be able to filter the events a queue can
receive? For instance the opener of the queue would grant that queue
subject creds (by ->f_cred) that could be used to govern what events
could be seen, assuming the target superblock to have some object
creds, based on, say, the mounter.
Signed-off-by: David Howells <dhowells@redhat.com>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 12 +++
fs/super.c | 115 ++++++++++++++++++++++++++++++++
include/linux/fs.h | 77 +++++++++++++++++++++
include/linux/syscalls.h | 2 +
include/uapi/linux/watch_queue.h | 26 +++++++
kernel/sys_ni.c | 3 +
8 files changed, 237 insertions(+)
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index a8416a9a0ccb..429416ce60e1 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -440,3 +440,4 @@
433 i386 fspick sys_fspick __ia32_sys_fspick
434 i386 fsinfo sys_fsinfo __ia32_sys_fsinfo
435 i386 mount_notify sys_mount_notify __ia32_sys_mount_notify
+436 i386 sb_notify sys_sb_notify __ia32_sys_sb_notify
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index ea052a94eb97..4ae146e472db 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -357,6 +357,7 @@
433 common fspick __x64_sys_fspick
434 common fsinfo __x64_sys_fsinfo
435 common mount_notify __x64_sys_mount_notify
+436 common sb_notify __x64_sys_sb_notify
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/Kconfig b/fs/Kconfig
index a26bbe27a791..fc0fa4b35f3c 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -130,6 +130,18 @@ config MOUNT_NOTIFICATIONS
device to handle the notification buffer and provides the
mount_notify() system call to enable/disable watchpoints.
+config SB_NOTIFICATIONS
+ bool "Superblock event notifications"
+ select WATCH_QUEUE
+ help
+ This option provides support for receiving superblock event
+ notifications. This makes use of the /dev/watch_queue misc device to
+ handle the notification buffer and provides the sb_notify() system
+ call to enable/disable watches.
+
+ Events can include things like changing between R/W and R/O, EIO
+ generation, ENOSPC generation and EDQUOT generation.
+
source "fs/quota/Kconfig"
source "fs/autofs/Kconfig"
diff --git a/fs/super.c b/fs/super.c
index 61819e8e5469..991d69d9dbed 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -36,6 +36,8 @@
#include <linux/lockdep.h>
#include <linux/user_namespace.h>
#include <linux/fs_context.h>
+#include <linux/syscalls.h>
+#include <linux/namei.h>
#include <uapi/linux/mount.h>
#include "internal.h"
@@ -350,6 +352,10 @@ void deactivate_locked_super(struct super_block *s)
{
struct file_system_type *fs = s->s_type;
if (atomic_dec_and_test(&s->s_active)) {
+#ifdef CONFIG_SB_NOTIFICATIONS
+ if (s->s_watchers)
+ remove_watch_list(s->s_watchers);
+#endif
cleancache_invalidate_fs(s);
unregister_shrinker(&s->s_shrink);
fs->kill_sb(s);
@@ -990,6 +996,8 @@ int reconfigure_super(struct fs_context *fc)
/* Needs to be ordered wrt mnt_is_readonly() */
smp_wmb();
sb->s_readonly_remount = 0;
+ notify_sb(sb, NOTIFY_SUPERBLOCK_READONLY,
+ remount_ro ? WATCH_INFO_FLAG_0 : 0);
/*
* Some filesystems modify their metadata via some other path than the
@@ -1808,3 +1816,110 @@ int thaw_super(struct super_block *sb)
return thaw_super_locked(sb);
}
EXPORT_SYMBOL(thaw_super);
+
+#ifdef CONFIG_SB_NOTIFICATIONS
+/*
+ * Post superblock notifications.
+ */
+void post_sb_notification(struct super_block *s, struct superblock_notification *n)
+{
+ post_watch_notification(s->s_watchers, &n->watch, current_cred(),
+ s->s_unique_id);
+}
+
+static void release_sb_watch(struct watch_list *wlist, struct watch *watch)
+{
+ struct super_block *s = watch->private;
+
+ put_super(s);
+}
+
+/**
+ * sys_sb_notify - Watch for superblock events.
+ * @dfd: Base directory to pathwalk from or fd referring to superblock.
+ * @filename: Path to superblock to place the watch upon
+ * @at_flags: Pathwalk control flags
+ * @watch_fd: The watch queue to send notifications to.
+ * @watch_id: The watch ID to be placed in the notification (-1 to remove watch)
+ */
+SYSCALL_DEFINE5(sb_notify,
+ int, dfd,
+ const char __user *, filename,
+ unsigned int, at_flags,
+ int, watch_fd,
+ int, watch_id)
+{
+ struct watch_queue *wqueue;
+ struct super_block *s;
+ struct watch_list *wlist = NULL;
+ struct watch *watch;
+ struct path path;
+ int ret;
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+
+ ret = user_path_at(dfd, filename, at_flags, &path);
+ if (ret)
+ return ret;
+
+ wqueue = get_watch_queue(watch_fd);
+ if (IS_ERR(wqueue))
+ goto err_path;
+
+ s = path.dentry->d_sb;
+ if (watch_id >= 0) {
+ if (!s->s_watchers) {
+ wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+ if (!wlist)
+ goto err_wqueue;
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+ wlist->release_watch = release_sb_watch;
+ }
+
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wlist;
+
+ init_watch(watch, wqueue);
+ watch->id = s->s_unique_id;
+ watch->private = s;
+ watch->info_id = (u32)watch_id << 24;
+
+ down_write(&s->s_umount);
+ ret = -EIO;
+ if (atomic_read(&s->s_active)) {
+ if (!s->s_watchers) {
+ s->s_watchers = wlist;
+ wlist = NULL;
+ }
+
+ ret = add_watch_to_object(watch, s->s_watchers);
+ if (ret == 0) {
+ spin_lock(&sb_lock);
+ s->s_count++;
+ spin_unlock(&sb_lock);
+ }
+ }
+ up_write(&s->s_umount);
+ if (ret < 0)
+ kfree(watch);
+ } else if (s->s_watchers) {
+ down_write(&s->s_umount);
+ ret = remove_watch_from_object(s->s_watchers, wqueue,
+ s->s_unique_id, false);
+ up_write(&s->s_umount);
+ } else {
+ ret = -EBADSLT;
+ }
+
+err_wlist:
+ kfree(wlist);
+err_wqueue:
+ put_watch_queue(wqueue);
+err_path:
+ path_put(&path);
+ return ret;
+}
+#endif
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f1c74596cd77..79ede28f54cc 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -40,6 +40,7 @@
#include <linux/fs_types.h>
#include <linux/build_bug.h>
#include <linux/stddef.h>
+#include <linux/watch_queue.h>
#include <asm/byteorder.h>
#include <uapi/linux/fs.h>
@@ -1530,6 +1531,10 @@ struct super_block {
/* Superblock event notifications */
u64 s_unique_id;
+
+#ifdef CONFIG_SB_NOTIFICATIONS
+ struct watch_list *s_watchers;
+#endif
} __randomize_layout;
/* Helper functions so that in most cases filesystems will
@@ -3530,4 +3535,76 @@ static inline struct sock *io_uring_get_socket(struct file *file)
}
#endif
+extern void post_sb_notification(struct super_block *, struct superblock_notification *);
+
+/**
+ * notify_sb: Post simple superblock notification.
+ * @s: The superblock the notification is about.
+ * @subtype: The type of notification.
+ * @info: WATCH_INFO_FLAG_* flags to be set in the record.
+ */
+static inline void notify_sb(struct super_block *s,
+ enum superblock_notification_type subtype,
+ u32 info)
+{
+#ifdef CONFIG_SB_NOTIFICATIONS
+ if (unlikely(s->s_watchers)) {
+ struct superblock_notification n = {
+ .watch.type = WATCH_TYPE_SB_NOTIFY,
+ .watch.subtype = subtype,
+ .watch.info = sizeof(n) | info,
+ .sb_id = s->s_unique_id,
+ };
+
+ post_sb_notification(s, &n);
+ }
+
+#endif
+}
+
+/**
+ * sb_error: Post superblock error notification.
+ * @s: The superblock the notification is about.
+ * @error: The error number to be recorded.
+ */
+static inline int sb_error(struct super_block *s, int error)
+{
+#ifdef CONFIG_SB_NOTIFICATIONS
+ if (unlikely(s->s_watchers)) {
+ struct superblock_error_notification n = {
+ .s.watch.type = WATCH_TYPE_SB_NOTIFY,
+ .s.watch.subtype = NOTIFY_SUPERBLOCK_ERROR,
+ .s.watch.info = sizeof(n),
+ .s.sb_id = s->s_unique_id,
+ .error_number = error,
+ .error_cookie = 0,
+ };
+
+ post_sb_notification(s, &n.s);
+ }
+#endif
+ return error;
+}
+
+/**
+ * sb_EDQUOT: Post superblock quota overrun notification.
+ * @s: The superblock the notification is about.
+ */
+static inline int sb_EQDUOT(struct super_block *s)
+{
+#ifdef CONFIG_SB_NOTIFICATIONS
+ if (unlikely(s->s_watchers)) {
+ struct superblock_notification n = {
+ .watch.type = WATCH_TYPE_SB_NOTIFY,
+ .watch.subtype = NOTIFY_SUPERBLOCK_EDQUOT,
+ .watch.info = sizeof(n),
+ .sb_id = s->s_unique_id,
+ };
+
+ post_sb_notification(s, &n);
+ }
+#endif
+ return -EDQUOT;
+}
+
#endif /* _LINUX_FS_H */
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 7c2b66175f3c..204a6dbcc34a 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1003,6 +1003,8 @@ asmlinkage long sys_fsinfo(int dfd, const char __user *path,
void __user *buffer, size_t buf_size);
asmlinkage long sys_mount_notify(int dfd, const char __user *path,
unsigned int at_flags, int watch_fd, int watch_id);
+asmlinkage long sys_sb_notify(int dfd, const char __user *path,
+ unsigned int at_flags, int watch_fd, int watch_id);
/*
* Architecture-specific system calls
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 388b4141bcee..126afcc98cc6 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -128,4 +128,30 @@ struct mount_notification {
__u32 changed_mount; /* The mount that got changed */
};
+/*
+ * Type of superblock notification.
+ */
+enum superblock_notification_type {
+ NOTIFY_SUPERBLOCK_READONLY = 0, /* Filesystem toggled between R/O and R/W */
+ NOTIFY_SUPERBLOCK_ERROR = 1, /* Error in filesystem or blockdev */
+ NOTIFY_SUPERBLOCK_EDQUOT = 2, /* EDQUOT notification */
+ NOTIFY_SUPERBLOCK_NETWORK = 3, /* Network status change */
+};
+
+/*
+ * Superblock notification record.
+ * - watch.type = WATCH_TYPE_MOUNT_NOTIFY
+ * - watch.subtype = enum superblock_notification_subtype
+ */
+struct superblock_notification {
+ struct watch_notification watch; /* WATCH_TYPE_SB_NOTIFY */
+ __u64 sb_id; /* 64-bit superblock ID [fsinfo_ids::f_sb_id] */
+};
+
+struct superblock_error_notification {
+ struct superblock_notification s; /* subtype = notify_superblock_error */
+ __u32 error_number;
+ __u32 error_cookie;
+};
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 97b025e7863c..565d1e3d1bed 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -108,6 +108,9 @@ COND_SYSCALL(quotactl);
/* fs/read_write.c */
+/* fs/sb_notify.c */
+COND_SYSCALL(sb_notify);
+
/* fs/sendfile.c */
/* fs/select.c */
^ permalink raw reply related
* [PATCH 3/7] vfs: Add a mount-notification facility
From: David Howells @ 2019-05-28 16:02 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155905930702.7587.7100265859075976147.stgit@warthog.procyon.org.uk>
Add a mount notification facility whereby notifications about changes in
mount topology and configuration can be received. Note that this only
covers vfsmount topology changes and not superblock events. A separate
facility will be added for that.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_MOUNT_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
mount_notify(AT_FDCWD, "/", 0, fd, 0x02);
In this case, it would let me monitor the mount topology subtree rooted at
"/" for events. Mount notifications propagate up the tree towards the
root, so a watch will catch all of the events happening in the subtree
rooted at the watch.
After setting the watch, records will be placed into the queue when, for
example, as superblock switches between read-write and read-only. Records
are of the following format:
struct mount_notification {
struct watch_notification watch;
__u32 triggered_on;
__u32 changed_mount;
} *n;
Where:
n->watch.type will be WATCH_TYPE_MOUNT_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_MOUNT_NEW_MOUNT.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the fifth argument to
mount_notify(), shifted.
n->watch.info & WATCH_INFO_FLAG_0 will be used for
NOTIFY_MOUNT_READONLY, being set if the superblock becomes R/O, and
being cleared otherwise, and for NOTIFY_MOUNT_NEW_MOUNT, being set
if the new mount is a submount (e.g. an automount).
n->triggered_on indicates the ID of the mount on which the watch
was installed.
n->changed_mount indicates the ID of the mount that was affected.
The mount IDs can be retrieved with the fsinfo() syscall, using the
fsinfo_mount_info and fsinfo_mount_child attributes. There are
notification counters there too for when a buffer overrun occurs, thereby
allowing the mount tree to be quickly rescanned.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also that
the queue can be shared between multiple notifications of various types.
Signed-off-by: David Howells <dhowells@redhat.com>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 9 ++
fs/Makefile | 1
fs/mount.h | 33 ++++--
fs/mount_notify.c | 178 ++++++++++++++++++++++++++++++++
fs/namespace.c | 9 +-
include/linux/dcache.h | 1
include/linux/syscalls.h | 2
include/uapi/linux/watch_queue.h | 24 ++++
kernel/sys_ni.c | 3 +
11 files changed, 248 insertions(+), 14 deletions(-)
create mode 100644 fs/mount_notify.c
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index 03decae51513..a8416a9a0ccb 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -439,3 +439,4 @@
432 i386 fsmount sys_fsmount __ia32_sys_fsmount
433 i386 fspick sys_fspick __ia32_sys_fspick
434 i386 fsinfo sys_fsinfo __ia32_sys_fsinfo
+435 i386 mount_notify sys_mount_notify __ia32_sys_mount_notify
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index ea63df9a1020..ea052a94eb97 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -356,6 +356,7 @@
432 common fsmount __x64_sys_fsmount
433 common fspick __x64_sys_fspick
434 common fsinfo __x64_sys_fsinfo
+435 common mount_notify __x64_sys_mount_notify
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/Kconfig b/fs/Kconfig
index 9e7d2f2c0111..a26bbe27a791 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -121,6 +121,15 @@ source "fs/crypto/Kconfig"
source "fs/notify/Kconfig"
+config MOUNT_NOTIFICATIONS
+ bool "Mount topology change notifications"
+ select WATCH_QUEUE
+ help
+ This option provides support for getting change notifications on the
+ mount tree topology. This makes use of the /dev/watch_queue misc
+ device to handle the notification buffer and provides the
+ mount_notify() system call to enable/disable watchpoints.
+
source "fs/quota/Kconfig"
source "fs/autofs/Kconfig"
diff --git a/fs/Makefile b/fs/Makefile
index 26eaeae4b9a1..c6a71daf2464 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -131,3 +131,4 @@ obj-$(CONFIG_F2FS_FS) += f2fs/
obj-$(CONFIG_CEPH_FS) += ceph/
obj-$(CONFIG_PSTORE) += pstore/
obj-$(CONFIG_EFIVAR_FS) += efivarfs/
+obj-$(CONFIG_MOUNT_NOTIFICATIONS) += mount_notify.o
diff --git a/fs/mount.h b/fs/mount.h
index 47795802f78e..a95b805d00d8 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -4,6 +4,7 @@
#include <linux/poll.h>
#include <linux/ns_common.h>
#include <linux/fs_pin.h>
+#include <linux/watch_queue.h>
struct mnt_namespace {
atomic_t count;
@@ -67,9 +68,13 @@ struct mount {
int mnt_id; /* mount identifier */
int mnt_group_id; /* peer group identifier */
int mnt_expiry_mark; /* true if marked for expiry */
+ int mnt_nr_watchers; /* The number of subtree watches tracking this */
struct hlist_head mnt_pins;
struct fs_pin mnt_umount;
struct dentry *mnt_ex_mountpoint;
+#ifdef CONFIG_MOUNT_NOTIFICATIONS
+ struct watch_list *mnt_watchers; /* Watches on dentries within this mount */
+#endif
atomic_t mnt_notify_counter; /* Number of notifications generated */
} __randomize_layout;
@@ -153,18 +158,8 @@ static inline bool is_anon_ns(struct mnt_namespace *ns)
return ns->seq == 0;
}
-/*
- * Type of mount topology change notification.
- */
-enum mount_notification_subtype {
- NOTIFY_MOUNT_NEW_MOUNT = 0, /* New mount added */
- NOTIFY_MOUNT_UNMOUNT = 1, /* Mount removed manually */
- NOTIFY_MOUNT_EXPIRY = 2, /* Automount expired */
- NOTIFY_MOUNT_READONLY = 3, /* Mount R/O state changed */
- NOTIFY_MOUNT_SETATTR = 4, /* Mount attributes changed */
- NOTIFY_MOUNT_MOVE_FROM = 5, /* Mount moved from here */
- NOTIFY_MOUNT_MOVE_TO = 6, /* Mount moved to here (compare op_id) */
-};
+extern void post_mount_notification(struct mount *changed,
+ struct mount_notification *notify);
static inline void notify_mount(struct mount *changed,
struct mount *aux,
@@ -172,4 +167,18 @@ static inline void notify_mount(struct mount *changed,
u32 info_flags)
{
atomic_inc(&changed->mnt_notify_counter);
+
+#ifdef CONFIG_MOUNT_NOTIFICATIONS
+ {
+ struct mount_notification n = {
+ .watch.type = WATCH_TYPE_MOUNT_NOTIFY,
+ .watch.subtype = subtype,
+ .watch.info = info_flags | sizeof(n),
+ .triggered_on = changed->mnt_id,
+ .changed_mount = aux ? aux->mnt_id : 0,
+ };
+
+ post_mount_notification(changed, &n);
+ }
+#endif
}
diff --git a/fs/mount_notify.c b/fs/mount_notify.c
new file mode 100644
index 000000000000..6c7f323dbd4f
--- /dev/null
+++ b/fs/mount_notify.c
@@ -0,0 +1,178 @@
+/* Provide mount topology/attribute change notifications.
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/syscalls.h>
+#include <linux/slab.h>
+#include "mount.h"
+
+/*
+ * Post mount notifications to all watches going rootwards along the tree.
+ *
+ * Must be called with the mount_lock held.
+ */
+void post_mount_notification(struct mount *changed,
+ struct mount_notification *notify)
+{
+ const struct cred *cred = current_cred();
+ struct path cursor;
+ struct mount *mnt;
+ unsigned seq;
+
+ seq = 0;
+ rcu_read_lock();
+restart:
+ cursor.mnt = &changed->mnt;
+ cursor.dentry = changed->mnt.mnt_root;
+ mnt = real_mount(cursor.mnt);
+ notify->watch.info &= ~WATCH_INFO_IN_SUBTREE;
+
+ read_seqbegin_or_lock(&rename_lock, &seq);
+ for (;;) {
+ if (mnt->mnt_watchers &&
+ !hlist_empty(&mnt->mnt_watchers->watchers)) {
+ if (cursor.dentry->d_flags & DCACHE_MOUNT_WATCH)
+ post_watch_notification(mnt->mnt_watchers,
+ ¬ify->watch, cred,
+ (unsigned long)cursor.dentry);
+ } else {
+ cursor.dentry = mnt->mnt.mnt_root;
+ }
+ notify->watch.info |= WATCH_INFO_IN_SUBTREE;
+
+ if (cursor.dentry == cursor.mnt->mnt_root ||
+ IS_ROOT(cursor.dentry)) {
+ struct mount *parent = READ_ONCE(mnt->mnt_parent);
+
+ /* Escaped? */
+ if (cursor.dentry != cursor.mnt->mnt_root)
+ break;
+
+ /* Global root? */
+ if (mnt != parent) {
+ cursor.dentry = READ_ONCE(mnt->mnt_mountpoint);
+ mnt = parent;
+ cursor.mnt = &mnt->mnt;
+ continue;
+ }
+ break;
+ }
+
+ cursor.dentry = cursor.dentry->d_parent;
+ }
+
+ if (need_seqretry(&rename_lock, seq)) {
+ seq = 1;
+ goto restart;
+ }
+
+ done_seqretry(&rename_lock, seq);
+ rcu_read_unlock();
+}
+
+static void release_mount_watch(struct watch_list *wlist, struct watch *watch)
+{
+ struct vfsmount *mnt = watch->private;
+ struct dentry *dentry = (struct dentry *)(unsigned long)watch->id;
+
+ dput(dentry);
+ mntput(mnt);
+}
+
+/**
+ * sys_mount_notify - Watch for mount topology/attribute changes
+ * @dfd: Base directory to pathwalk from or fd referring to mount.
+ * @filename: Path to mount to place the watch upon
+ * @at_flags: Pathwalk control flags
+ * @watch_fd: The watch queue to send notifications to.
+ * @watch_id: The watch ID to be placed in the notification (-1 to remove watch)
+ */
+SYSCALL_DEFINE5(mount_notify,
+ int, dfd,
+ const char __user *, filename,
+ unsigned int, at_flags,
+ int, watch_fd,
+ int, watch_id)
+{
+ struct watch_queue *wqueue;
+ struct watch_list *wlist = NULL;
+ struct watch *watch;
+ struct mount *m;
+ struct path path;
+ int ret;
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+
+ ret = user_path_at(dfd, filename, at_flags, &path);
+ if (ret)
+ return ret;
+
+ wqueue = get_watch_queue(watch_fd);
+ if (IS_ERR(wqueue))
+ goto err_path;
+
+ m = real_mount(path.mnt);
+
+ if (watch_id >= 0) {
+ if (!m->mnt_watchers) {
+ wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+ if (!wlist)
+ goto err_wqueue;
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+ wlist->release_watch = release_mount_watch;
+ }
+
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wlist;
+
+ init_watch(watch, wqueue);
+ watch->id = (unsigned long)path.dentry;
+ watch->private = path.mnt;
+ watch->info_id = (u32)watch_id << 24;
+
+ down_write(&m->mnt.mnt_sb->s_umount);
+ if (!m->mnt_watchers) {
+ m->mnt_watchers = wlist;
+ wlist = NULL;
+ }
+
+ ret = add_watch_to_object(watch, m->mnt_watchers);
+ if (ret == 0) {
+ spin_lock(&path.dentry->d_lock);
+ path.dentry->d_flags |= DCACHE_MOUNT_WATCH;
+ spin_unlock(&path.dentry->d_lock);
+ path_get(&path);
+ }
+ up_write(&m->mnt.mnt_sb->s_umount);
+ if (ret < 0)
+ kfree(watch);
+ } else if (m->mnt_watchers) {
+ down_write(&m->mnt.mnt_sb->s_umount);
+ ret = remove_watch_from_object(m->mnt_watchers, wqueue,
+ (unsigned long)path.dentry,
+ false);
+ up_write(&m->mnt.mnt_sb->s_umount);
+ } else {
+ ret = -EBADSLT;
+ }
+
+err_wlist:
+ kfree(wlist);
+err_wqueue:
+ put_watch_queue(wqueue);
+err_path:
+ path_put(&path);
+ return ret;
+}
diff --git a/fs/namespace.c b/fs/namespace.c
index ae03066b2d9b..de778b2e8ec4 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -515,7 +515,8 @@ static int mnt_make_readonly(struct mount *mnt)
mnt->mnt.mnt_flags &= ~MNT_WRITE_HOLD;
unlock_mount_hash();
if (ret == 0)
- notify_mount(mnt, NULL, NOTIFY_MOUNT_READONLY, 0x10000);
+ notify_mount(mnt, NULL, NOTIFY_MOUNT_READONLY,
+ WATCH_INFO_FLAG_0);
return ret;
}
@@ -1478,6 +1479,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
list_del_init(&p->mnt_expire);
list_del_init(&p->mnt_list);
+#ifdef CONFIG_MOUNT_NOTIFICATIONS
+ if (p->mnt_watchers)
+ remove_watch_list(p->mnt_watchers);
+#endif
ns = p->mnt_ns;
if (ns) {
ns->mounts--;
@@ -2115,7 +2120,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,
mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
notify_mount(dest_mnt, source_mnt, NOTIFY_MOUNT_NEW_MOUNT,
source_mnt->mnt.mnt_sb->s_flags & SB_SUBMOUNT ?
- 0x10000 : 0);
+ WATCH_INFO_FLAG_0 : 0);
commit_tree(source_mnt);
}
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 361305ddd75e..5db8e244d9a0 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -217,6 +217,7 @@ struct dentry_operations {
#define DCACHE_PAR_LOOKUP 0x10000000 /* being looked up (with parent locked shared) */
#define DCACHE_DENTRY_CURSOR 0x20000000
#define DCACHE_NORCU 0x40000000 /* No RCU delay for freeing */
+#define DCACHE_MOUNT_WATCH 0x80000000 /* There's a mount watch here */
extern seqlock_t rename_lock;
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 217d25b62b4f..7c2b66175f3c 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1001,6 +1001,8 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,
asmlinkage long sys_fsinfo(int dfd, const char __user *path,
struct fsinfo_params __user *params,
void __user *buffer, size_t buf_size);
+asmlinkage long sys_mount_notify(int dfd, const char __user *path,
+ unsigned int at_flags, int watch_fd, int watch_id);
/*
* Architecture-specific system calls
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index e3bb35a480ae..388b4141bcee 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -104,4 +104,28 @@ struct key_notification {
__u32 aux; /* Per-type auxiliary data */
};
+/*
+ * Type of mount topology change notification.
+ */
+enum mount_notification_subtype {
+ NOTIFY_MOUNT_NEW_MOUNT = 0, /* New mount added */
+ NOTIFY_MOUNT_UNMOUNT = 1, /* Mount removed manually */
+ NOTIFY_MOUNT_EXPIRY = 2, /* Automount expired */
+ NOTIFY_MOUNT_READONLY = 3, /* Mount R/O state changed */
+ NOTIFY_MOUNT_SETATTR = 4, /* Mount attributes changed */
+ NOTIFY_MOUNT_MOVE_FROM = 5, /* Mount moved from here */
+ NOTIFY_MOUNT_MOVE_TO = 6, /* Mount moved to here (compare op_id) */
+};
+
+/*
+ * Mount topology/configuration change notification record.
+ * - watch.type = WATCH_TYPE_MOUNT_NOTIFY
+ * - watch.subtype = enum mount_notification_subtype
+ */
+struct mount_notification {
+ struct watch_notification watch; /* WATCH_TYPE_MOUNT_NOTIFY */
+ __u32 triggered_on; /* The mount that the notify was on */
+ __u32 changed_mount; /* The mount that got changed */
+};
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index d1d9d76cae1e..97b025e7863c 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -88,6 +88,9 @@ COND_SYSCALL(ioprio_get);
/* fs/locks.c */
COND_SYSCALL(flock);
+/* fs/mount_notify.c */
+COND_SYSCALL(mount_notify);
+
/* fs/namei.c */
/* fs/namespace.c */
^ permalink raw reply related
* [PATCH 2/7] keys: Add a notification facility
From: David Howells @ 2019-05-28 16:02 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155905930702.7587.7100265859075976147.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:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_KEY_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01);
After that, records will be placed into the queue when events occur in
which keys are changed in some way. Records are of the following format:
struct key_notification {
struct watch_notification watch;
__u32 key_id;
__u32 aux;
} *n;
Where:
n->watch.type will be WATCH_TYPE_KEY_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_KEY_REVOKED.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the second argument to
keyctl_watch_key(), shifted.
n->key will be the ID of the affected key.
n->aux will hold subtype-dependent information, such as the key
being linked into the keyring specified by n->key in the case of
NOTIFY_KEY_LINKED.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also that
the queue can be shared between multiple notifications of various types.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/security/keys/core.rst | 58 ++++++++++++++++++++++
include/linux/key.h | 4 ++
include/uapi/linux/keyctl.h | 1
include/uapi/linux/watch_queue.h | 25 ++++++++++
security/keys/Kconfig | 10 ++++
security/keys/compat.c | 2 +
security/keys/gc.c | 5 ++
security/keys/internal.h | 30 +++++++++++-
security/keys/key.c | 37 +++++++++-----
security/keys/keyctl.c | 88 +++++++++++++++++++++++++++++++++-
security/keys/keyring.c | 17 +++++--
security/keys/request_key.c | 4 +-
12 files changed, 257 insertions(+), 24 deletions(-)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 9521c4207f01..05ef58c753f3 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -808,6 +808,7 @@ The keyctl syscall functions are:
A process must have search permission on the key for this function to be
successful.
+
* Compute a Diffie-Hellman shared secret or public key::
long keyctl(KEYCTL_DH_COMPUTE, struct keyctl_dh_params *params,
@@ -1001,6 +1002,63 @@ The keyctl syscall functions are:
written into the output buffer. Verification returns 0 on success.
+ * Watch a key or keyring for changes::
+
+ long keyctl(KEYCTL_WATCH_KEY, key_serial_t key, int queue_fd,
+ const struct watch_notification_filter *filter);
+
+ This will set or remove a watch for changes on the specified key or
+ keyring.
+
+ "key" is the ID of the key to be watched.
+
+ "queue_fd" is a file descriptor referring to an open "/dev/watch_queue"
+ which manages the buffer into which notifications will be delivered.
+
+ "filter" is either NULL to remove a watch or a filter specification to
+ indicate what events are required from the key.
+
+ See Documentation/watch_queue.rst for more information.
+
+ Note that only one watch may be emplaced for any particular { key,
+ queue_fd } combination.
+
+ Notification records look like::
+
+ struct key_notification {
+ struct watch_notification watch;
+ __u32 key_id;
+ __u32 aux;
+ };
+
+ In this, watch::type will be "WATCH_TYPE_KEY_NOTIFY" and subtype will be
+ one of::
+
+ NOTIFY_KEY_INSTANTIATED
+ NOTIFY_KEY_UPDATED
+ NOTIFY_KEY_LINKED
+ NOTIFY_KEY_UNLINKED
+ NOTIFY_KEY_CLEARED
+ NOTIFY_KEY_REVOKED
+ NOTIFY_KEY_INVALIDATED
+ NOTIFY_KEY_SETATTR
+
+ Where these indicate a key being instantiated/rejected, updated, a link
+ being made in a keyring, a link being removed from a keyring, a keyring
+ being cleared, a key being revoked, a key being invalidated or a key
+ having one of its attributes changed (user, group, perm, timeout,
+ restriction).
+
+ If a watched key is deleted, a basic watch_notification will be issued
+ with "type" set to WATCH_TYPE_META and "subtype" set to
+ watch_meta_removal_notification. The watchpoint ID will be set in the
+ "info" field.
+
+ This needs to be configured by enabling:
+
+ "Provide key/keyring change notifications" (KEY_NOTIFICATIONS)
+
+
Kernel Services
===============
diff --git a/include/linux/key.h b/include/linux/key.h
index 7099985e35a9..f1c43852c0c6 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -159,6 +159,9 @@ struct key {
struct list_head graveyard_link;
struct rb_node serial_node;
};
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ struct watch_list *watchers; /* Entities watching this key for changes */
+#endif
struct rw_semaphore sem; /* change vs change sem */
struct key_user *user; /* owner of this key */
void *security; /* security data for this key */
@@ -193,6 +196,7 @@ struct key {
#define KEY_FLAG_ROOT_CAN_INVAL 7 /* set if key can be invalidated by root without permission */
#define KEY_FLAG_KEEP 8 /* set if key should not be removed */
#define KEY_FLAG_UID_KEYRING 9 /* set if key is a user or user session keyring */
+#define KEY_FLAG_SET_WATCH_PROXY 10 /* Set if watch_proxy should be set on added keys */
/* the key type and key description string
* - the desc is used to match a key against search criteria
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index f45ee0f69c0c..e9e7da849619 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -67,6 +67,7 @@
#define KEYCTL_PKEY_SIGN 27 /* Create a public key signature */
#define KEYCTL_PKEY_VERIFY 28 /* Verify a public key signature */
#define KEYCTL_RESTRICT_KEYRING 29 /* Restrict keys allowed to link to a keyring */
+#define KEYCTL_WATCH_KEY 30 /* Watch a key or ring of keys for changes */
/* keyctl structures */
struct keyctl_dh_params {
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 01746982c2ba..e3bb35a480ae 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -79,4 +79,29 @@ struct watch_notification_filter {
struct watch_notification_type_filter filters[];
};
+/*
+ * Type of key/keyring change notification.
+ */
+enum key_notification_subtype {
+ NOTIFY_KEY_INSTANTIATED = 0, /* Key was instantiated (aux is error code) */
+ NOTIFY_KEY_UPDATED = 1, /* Key was updated */
+ NOTIFY_KEY_LINKED = 2, /* Key (aux) was added to watched keyring */
+ NOTIFY_KEY_UNLINKED = 3, /* Key (aux) was removed from watched keyring */
+ NOTIFY_KEY_CLEARED = 4, /* Keyring was cleared */
+ NOTIFY_KEY_REVOKED = 5, /* Key was revoked */
+ NOTIFY_KEY_INVALIDATED = 6, /* Key was invalidated */
+ NOTIFY_KEY_SETATTR = 7, /* Key's attributes got changed */
+};
+
+/*
+ * Key/keyring notification record.
+ * - watch.type = WATCH_TYPE_KEY_NOTIFY
+ * - watch.subtype = enum key_notification_type
+ */
+struct key_notification {
+ struct watch_notification watch;
+ __u32 key_id; /* The key/keyring affected */
+ __u32 aux; /* Per-type auxiliary data */
+};
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index 6462e6654ccf..fbe064fa0a17 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -101,3 +101,13 @@ config KEY_DH_OPERATIONS
in the kernel.
If you are unsure as to whether this is required, answer N.
+
+config KEY_NOTIFICATIONS
+ bool "Provide key/keyring change notifications"
+ depends on KEYS
+ select WATCH_QUEUE
+ help
+ This option provides support for getting change notifications on keys
+ and keyrings on which the caller has View permission. This makes use
+ of the /dev/watch_queue misc device to handle the notification
+ buffer and provides KEYCTL_WATCH_KEY to enable/disable watches.
diff --git a/security/keys/compat.c b/security/keys/compat.c
index 9482df601dc3..021d8e1c9233 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -158,6 +158,8 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
case KEYCTL_PKEY_VERIFY:
return keyctl_pkey_verify(compat_ptr(arg2), compat_ptr(arg3),
compat_ptr(arg4), compat_ptr(arg5));
+ case KEYCTL_WATCH_KEY:
+ return keyctl_watch_key(arg2, arg3, arg4);
default:
return -EOPNOTSUPP;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 634e96b380e8..b685b9a85a9e 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -135,6 +135,11 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
kdebug("- %u", key->serial);
key_check(key);
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ remove_watch_list(key->watchers);
+ key->watchers = NULL;
+#endif
+
/* Throw away the key data if the key is instantiated */
if (state == KEY_IS_POSITIVE && key->type->destroy)
key->type->destroy(key);
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 8f533c81aa8d..a7ac0f823ade 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -19,6 +19,7 @@
#include <linux/task_work.h>
#include <linux/keyctl.h>
#include <linux/refcount.h>
+#include <linux/watch_queue.h>
#include <linux/compat.h>
struct iovec;
@@ -97,7 +98,8 @@ extern int __key_link_begin(struct key *keyring,
const struct keyring_index_key *index_key,
struct assoc_array_edit **_edit);
extern int __key_link_check_live_key(struct key *keyring, struct key *key);
-extern void __key_link(struct key *key, struct assoc_array_edit **_edit);
+extern void __key_link(struct key *keyring, struct key *key,
+ struct assoc_array_edit **_edit);
extern void __key_link_end(struct key *keyring,
const struct keyring_index_key *index_key,
struct assoc_array_edit *edit);
@@ -178,6 +180,23 @@ extern int key_task_permission(const key_ref_t key_ref,
const struct cred *cred,
key_perm_t perm);
+static inline void notify_key(struct key *key,
+ enum key_notification_subtype subtype, u32 aux)
+{
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ struct key_notification n = {
+ .watch.type = WATCH_TYPE_KEY_NOTIFY,
+ .watch.subtype = subtype,
+ .watch.info = sizeof(n),
+ .key_id = key_serial(key),
+ .aux = aux,
+ };
+
+ post_watch_notification(key->watchers, &n.watch, current_cred(),
+ n.key_id);
+#endif
+}
+
/*
* Check to see whether permission is granted to use a key in the desired way.
*/
@@ -324,6 +343,15 @@ static inline long keyctl_pkey_e_d_s(int op,
}
#endif
+#ifdef CONFIG_KEY_NOTIFICATIONS
+extern long keyctl_watch_key(key_serial_t, int, int);
+#else
+static inline long keyctl_watch_key(key_serial_t key_id, int watch_fd, int watch_id)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
/*
* Debugging key validation
*/
diff --git a/security/keys/key.c b/security/keys/key.c
index 696f1c092c50..9d9f94992470 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -412,6 +412,7 @@ static void mark_key_instantiated(struct key *key, int reject_error)
*/
smp_store_release(&key->state,
(reject_error < 0) ? reject_error : KEY_IS_POSITIVE);
+ notify_key(key, NOTIFY_KEY_INSTANTIATED, reject_error);
}
/*
@@ -454,7 +455,7 @@ static int __key_instantiate_and_link(struct key *key,
if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
set_bit(KEY_FLAG_KEEP, &key->flags);
- __key_link(key, _edit);
+ __key_link(keyring, key, _edit);
}
/* disable the authorisation key */
@@ -603,7 +604,7 @@ int key_reject_and_link(struct key *key,
/* and link it into the destination keyring */
if (keyring && link_ret == 0)
- __key_link(key, &edit);
+ __key_link(keyring, key, &edit);
/* disable the authorisation key */
if (authkey)
@@ -756,9 +757,11 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
down_write(&key->sem);
ret = key->type->update(key, prep);
- if (ret == 0)
+ if (ret == 0) {
/* Updating a negative key positively instantiates it */
mark_key_instantiated(key, 0);
+ notify_key(key, NOTIFY_KEY_UPDATED, 0);
+ }
up_write(&key->sem);
@@ -999,9 +1002,11 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
down_write(&key->sem);
ret = key->type->update(key, &prep);
- if (ret == 0)
+ if (ret == 0) {
/* Updating a negative key positively instantiates it */
mark_key_instantiated(key, 0);
+ notify_key(key, NOTIFY_KEY_UPDATED, 0);
+ }
up_write(&key->sem);
@@ -1033,15 +1038,17 @@ void key_revoke(struct key *key)
* instantiated
*/
down_write_nested(&key->sem, 1);
- if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) &&
- key->type->revoke)
- key->type->revoke(key);
-
- /* set the death time to no more than the expiry time */
- time = ktime_get_real_seconds();
- if (key->revoked_at == 0 || key->revoked_at > time) {
- key->revoked_at = time;
- key_schedule_gc(key->revoked_at + key_gc_delay);
+ if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags)) {
+ notify_key(key, NOTIFY_KEY_REVOKED, 0);
+ if (key->type->revoke)
+ key->type->revoke(key);
+
+ /* set the death time to no more than the expiry time */
+ time = ktime_get_real_seconds();
+ if (key->revoked_at == 0 || key->revoked_at > time) {
+ key->revoked_at = time;
+ key_schedule_gc(key->revoked_at + key_gc_delay);
+ }
}
up_write(&key->sem);
@@ -1063,8 +1070,10 @@ void key_invalidate(struct key *key)
if (!test_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
down_write_nested(&key->sem, 1);
- if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags))
+ if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
+ notify_key(key, NOTIFY_KEY_INVALIDATED, 0);
key_schedule_gc_links();
+ }
up_write(&key->sem);
}
}
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 3e4053a217c3..cc2e6feafbc7 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -914,6 +914,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
if (group != (gid_t) -1)
key->gid = gid;
+ notify_key(key, NOTIFY_KEY_SETATTR, 0);
ret = 0;
error_put:
@@ -964,6 +965,7 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
/* if we're not the sysadmin, we can only change a key that we own */
if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
key->perm = perm;
+ notify_key(key, NOTIFY_KEY_SETATTR, 0);
ret = 0;
}
@@ -1355,10 +1357,12 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
okay:
key = key_ref_to_ptr(key_ref);
ret = 0;
- if (test_bit(KEY_FLAG_KEEP, &key->flags))
+ if (test_bit(KEY_FLAG_KEEP, &key->flags)) {
ret = -EPERM;
- else
+ } else {
key_set_timeout(key, timeout);
+ notify_key(key, NOTIFY_KEY_SETATTR, 0);
+ }
key_put(key);
error:
@@ -1631,6 +1635,83 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
return ret;
}
+#ifdef CONFIG_KEY_NOTIFICATIONS
+/*
+ * Watch for changes to a key.
+ *
+ * The caller must have View permission to watch a key or keyring.
+ */
+long keyctl_watch_key(key_serial_t id, int watch_queue_fd, int watch_id)
+{
+ struct watch_queue *wqueue;
+ struct watch_list *wlist = NULL;
+ struct watch *watch;
+ struct key *key;
+ key_ref_t key_ref;
+ long ret = -ENOMEM;
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+
+ key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE, KEY_NEED_VIEW);
+ if (IS_ERR(key_ref))
+ return PTR_ERR(key_ref);
+ key = key_ref_to_ptr(key_ref);
+
+ wqueue = get_watch_queue(watch_queue_fd);
+ if (IS_ERR(wqueue)) {
+ ret = PTR_ERR(wqueue);
+ goto err_key;
+ }
+
+ if (watch_id >= 0) {
+ if (!key->watchers) {
+ wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+ if (!wlist)
+ goto err_wqueue;
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+ }
+
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wlist;
+
+ init_watch(watch, wqueue);
+ watch->id = key->serial;
+ watch->info_id = (u32)watch_id << 24;
+
+ 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)
+ kfree(watch);
+ } else if (key->watchers) {
+ down_write(&key->sem);
+ ret = remove_watch_from_object(key->watchers,
+ wqueue, key_serial(key),
+ false);
+ up_write(&key->sem);
+ } else {
+ ret = -EBADSLT;
+ }
+
+err_wlist:
+ kfree(wlist);
+err_wqueue:
+ put_watch_queue(wqueue);
+err_key:
+ key_put(key);
+ return ret;
+}
+#endif /* CONFIG_KEY_NOTIFICATIONS */
+
/*
* The key control system call
*/
@@ -1771,6 +1852,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
(const void __user *)arg4,
(const void __user *)arg5);
+ case KEYCTL_WATCH_KEY:
+ return keyctl_watch_key((key_serial_t)arg2, (int)arg3, (int)arg4);
+
default:
return -EOPNOTSUPP;
}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index e14f09e3a4b0..f0f9ab3c5587 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -1018,12 +1018,14 @@ int keyring_restrict(key_ref_t keyring_ref, const char *type,
down_write(&keyring->sem);
down_write(&keyring_serialise_restrict_sem);
- if (keyring->restrict_link)
+ if (keyring->restrict_link) {
ret = -EEXIST;
- else if (keyring_detect_restriction_cycle(keyring, restrict_link))
+ } else if (keyring_detect_restriction_cycle(keyring, restrict_link)) {
ret = -EDEADLK;
- else
+ } else {
keyring->restrict_link = restrict_link;
+ notify_key(keyring, NOTIFY_KEY_SETATTR, 0);
+ }
up_write(&keyring_serialise_restrict_sem);
up_write(&keyring->sem);
@@ -1286,12 +1288,14 @@ int __key_link_check_live_key(struct key *keyring, struct key *key)
* holds at most one link to any given key of a particular type+description
* combination.
*/
-void __key_link(struct key *key, struct assoc_array_edit **_edit)
+void __key_link(struct key *keyring, struct key *key,
+ struct assoc_array_edit **_edit)
{
__key_get(key);
assoc_array_insert_set_object(*_edit, keyring_key_to_ptr(key));
assoc_array_apply_edit(*_edit);
*_edit = NULL;
+ notify_key(keyring, NOTIFY_KEY_LINKED, key_serial(key));
}
/*
@@ -1369,7 +1373,7 @@ int key_link(struct key *keyring, struct key *key)
if (ret == 0)
ret = __key_link_check_live_key(keyring, key);
if (ret == 0)
- __key_link(key, &edit);
+ __key_link(keyring, key, &edit);
__key_link_end(keyring, &key->index_key, edit);
}
@@ -1398,6 +1402,7 @@ EXPORT_SYMBOL(key_link);
int key_unlink(struct key *keyring, struct key *key)
{
struct assoc_array_edit *edit;
+ key_serial_t target = key_serial(key);
int ret;
key_check(keyring);
@@ -1419,6 +1424,7 @@ int key_unlink(struct key *keyring, struct key *key)
goto error;
assoc_array_apply_edit(edit);
+ notify_key(keyring, NOTIFY_KEY_UNLINKED, target);
key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES);
ret = 0;
@@ -1452,6 +1458,7 @@ int keyring_clear(struct key *keyring)
} else {
if (edit)
assoc_array_apply_edit(edit);
+ notify_key(keyring, NOTIFY_KEY_CLEARED, 0);
key_payload_reserve(keyring, 0);
ret = 0;
}
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 75d87f9e0f49..5f474d0e8620 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -387,7 +387,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
goto key_already_present;
if (dest_keyring)
- __key_link(key, &edit);
+ __key_link(dest_keyring, key, &edit);
mutex_unlock(&key_construction_mutex);
if (dest_keyring)
@@ -406,7 +406,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
if (dest_keyring) {
ret = __key_link_check_live_key(dest_keyring, key);
if (ret == 0)
- __key_link(key, &edit);
+ __key_link(dest_keyring, key, &edit);
__key_link_end(dest_keyring, &ctx->index_key, edit);
if (ret < 0)
goto link_check_failed;
^ permalink raw reply related
* [PATCH 1/7] General notification queue with user mmap()'able ring buffer
From: David Howells @ 2019-05-28 16:01 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155905930702.7587.7100265859075976147.stgit@warthog.procyon.org.uk>
Implement a misc device that implements a general notification queue as a
ring buffer that can be mmap()'d from userspace.
The way this is done is:
(1) An application opens the device and indicates the size of the ring
buffer that it wants to reserve in pages (this can only be set once):
fd = open("/dev/watch_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_NR_PAGES, nr_of_pages);
(2) The application should then map the pages that the device has
reserved. Each instance of the device created by open() allocates
separate pages so that maps of different fds don't interfere with one
another. Multiple mmap() calls on the same fd, however, will all work
together.
page_size = sysconf(_SC_PAGESIZE);
mapping_size = nr_of_pages * page_size;
char *buf = mmap(NULL, mapping_size, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
The ring is divided into 8-byte slots. Entries written into the ring are
variable size and can use between 1 and 63 slots. A special entry is
maintained in the first two slots of the ring that contains the head and
tail pointers. This is skipped when the ring wraps round. Note that
multislot entries, therefore, aren't allowed to be broken over the end of
the ring, but instead "skip" entries are inserted to pad out the buffer.
Each entry has a 1-slot header that describes it:
struct watch_notification {
__u32 type:24;
__u32 subtype:8;
__u32 info;
};
The type indicates the source (eg. mount tree changes, superblock events,
keyring changes, block layer events) and the subtype indicates the event
type (eg. mount, unmount; EIO, EDQUOT; link, unlink). The info field
indicates a number of things, including the entry length, an ID assigned to
a watchpoint contributing to this buffer, type-specific flags and meta
flags, such as an overrun indicator.
Supplementary data, such as the key ID that generated an event, are
attached in additional slots.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/watch_queue.rst | 311 +++++++++++++
drivers/misc/Kconfig | 13 +
drivers/misc/Makefile | 1
drivers/misc/watch_queue.c | 877 ++++++++++++++++++++++++++++++++++++++
include/linux/lsm_hooks.h | 15 +
include/linux/security.h | 14 +
include/linux/watch_queue.h | 86 ++++
include/uapi/linux/watch_queue.h | 82 ++++
mm/interval_tree.c | 2
mm/memory.c | 1
security/security.c | 9
11 files changed, 1411 insertions(+)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 drivers/misc/watch_queue.c
create mode 100644 include/linux/watch_queue.h
create mode 100644 include/uapi/linux/watch_queue.h
diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
new file mode 100644
index 000000000000..01fe937092d6
--- /dev/null
+++ b/Documentation/watch_queue.rst
@@ -0,0 +1,311 @@
+============================
+Mappable notifications queue
+============================
+
+This is a misc device that acts as a mapped ring buffer by which userspace can
+receive notifications from the kernel. This is can be used in conjunction
+with::
+
+ * Key/keyring notifications
+
+ * Mount topology change notifications
+
+ * Superblock event notifications
+
+
+The notifications buffers can be enabled by:
+
+ "Device Drivers"/"Misc devices"/"Mappable notification queue"
+ (CONFIG_WATCH_QUEUE)
+
+This document has the following sections:
+
+.. contents:: :local:
+
+
+Overview
+========
+
+This facility appears as a misc device file that is opened and then mapped and
+polled. Each time it is opened, it creates a new buffer specific to the
+returned file descriptor. Then, when the opening process sets watches, it
+indicates that particular buffer it wants notifications from that watch to be
+written into. Note that there are no read() and write() methods (except for
+debugging). The user is expected to access the ring directly and to use poll
+to wait for new data.
+
+If a watch is in place, notifications are only written into the buffer if the
+filter criteria are passed and if there's sufficient space available in the
+ring. If neither of those is so, a notification will be discarded. In the
+latter case, an overrun indicator will also be set.
+
+Note that when producing a notification, the kernel does not wait for the
+consumers to collect it, but rather just continues on. This means that
+notifications can be generated whilst spinlocks are held and also protects the
+kernel from being held up indefinitely by a userspace malfunction.
+
+As far as the ring goes, the head index belongs to the kernel and the tail
+index belongs to userspace. The kernel will refuse to write anything if the
+tail index becomes invalid. Userspace *must* use appropriate memory barriers
+between reading or updating the tail index and reading the ring.
+
+
+Record Structure
+================
+
+Notification records in the ring may occupy a variable number of slots within
+the buffer, beginning with a 1-slot header::
+
+ struct watch_notification {
+ __u16 type;
+ __u16 subtype;
+ __u32 info;
+ };
+
+"type" indicates the source of the notification record and "subtype" indicates
+the type of record from that source (see the Watch Sources section below). The
+type may also be "WATCH_TYPE_META". This is a special record type generated
+internally by the watch queue driver itself. There are two subtypes, one of
+which indicates records that should be just skipped (padding or metadata):
+
+ * WATCH_META_SKIP_NOTIFICATION
+ * WATCH_META_REMOVAL_NOTIFICATION
+
+The former indicates a record that should just be skipped and the latter
+indicates that an object on which a watchpoint was installed was removed or
+destroyed.
+
+"info" indicates a bunch of things, including:
+
+ * The length of the record (mask with WATCH_INFO_LENGTH). This indicates the
+ size of the record, which may be between 1 and 63 slots. Note that this is
+ placed appropriately within the info value so that no shifting is required
+ to convert number of occupied slots to byte length.
+
+ * The watchpoint ID (mask with WATCH_INFO_ID). This indicates that caller's
+ ID of the watchpoint, which may be between 0 and 255. Multiple watchpoints
+ may share a queue, and this provides a means to distinguish them.
+
+ * A buffer overrun flag (WATCH_INFO_OVERRUN flag). If this is set in a
+ notification record, some of the preceding records were discarded.
+
+ * An ENOMEM-loss flag (WATCH_INFO_ENOMEM flag). This is set to indicate that
+ an event was lost to ENOMEM.
+
+ * A recursive-change flag (WATCH_INFO_RECURSIVE flag). This is set to
+ indicate that the change that happened was recursive - for instance
+ changing the attributes on an entire mount subtree.
+
+ * An exact-match flag (WATCH_INFO_IN_SUBTREE flag). This is set if the event
+ didn't happen exactly at the watchpoint, but rather somewhere in the
+ subtree thereunder.
+
+ * Some type-specific flags (WATCH_INFO_TYPE_FLAGS). These are set by the
+ notification producer to indicate some meaning to the kernel.
+
+Everything in info apart from the length can be used for filtering.
+
+
+Ring Structure
+==============
+
+The ring is divided into 8-byte slots. The caller uses an ioctl() to set the
+size of the ring after opening and this must be a power-of-2 multiple of the
+system page size (so that the mask can be used with AND).
+
+The head and tail indices are stored in the first two slots in the ring, which
+are marked out as a skippable entry::
+
+ struct watch_queue_buffer {
+ union {
+ struct {
+ struct watch_notification watch;
+ volatile __u32 head;
+ volatile __u32 tail;
+ __u32 mask;
+ } meta;
+ struct watch_notification slots[0];
+ };
+ };
+
+In "meta.watch", type will be set to WATCH_TYPE_META and subtype to
+WATCH_META_SKIP_NOTIFICATION so that anyone processing the buffer will just
+skip this record. Also, because this record is here, records cannot wrap round
+the end of the buffer, so a skippable padding element will be inserted at the
+end of the buffer if needed. Thus the contents of a notification record in the
+buffer are always contiguous.
+
+"meta.mask" is an AND'able mask to turn the index counters into slots array
+indices.
+
+The buffer is empty if "meta.head" == "meta.tail".
+
+[!] NOTE that the ring indices "meta.head" and "meta.tail" are indices into
+"slots[]" not byte offsets into the buffer.
+
+[!] NOTE that userspace must never change the head pointer. This belongs to
+the kernel and will be updated by that. The kernel will never change the tail
+pointer.
+
+[!] NOTE that userspace must never AND-off the tail pointer before updating it,
+but should just keep adding to it and letting it wrap naturally. The value
+*should* be masked off when used as an index into slots[].
+
+[!] NOTE that if the distance between head and tail becomes too great, the
+kernel will assume the buffer is full and write no more until the issue is
+resolved.
+
+
+Watch Sources
+=============
+
+Any particular buffer can be fed from multiple sources. Sources include:
+
+ * WATCH_TYPE_MOUNT_NOTIFY
+
+ Notifications of this type indicate mount tree topology changes and mount
+ attribute changes. A watchpoint can be set on a particular file or
+ directory and notifications from the path subtree rooted at that point will
+ be intercepted.
+
+ * WATCH_TYPE_SB_NOTIFY
+
+ Notifications of this type indicate superblock events, such as quota limits
+ being hit, I/O errors being produced or network server loss/reconnection.
+ Watchpoints of this type are set directly on superblocks.
+
+ * WATCH_TYPE_KEY_NOTIFY
+
+ Notifications of this type indicate changes to keys and keyrings, including
+ the changes of keyring contents or the attributes of keys.
+
+ See Documentation/security/keys/core.rst for more information.
+
+ * WATCH_TYPE_BLOCK_NOTIFY
+
+ Notifications of this type indicate block layer events, such as I/O errors
+ or temporary link loss. Watchpoints of this type are set on a global
+ queue.
+
+
+Configuring Watchpoints
+=======================
+
+When a watchpoint is set up, the caller assigns an ID and can set filtering
+parameters. The following structure is filled out and passed to the
+watchpoint creation system call::
+
+ struct watch_notification_filter {
+ __u64 subtype_filter[4];
+ __u32 info_filter;
+ __u32 info_mask;
+ __u32 info_id;
+ __u32 __reserved;
+ };
+
+"subtype_filter" is a bitmask indicating the subtypes that are of interest. In
+this version of the structure, only the first 256 subtypes are supported. Bit
+0 of subtype_filter[0] corresponds to subtype 0, bit 1 to subtype 1, and so on.
+
+"info_filter" and "info_mask" act as a filter on the info field of the
+notification record. The notification is only written into the buffer if::
+
+ (watch.info & info_mask) == info_filter
+
+This can be used, for example, to ignore events that are not exactly on the
+watched point in a mount tree by specifying WATCH_INFO_IN_SUBTREE must be 0.
+
+"info_id" is OR'd into watch.info. This indicates the watchpoint ID in the top
+8 bits. All bits outside of WATCH_INFO_ID must be 0.
+
+"__reserved" must be 0.
+
+If the pointer to this structure is NULL, this indicates to the system call
+that the watchpoint should be removed.
+
+
+Polling
+=======
+
+The file descriptor that holds the buffer may be used with poll() and similar.
+POLLIN and POLLRDNORM are set if the buffer indices differ. POLLERR is set if
+the buffer indices are further apart than the size of the buffer. Wake-up
+events are only generated if the buffer is transitioned from an empty state.
+
+
+Example
+=======
+
+A buffer is created with something like the following::
+
+ fd = open("/dev/watch_queue", O_RDWR);
+
+ #define BUF_SIZE 4
+ ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE);
+
+ page_size = sysconf(_SC_PAGESIZE);
+ buf = mmap(NULL, BUF_SIZE * page_size,
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+It can then be set to receive mount topology change notifications, keyring
+change notifications and superblock notifications::
+
+ memset(&filter, 0, sizeof(filter));
+ filter.subtype_filter[0] = ~0ULL;
+ filter.info_mask = WATCH_INFO_IN_SUBTREE;
+ filter.info_filter = 0;
+ filter.info_id = 0x01000000;
+
+ keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fd, &filter);
+
+ mount_notify(AT_FDCWD, "/", 0, fd, &filter);
+
+ sb_notify(AT_FDCWD, "/", 0, fd, &filter);
+
+The notifications can then be consumed by something like the following::
+
+ extern void saw_mount_change(struct watch_notification *n);
+ extern void saw_key_change(struct watch_notification *n);
+
+ static int consumer(int fd, struct watch_queue_buffer *buf)
+ {
+ struct watch_notification *n;
+ struct pollfd p[1];
+ unsigned int head, tail, mask = buf->meta.mask;
+
+ for (;;) {
+ p[0].fd = fd;
+ p[0].events = POLLIN | POLLERR;
+ p[0].revents = 0;
+
+ if (poll(p, 1, -1) == -1 || p[0].revents & POLLERR)
+ goto went_wrong;
+
+ while (head = _atomic_load_acquire(buf->meta.head),
+ tail = buf->meta.tail,
+ tail != head
+ ) {
+ n = &buf->slots[tail & mask];
+ if ((n->info & WATCH_INFO_LENGTH) == 0)
+ goto went_wrong;
+
+ switch (n->type) {
+ case WATCH_TYPE_MOUNT_NOTIFY:
+ saw_mount_change(n);
+ break;
+ case WATCH_TYPE_KEY_NOTIFY:
+ saw_key_change(n);
+ break;
+ }
+
+ tail += (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+ _atomic_store_release(buf->meta.tail, tail);
+ }
+ }
+
+ went_wrong:
+ return 0;
+ }
+
+Note the memory barriers when loading the head pointer and storing the tail
+pointer!
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6a0365b2332c..19668c0ebe03 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -4,6 +4,19 @@
menu "Misc devices"
+config WATCH_QUEUE
+ bool "Mappable notification queue"
+ default n
+ depends on MMU
+ help
+ This is a general notification queue for the kernel to pass events to
+ userspace through a mmap()'able ring buffer. It can be used in
+ conjunction with watches for mount topology change notifications,
+ superblock change notifications and key/keyring change notifications.
+
+ Note that in theory this should work fine with NOMMU, but I'm not
+ sure how to make that work.
+
config SENSORS_LIS3LV02D
tristate
depends on INPUT
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index b9affcdaa3d6..bf16acd9f8cc 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -3,6 +3,7 @@
# Makefile for misc devices that really don't fit anywhere else.
#
+obj-$(CONFIG_WATCH_QUEUE) += watch_queue.o
obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_IBMVMC) += ibmvmc.o
obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o
diff --git a/drivers/misc/watch_queue.c b/drivers/misc/watch_queue.c
new file mode 100644
index 000000000000..39a09ea15d97
--- /dev/null
+++ b/drivers/misc/watch_queue.c
@@ -0,0 +1,877 @@
+/* User-mappable watch queue
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ *
+ * See Documentation/watch_queue.rst
+ */
+
+#define pr_fmt(fmt) "watchq: " fmt
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/printk.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/file.h>
+#include <linux/security.h>
+#include <linux/cred.h>
+#include <linux/watch_queue.h>
+
+#define DEBUG_WITH_WRITE /* Allow use of write() to record notifications */
+
+MODULE_DESCRIPTION("Watch queue");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL");
+
+struct watch_type_filter {
+ enum watch_notification_type type;
+ __u32 subtype_filter[1]; /* Bitmask of subtypes to filter on */
+ __u32 info_filter; /* Filter on watch_notification::info */
+ __u32 info_mask; /* Mask of relevant bits in info_filter */
+};
+
+struct watch_filter {
+ union {
+ struct rcu_head rcu;
+ unsigned long type_filter[2]; /* Bitmask of accepted types */
+ };
+ u32 nr_filters; /* Number of filters */
+ struct watch_type_filter filters[];
+};
+
+struct watch_queue {
+ struct rcu_head rcu;
+ struct address_space mapping;
+ const struct cred *cred; /* Creds of the owner of the queue */
+ struct watch_filter __rcu *filter;
+ wait_queue_head_t waiters;
+ struct hlist_head watches; /* Contributory watches */
+ refcount_t usage;
+ spinlock_t lock;
+ bool defunct; /* T when queues closed */
+ u8 nr_pages; /* Size of pages[] */
+ u8 flag_next; /* Flag to apply to next item */
+#ifdef DEBUG_WITH_WRITE
+ u8 debug;
+#endif
+ u32 size;
+ struct watch_queue_buffer *buffer; /* Pointer to first record */
+
+ /* The mappable pages. The zeroth page holds the ring pointers. */
+ struct page **pages;
+};
+
+/**
+ * post_one_notification - Post an event notification to one queue
+ * @wqueue: The watch queue to add the event to.
+ * @n: The notification record to post.
+ * @cred: The credentials to use in security checks.
+ *
+ * Post a notification of an event into an mmap'd queue and let the user know.
+ * Returns true if successful and false on failure (eg. buffer overrun or
+ * userspace mucked up the ring indices).
+ *
+ *
+ * The size of the notification should be set in n->flags & WATCH_LENGTH and
+ * should be in units of sizeof(*n).
+ */
+static bool post_one_notification(struct watch_queue *wqueue,
+ struct watch_notification *n,
+ const struct cred *cred)
+{
+ struct watch_queue_buffer *buf = wqueue->buffer;
+ unsigned int metalen = sizeof(buf->meta) / sizeof(buf->slots[0]);
+ unsigned int size = wqueue->size, mask = size - 1;
+ unsigned int len;
+ unsigned int ring_tail, tail, head, used, segment, h;
+
+ if (!buf)
+ return false;
+
+ len = (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+ if (len == 0)
+ return false;
+
+ spin_lock_bh(&wqueue->lock); /* Protect head pointer */
+
+ if (wqueue->defunct ||
+ security_post_notification(wqueue->cred, cred, n) < 0)
+ goto out;
+
+ ring_tail = READ_ONCE(buf->meta.tail);
+ head = READ_ONCE(buf->meta.head);
+ used = head - ring_tail;
+
+ /* Check to see if userspace mucked up the pointers */
+ if (used >= size)
+ goto overrun;
+ tail = ring_tail & mask;
+ if (tail > 0 && tail < metalen)
+ goto overrun;
+
+ h = head & mask;
+ if (h >= tail) {
+ /* Head is at or after tail in the buffer. There may then be
+ * two segments: one to the end of buffer and one at the
+ * beginning of the buffer between the metadata block and the
+ * tail pointer.
+ */
+ segment = size - h;
+ if (len > segment) {
+ /* Not enough space in the post-head segment; we need
+ * to wrap. When wrapping, we will have to skip the
+ * metadata at the beginning of the buffer.
+ */
+ if (len > tail - metalen)
+ goto overrun;
+
+ /* Fill the space at the end of the page */
+ buf->slots[h].type = WATCH_TYPE_META;
+ buf->slots[h].subtype = WATCH_META_SKIP_NOTIFICATION;
+ buf->slots[h].info = segment << WATCH_LENGTH_SHIFT;
+ head += segment;
+ h = 0;
+ if (h >= tail)
+ goto overrun;
+ }
+ }
+
+ if (h == 0) {
+ /* Reset and skip the header metadata */
+ buf->meta.watch.type = WATCH_TYPE_META;
+ buf->meta.watch.subtype = WATCH_META_SKIP_NOTIFICATION;
+ buf->meta.watch.info = metalen << WATCH_LENGTH_SHIFT;
+ head += metalen;
+ h = metalen;
+ if (h >= tail)
+ goto overrun;
+ }
+
+ if (h < tail) {
+ /* Head is before tail in the buffer. There may be one segment
+ * between the two, but we may need to skip the metadata block.
+ */
+ segment = tail - h;
+ if (len > segment)
+ goto overrun;
+ }
+
+ n->info |= wqueue->flag_next;
+ wqueue->flag_next = 0;
+ memcpy(buf->slots + h, n, len * sizeof(buf->slots[0]));
+ head += len;
+
+ smp_store_release(&buf->meta.head, head);
+ spin_unlock_bh(&wqueue->lock);
+ if (used == 0)
+ wake_up(&wqueue->waiters);
+ return true;
+
+overrun:
+ wqueue->flag_next = WATCH_INFO_OVERRUN;
+out:
+ spin_unlock_bh(&wqueue->lock);
+ return false;
+}
+
+/*
+ * Apply filter rules to a notification.
+ */
+static bool filter_watch_notification(const struct watch_filter *wf,
+ const struct watch_notification *n)
+{
+ const struct watch_type_filter *wt;
+ int i;
+
+ if (!test_bit(n->type, wf->type_filter))
+ return false;
+
+ for (i = 0; i < wf->nr_filters; i++) {
+ wt = &wf->filters[i];
+ if (n->type == wt->type &&
+ ((1U << n->subtype) & wt->subtype_filter[0]) &&
+ (n->info & wt->info_mask) == wt->info_filter)
+ return true;
+ }
+
+ return false; /* If there is a filter, the default is to reject. */
+}
+
+/**
+ * __post_watch_notification - Post an event notification
+ * @wlist: The watch list to post the event to.
+ * @n: The notification record to post.
+ * @cred: The creds of the process that triggered the notification.
+ * @id: The ID to match on the watch.
+ *
+ * Post a notification of an event into a set of watch queues and let the users
+ * know.
+ *
+ * If @n is NULL then WATCH_INFO_LENGTH will be set on the next event posted.
+ *
+ * The size of the notification should be set in n->info & WATCH_INFO_LENGTH and
+ * should be in units of sizeof(*n).
+ */
+void __post_watch_notification(struct watch_list *wlist,
+ struct watch_notification *n,
+ const struct cred *cred,
+ u64 id)
+{
+ const struct watch_filter *wf;
+ struct watch_queue *wqueue;
+ struct watch *watch;
+
+ rcu_read_lock();
+
+ hlist_for_each_entry_rcu(watch, &wlist->watchers, list_node) {
+ if (watch->id != id)
+ continue;
+ n->info &= ~(WATCH_INFO_ID | WATCH_INFO_OVERRUN);
+ n->info |= watch->info_id;
+
+ wqueue = rcu_dereference(watch->queue);
+ wf = rcu_dereference(wqueue->filter);
+ if (wf && !filter_watch_notification(wf, n))
+ continue;
+
+ post_one_notification(wqueue, n, cred);
+ }
+
+ rcu_read_unlock();
+}
+EXPORT_SYMBOL(__post_watch_notification);
+
+/*
+ * Allow the queue to be polled.
+ */
+static __poll_t watch_queue_poll(struct file *file, poll_table *wait)
+{
+ struct watch_queue *wqueue = file->private_data;
+ struct watch_queue_buffer *buf = wqueue->buffer;
+ unsigned int head, tail;
+ __poll_t mask = 0;
+
+ poll_wait(file, &wqueue->waiters, wait);
+
+ head = READ_ONCE(buf->meta.head);
+ tail = READ_ONCE(buf->meta.tail);
+ if (head != tail)
+ mask |= EPOLLIN | EPOLLRDNORM;
+ if (head - tail > wqueue->size)
+ mask |= EPOLLERR;
+ return mask;
+}
+
+static int watch_queue_set_page_dirty(struct page *page)
+{
+ SetPageDirty(page);
+ return 0;
+}
+
+static const struct address_space_operations watch_queue_aops = {
+ .set_page_dirty = watch_queue_set_page_dirty,
+};
+
+static vm_fault_t watch_queue_fault(struct vm_fault *vmf)
+{
+ struct watch_queue *wqueue = vmf->vma->vm_file->private_data;
+ struct page *page;
+
+ page = wqueue->pages[vmf->pgoff];
+ get_page(page);
+ if (!lock_page_or_retry(page, vmf->vma->vm_mm, vmf->flags)) {
+ put_page(page);
+ return VM_FAULT_RETRY;
+ }
+ vmf->page = page;
+ return VM_FAULT_LOCKED;
+}
+
+static void watch_queue_map_pages(struct vm_fault *vmf,
+ pgoff_t start_pgoff, pgoff_t end_pgoff)
+{
+ struct watch_queue *wqueue = vmf->vma->vm_file->private_data;
+ struct page *page;
+
+ rcu_read_lock();
+
+ do {
+ page = wqueue->pages[start_pgoff];
+ if (trylock_page(page)) {
+ vm_fault_t ret;
+ get_page(page);
+ ret = alloc_set_pte(vmf, NULL, page);
+ if (ret != 0)
+ put_page(page);
+
+ unlock_page(page);
+ }
+ } while (++start_pgoff < end_pgoff);
+
+ rcu_read_unlock();
+}
+
+static const struct vm_operations_struct watch_queue_vm_ops = {
+ .fault = watch_queue_fault,
+ .map_pages = watch_queue_map_pages,
+};
+
+/*
+ * Map the buffer.
+ */
+static int watch_queue_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct watch_queue *wqueue = file->private_data;
+
+ if (vma->vm_pgoff != 0 ||
+ vma->vm_end - vma->vm_start > wqueue->nr_pages * PAGE_SIZE ||
+ !(pgprot_val(vma->vm_page_prot) & pgprot_val(PAGE_SHARED)))
+ return -EINVAL;
+
+ vma->vm_ops = &watch_queue_vm_ops;
+
+ vma_interval_tree_insert(vma, &wqueue->mapping.i_mmap);
+ return 0;
+}
+
+/*
+ * Allocate the required number of pages.
+ */
+static long watch_queue_set_size(struct watch_queue *wqueue, unsigned long nr_pages)
+{
+ struct watch_queue_buffer *buf;
+ u32 len;
+ int i;
+
+ if (nr_pages == 0 ||
+ nr_pages > 16 || /* TODO: choose a better hard limit */
+ !is_power_of_2(nr_pages))
+ return -EINVAL;
+
+ wqueue->pages = kcalloc(nr_pages, sizeof(struct page *), GFP_KERNEL);
+ if (!wqueue->pages)
+ goto err;
+
+ for (i = 0; i < nr_pages; i++) {
+ wqueue->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!wqueue->pages[i])
+ goto err_some_pages;
+ wqueue->pages[i]->mapping = &wqueue->mapping;
+ SetPageUptodate(wqueue->pages[i]);
+ }
+
+ buf = vmap(wqueue->pages, nr_pages, VM_MAP, PAGE_SHARED);
+ if (!buf)
+ goto err_some_pages;
+
+ wqueue->buffer = buf;
+ wqueue->nr_pages = nr_pages;
+ wqueue->size = ((nr_pages * PAGE_SIZE) / sizeof(struct watch_notification));
+
+ /* The first four slots in the buffer contain metadata about the ring,
+ * including the head and tail indices and mask.
+ */
+ len = sizeof(buf->meta) / sizeof(buf->slots[0]);
+ buf->meta.watch.info = len << WATCH_LENGTH_SHIFT;
+ buf->meta.watch.type = WATCH_TYPE_META;
+ buf->meta.watch.subtype = WATCH_META_SKIP_NOTIFICATION;
+ buf->meta.tail = len;
+ buf->meta.mask = wqueue->size - 1;
+ smp_store_release(&buf->meta.head, len);
+ return 0;
+
+err_some_pages:
+ for (i--; i >= 0; i--) {
+ ClearPageUptodate(wqueue->pages[i]);
+ wqueue->pages[i]->mapping = NULL;
+ put_page(wqueue->pages[i]);
+ }
+
+ kfree(wqueue->pages);
+ wqueue->pages = NULL;
+err:
+ return -ENOMEM;
+}
+
+/*
+ * Set the filter on a watch queue.
+ */
+static long watch_queue_set_filter(struct inode *inode,
+ struct watch_queue *wqueue,
+ struct watch_notification_filter __user *_filter)
+{
+ struct watch_notification_type_filter *tf;
+ struct watch_notification_filter filter;
+ struct watch_type_filter *q;
+ struct watch_filter *wfilter;
+ int ret, nr_filter = 0, i;
+
+ if (!_filter) {
+ /* Remove the old filter */
+ wfilter = NULL;
+ goto set;
+ }
+
+ /* Grab the user's filter specification */
+ if (copy_from_user(&filter, _filter, sizeof(filter)) != 0)
+ return -EFAULT;
+ if (filter.nr_filters == 0 ||
+ filter.nr_filters > 16 ||
+ filter.__reserved != 0)
+ return -EINVAL;
+
+ tf = memdup_user(_filter->filters, filter.nr_filters * sizeof(*tf));
+ if (IS_ERR(tf))
+ return PTR_ERR(tf);
+
+ ret = -EINVAL;
+ for (i = 0; i < filter.nr_filters; i++) {
+ if ((tf[i].info_filter & ~tf[i].info_mask) ||
+ tf[i].info_mask & WATCH_INFO_LENGTH)
+ goto err_filter;
+ /* Ignore any unknown types */
+ if (tf[i].type >= sizeof(wfilter->type_filter) * 8)
+ continue;
+ nr_filter++;
+ }
+
+ /* Now we need to build the internal filter from only the relevant
+ * user-specified filters.
+ */
+ ret = -ENOMEM;
+ wfilter = kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL);
+ if (!wfilter)
+ goto err_filter;
+ wfilter->nr_filters = nr_filter;
+
+ q = wfilter->filters;
+ for (i = 0; i < filter.nr_filters; i++) {
+ if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG)
+ continue;
+
+ q->type = tf[i].type;
+ q->info_filter = tf[i].info_filter;
+ q->info_mask = tf[i].info_mask;
+ q->subtype_filter[0] = tf[i].subtype_filter[0];
+ __set_bit(q->type, wfilter->type_filter);
+ q++;
+ }
+
+ kfree(tf);
+set:
+ rcu_swap_protected(wqueue->filter, wfilter,
+ lockdep_is_held(&inode->i_rwsem));
+ if (wfilter)
+ kfree_rcu(wfilter, rcu);
+ return 0;
+
+err_filter:
+ kfree(tf);
+ return ret;
+}
+
+/*
+ * Set parameters.
+ */
+static long watch_queue_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct watch_queue *wqueue = file->private_data;
+ struct inode *inode = file_inode(file);
+ long ret;
+
+ switch (cmd) {
+ case IOC_WATCH_QUEUE_SET_SIZE:
+ if (wqueue->buffer)
+ return -EBUSY;
+ inode_lock(inode);
+ ret = watch_queue_set_size(wqueue, arg);
+ inode_unlock(inode);
+ return ret;
+
+ case IOC_WATCH_QUEUE_SET_FILTER:
+ inode_lock(inode);
+ ret = watch_queue_set_filter(
+ inode, wqueue,
+ (struct watch_notification_filter __user *)arg);
+ inode_unlock(inode);
+ return ret;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/*
+ * Open the file.
+ */
+static int watch_queue_open(struct inode *inode, struct file *file)
+{
+ struct watch_queue *wqueue;
+
+ wqueue = kzalloc(sizeof(*wqueue), GFP_KERNEL);
+ if (!wqueue)
+ return -ENOMEM;
+
+ wqueue->mapping.a_ops = &watch_queue_aops;
+ wqueue->mapping.i_mmap = RB_ROOT_CACHED;
+ init_rwsem(&wqueue->mapping.i_mmap_rwsem);
+ spin_lock_init(&wqueue->mapping.private_lock);
+
+ refcount_set(&wqueue->usage, 1);
+ spin_lock_init(&wqueue->lock);
+ init_waitqueue_head(&wqueue->waiters);
+ wqueue->cred = get_cred(file->f_cred);
+
+ file->private_data = wqueue;
+ return 0;
+}
+
+/**
+ * put_watch_queue - Dispose of a ref on a watchqueue.
+ * @wqueue: The watch queue to unref.
+ */
+void put_watch_queue(struct watch_queue *wqueue)
+{
+ if (refcount_dec_and_test(&wqueue->usage))
+ kfree_rcu(wqueue, rcu);
+}
+EXPORT_SYMBOL(put_watch_queue);
+
+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));
+}
+
+/*
+ * Discard a watch.
+ */
+static void put_watch(struct watch *watch)
+{
+ if (refcount_dec_and_test(&watch->usage))
+ call_rcu(&watch->rcu, free_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)
+{
+ refcount_set(&watch->usage, 1);
+ INIT_HLIST_NODE(&watch->list_node);
+ INIT_HLIST_NODE(&watch->queue_node);
+ rcu_assign_pointer(watch->queue, wqueue);
+}
+
+/**
+ * add_watch_to_object - Add a watch on an object to a watch list
+ * @watch: The watch to add
+ * @wlist: The watch list to add to
+ *
+ * @watch->queue must have been set to point to the queue to post notifications
+ * to and the watch list of the object to be watched.
+ *
+ * The caller must pin the queue and the list both and must hold the list
+ * locked against racing watch additions/removals.
+ */
+int add_watch_to_object(struct watch *watch, struct watch_list *wlist)
+{
+ struct watch_queue *wqueue = rcu_access_pointer(watch->queue);
+ struct watch *w;
+
+ hlist_for_each_entry(w, &wlist->watchers, list_node) {
+ if (watch->id == w->id)
+ return -EBUSY;
+ }
+
+ rcu_assign_pointer(watch->watch_list, wlist);
+
+ spin_lock_bh(&wqueue->lock);
+ refcount_inc(&wqueue->usage);
+ hlist_add_head(&watch->queue_node, &wqueue->watches);
+ spin_unlock_bh(&wqueue->lock);
+
+ hlist_add_head(&watch->list_node, &wlist->watchers);
+ return 0;
+}
+EXPORT_SYMBOL(add_watch_to_object);
+
+/**
+ * remove_watch_from_object - Remove a watch or all watches from an object.
+ * @wlist: The watch list to remove from
+ * @wq: The watch queue of interest (ignored if @all is true)
+ * @id: The ID of the watch to remove (ignored if @all is true)
+ * @all: True to remove all objects
+ *
+ * Remove a specific watch or all watches from an object. A notification is
+ * sent to the watcher to tell them that this happened.
+ */
+int remove_watch_from_object(struct watch_list *wlist, struct watch_queue *wq,
+ u64 id, bool all)
+{
+ struct watch_notification n;
+ struct watch_queue *wqueue;
+ struct watch *watch;
+ int ret = -EBADSLT;
+
+ rcu_read_lock();
+
+again:
+ spin_lock(&wlist->lock);
+ hlist_for_each_entry(watch, &wlist->watchers, list_node) {
+ if (all ||
+ (watch->id == id && rcu_access_pointer(watch->queue) == wq))
+ goto found;
+ }
+ spin_unlock(&wlist->lock);
+ goto out;
+
+found:
+ ret = 0;
+ hlist_del_init_rcu(&watch->list_node);
+ rcu_assign_pointer(watch->watch_list, NULL);
+ spin_unlock(&wlist->lock);
+
+ n.type = WATCH_TYPE_META;
+ n.subtype = WATCH_META_REMOVAL_NOTIFICATION;
+ n.info = watch->info_id | sizeof(n);
+
+ wqueue = rcu_dereference(watch->queue);
+ post_one_notification(wqueue, &n, wq ? wq->cred : NULL);
+
+ /* We don't need the watch list lock for the next bit as RCU is
+ * protecting everything from being deallocated.
+ */
+ if (wqueue) {
+ 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) {
+ rcu_read_unlock();
+ wlist->release_watch(wlist, watch);
+ rcu_read_lock();
+ }
+ put_watch(watch);
+
+ if (all && !hlist_empty(&wlist->watchers))
+ goto again;
+out:
+ rcu_read_unlock();
+ return ret;
+}
+EXPORT_SYMBOL(remove_watch_from_object);
+
+/*
+ * Remove all the watches that are contributory to a queue. This will
+ * potentially race with removal of the watches by the destruction of the
+ * objects being watched or the distribution of notifications.
+ */
+static void watch_queue_clear(struct watch_queue *wqueue)
+{
+ struct watch_list *wlist;
+ struct watch *watch;
+ bool release;
+
+ rcu_read_lock();
+ spin_lock_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);
+ 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) {
+ spin_lock(&wlist->lock);
+
+ release = !hlist_unhashed(&watch->list_node);
+ if (release) {
+ hlist_del_init_rcu(&watch->list_node);
+ rcu_assign_pointer(watch->watch_list, NULL);
+ }
+
+ spin_unlock(&wlist->lock);
+
+ if (release) {
+ if (wlist->release_watch) {
+ rcu_read_unlock();
+ /* This might need to call dput(), so
+ * we have to drop all the locks.
+ */
+ wlist->release_watch(wlist, watch);
+ rcu_read_lock();
+ }
+ put_watch(watch);
+ }
+ }
+
+ put_watch(watch);
+ spin_lock_bh(&wqueue->lock);
+ }
+
+ spin_unlock_bh(&wqueue->lock);
+ rcu_read_unlock();
+}
+
+/*
+ * Release the file.
+ */
+static int watch_queue_release(struct inode *inode, struct file *file)
+{
+ struct watch_filter *wfilter;
+ struct watch_queue *wqueue = file->private_data;
+ int i, pgref;
+
+ watch_queue_clear(wqueue);
+
+ if (wqueue->pages && wqueue->pages[0])
+ WARN_ON(page_ref_count(wqueue->pages[0]) != 1);
+
+ if (wqueue->buffer)
+ vfree(wqueue->buffer);
+ for (i = 0; i < wqueue->nr_pages; i++) {
+ ClearPageUptodate(wqueue->pages[i]);
+ wqueue->pages[i]->mapping = NULL;
+ pgref = page_ref_count(wqueue->pages[i]);
+ WARN(pgref != 1,
+ "FREE PAGE[%d] refcount %d\n", i, page_ref_count(wqueue->pages[i]));
+ __free_page(wqueue->pages[i]);
+ }
+
+ wfilter = rcu_access_pointer(wqueue->filter);
+ if (wfilter)
+ kfree_rcu(wfilter, rcu);
+ kfree(wqueue->pages);
+ put_cred(wqueue->cred);
+ put_watch_queue(wqueue);
+ return 0;
+}
+
+#ifdef DEBUG_WITH_WRITE
+static ssize_t watch_queue_write(struct file *file,
+ const char __user *_buf, size_t len, loff_t *pos)
+{
+ struct watch_notification *n;
+ struct watch_queue *wqueue = file->private_data;
+ ssize_t ret;
+
+ if (!wqueue->buffer)
+ return -ENOBUFS;
+
+ if (len & ~WATCH_INFO_LENGTH || len == 0 || !_buf)
+ return -EINVAL;
+
+ n = memdup_user(_buf, len);
+ if (IS_ERR(n))
+ return PTR_ERR(n);
+
+ ret = -EINVAL;
+ if ((n->info & WATCH_INFO_LENGTH) != len)
+ goto error;
+ n->info &= (WATCH_INFO_LENGTH | WATCH_INFO_TYPE_FLAGS | WATCH_INFO_ID);
+
+ if (post_one_notification(wqueue, n, file->f_cred))
+ wqueue->debug = 0;
+ else
+ wqueue->debug++;
+ ret = len;
+ if (wqueue->debug > 20)
+ ret = -EIO;
+
+error:
+ kfree(n);
+ return ret;
+}
+#endif
+
+static const struct file_operations watch_queue_fops = {
+ .owner = THIS_MODULE,
+ .open = watch_queue_open,
+ .release = watch_queue_release,
+ .unlocked_ioctl = watch_queue_ioctl,
+ .poll = watch_queue_poll,
+ .mmap = watch_queue_mmap,
+#ifdef DEBUG_WITH_WRITE
+ .write = watch_queue_write,
+#endif
+ .llseek = no_llseek,
+};
+
+/**
+ * get_watch_queue - Get a watch queue from its file descriptor.
+ * @fd: The fd to query.
+ */
+struct watch_queue *get_watch_queue(int fd)
+{
+ struct watch_queue *wqueue = ERR_PTR(-EBADF);
+ struct fd f;
+
+ f = fdget(fd);
+ if (f.file) {
+ wqueue = ERR_PTR(-EINVAL);
+ if (f.file->f_op == &watch_queue_fops) {
+ wqueue = f.file->private_data;
+ refcount_inc(&wqueue->usage);
+ }
+ fdput(f);
+ }
+
+ return wqueue;
+}
+EXPORT_SYMBOL(get_watch_queue);
+
+static struct miscdevice watch_queue_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "watch_queue",
+ .fops = &watch_queue_fops,
+ .mode = 0666,
+};
+
+static int __init watch_queue_init(void)
+{
+ int ret;
+
+ ret = misc_register(&watch_queue_dev);
+ if (ret < 0)
+ pr_err("Failed to register %d\n", ret);
+ return ret;
+}
+fs_initcall(watch_queue_init);
+
+static void __exit watch_queue_exit(void)
+{
+ misc_deregister(&watch_queue_dev);
+}
+module_exit(watch_queue_exit);
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 2474c3f785ca..2f72ea80d4fe 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1420,6 +1420,13 @@
* @ctx is a pointer in which to place the allocated security context.
* @ctxlen points to the place to put the length of @ctx.
*
+ * @post_notification:
+ * Check to see if a watch notification can be posted to a particular
+ * queue.
+ * @q_cred: The credentials of the target watch queue.
+ * @cred: The event-triggerer's credentials
+ * @n: The notification being posted
+ *
* Security hooks for using the eBPF maps and programs functionalities through
* eBPF syscalls.
*
@@ -1698,6 +1705,11 @@ union security_list_options {
int (*inode_notifysecctx)(struct inode *inode, void *ctx, u32 ctxlen);
int (*inode_setsecctx)(struct dentry *dentry, void *ctx, u32 ctxlen);
int (*inode_getsecctx)(struct inode *inode, void **ctx, u32 *ctxlen);
+#ifdef CONFIG_WATCH_QUEUE
+ int (*post_notification)(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n);
+#endif
#ifdef CONFIG_SECURITY_NETWORK
int (*unix_stream_connect)(struct sock *sock, struct sock *other,
@@ -1977,6 +1989,9 @@ struct security_hook_heads {
struct hlist_head inode_notifysecctx;
struct hlist_head inode_setsecctx;
struct hlist_head inode_getsecctx;
+#ifdef CONFIG_WATCH_QUEUE
+ struct hlist_head post_notification;
+#endif
#ifdef CONFIG_SECURITY_NETWORK
struct hlist_head unix_stream_connect;
struct hlist_head unix_may_send;
diff --git a/include/linux/security.h b/include/linux/security.h
index 23c8b602c0ab..1df8d55de8da 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -58,6 +58,7 @@ struct fs_context;
struct fs_parameter;
enum fs_value_type;
struct fsinfo_kparams;
+struct watch_notification;
/* Default (no) options for the capable function */
#define CAP_OPT_NONE 0x0
@@ -396,6 +397,11 @@ void security_inode_invalidate_secctx(struct inode *inode);
int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen);
int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen);
int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen);
+#ifdef CONFIG_WATCH_QUEUE
+int security_post_notification(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n);
+#endif
#else /* CONFIG_SECURITY */
static inline int call_lsm_notifier(enum lsm_event event, void *data)
@@ -1215,6 +1221,14 @@ static inline int security_inode_getsecctx(struct inode *inode, void **ctx, u32
{
return -EOPNOTSUPP;
}
+#ifdef CONFIG_WATCH_QUEUE
+static inline int security_post_notification(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n)
+{
+ return 0;
+}
+#endif
#endif /* CONFIG_SECURITY */
#ifdef CONFIG_SECURITY_NETWORK
diff --git a/include/linux/watch_queue.h b/include/linux/watch_queue.h
new file mode 100644
index 000000000000..f200b68c799e
--- /dev/null
+++ b/include/linux/watch_queue.h
@@ -0,0 +1,86 @@
+/* User-mappable watch queue
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ *
+ * See Documentation/watch_queue.rst
+ */
+
+#ifndef _LINUX_WATCH_QUEUE_H
+#define _LINUX_WATCH_QUEUE_H
+
+#include <uapi/linux/watch_queue.h>
+
+#ifdef CONFIG_WATCH_QUEUE
+
+struct watch_queue;
+
+/*
+ * Representation of a watch on an object.
+ */
+struct watch {
+ union {
+ struct rcu_head rcu;
+ u32 info_id; /* ID to be OR'd in to info field */
+ };
+ struct watch_queue __rcu *queue; /* Queue to post events to */
+ struct hlist_node queue_node; /* Link in queue->watches */
+ struct watch_list __rcu *watch_list;
+ struct hlist_node list_node; /* Link in watch_list->watchers */
+ void *private; /* Private data for the watched object */
+ u64 id; /* Internal identifier */
+ refcount_t usage;
+};
+
+/*
+ * List of watches on an object.
+ */
+struct watch_list {
+ struct rcu_head rcu;
+ struct hlist_head watchers;
+ void (*release_watch)(struct watch_list *, struct watch *);
+ spinlock_t lock;
+};
+
+extern void __post_watch_notification(struct watch_list *,
+ struct watch_notification *,
+ const struct cred *,
+ u64);
+extern struct watch_queue *get_watch_queue(int);
+extern void put_watch_queue(struct watch_queue *);
+extern void put_watch_list(struct watch_list *);
+extern void init_watch(struct watch *, struct watch_queue *);
+extern int add_watch_to_object(struct watch *, struct watch_list *);
+extern int remove_watch_from_object(struct watch_list *, struct watch_queue *, u64, bool);
+
+static inline void init_watch_list(struct watch_list *wlist)
+{
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+}
+
+static inline void post_watch_notification(struct watch_list *wlist,
+ struct watch_notification *n,
+ const struct cred *cred,
+ u64 id)
+{
+ if (unlikely(wlist))
+ __post_watch_notification(wlist, n, cred, id);
+}
+
+static inline void remove_watch_list(struct watch_list *wlist)
+{
+ if (wlist) {
+ remove_watch_from_object(wlist, NULL, 0, true);
+ kfree_rcu(wlist, rcu);
+ }
+}
+
+#endif
+
+#endif /* _LINUX_WATCH_QUEUE_H */
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
new file mode 100644
index 000000000000..01746982c2ba
--- /dev/null
+++ b/include/uapi/linux/watch_queue.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_WATCH_QUEUE_H
+#define _UAPI_LINUX_WATCH_QUEUE_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define IOC_WATCH_QUEUE_SET_SIZE _IO('s', 0x01) /* Set the size in pages */
+#define IOC_WATCH_QUEUE_SET_FILTER _IO('s', 0x02) /* Set the filter */
+
+enum watch_notification_type {
+ WATCH_TYPE_META = 0, /* Special record */
+ WATCH_TYPE_MOUNT_NOTIFY = 1, /* Mount notification record */
+ WATCH_TYPE_SB_NOTIFY = 2, /* Superblock notification */
+ WATCH_TYPE_KEY_NOTIFY = 3, /* Key/keyring change notification */
+ WATCH_TYPE_BLOCK_NOTIFY = 4, /* Block layer notifications */
+#define WATCH_TYPE___NR 5
+};
+
+enum watch_meta_notification_subtype {
+ WATCH_META_SKIP_NOTIFICATION = 0, /* Just skip this record */
+ WATCH_META_REMOVAL_NOTIFICATION = 1, /* Watched object was removed */
+};
+
+/*
+ * Notification record
+ */
+struct watch_notification {
+ __u32 type:24; /* enum watch_notification_type */
+ __u32 subtype:8; /* Type-specific subtype (filterable) */
+ __u32 info;
+#define WATCH_INFO_OVERRUN 0x00000001 /* Event(s) lost due to overrun */
+#define WATCH_INFO_ENOMEM 0x00000002 /* Event(s) lost due to ENOMEM */
+#define WATCH_INFO_RECURSIVE 0x00000004 /* Change was recursive */
+#define WATCH_INFO_LENGTH 0x000001f8 /* Length of record / sizeof(watch_notification) */
+#define WATCH_INFO_IN_SUBTREE 0x00000200 /* Change was not at watched root */
+#define WATCH_INFO_TYPE_FLAGS 0x00ff0000 /* Type-specific flags */
+#define WATCH_INFO_FLAG_0 0x00010000
+#define WATCH_INFO_FLAG_1 0x00020000
+#define WATCH_INFO_FLAG_2 0x00040000
+#define WATCH_INFO_FLAG_3 0x00080000
+#define WATCH_INFO_FLAG_4 0x00100000
+#define WATCH_INFO_FLAG_5 0x00200000
+#define WATCH_INFO_FLAG_6 0x00400000
+#define WATCH_INFO_FLAG_7 0x00800000
+#define WATCH_INFO_ID 0xff000000 /* ID of watchpoint */
+};
+
+#define WATCH_LENGTH_SHIFT 3
+
+struct watch_queue_buffer {
+ union {
+ /* The first few entries are special, containing the
+ * ring management variables.
+ */
+ struct {
+ struct watch_notification watch; /* WATCH_TYPE_SKIP */
+ volatile __u32 head; /* Ring head index */
+ volatile __u32 tail; /* Ring tail index */
+ __u32 mask; /* Ring index mask */
+ } meta;
+ struct watch_notification slots[0];
+ };
+};
+
+/*
+ * Notification filtering rules (IOC_WATCH_QUEUE_SET_FILTER).
+ */
+struct watch_notification_type_filter {
+ __u32 type; /* Type to apply filter to */
+ __u32 info_filter; /* Filter on watch_notification::info */
+ __u32 info_mask; /* Mask of relevant bits in info_filter */
+ __u32 subtype_filter[8]; /* Bitmask of subtypes to filter on */
+};
+
+struct watch_notification_filter {
+ __u32 nr_filters; /* Number of filters */
+ __u32 __reserved; /* Must be 0 */
+ struct watch_notification_type_filter filters[];
+};
+
+#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/mm/interval_tree.c b/mm/interval_tree.c
index 27ddfd29112a..9a53ddf4bd62 100644
--- a/mm/interval_tree.c
+++ b/mm/interval_tree.c
@@ -25,6 +25,8 @@ INTERVAL_TREE_DEFINE(struct vm_area_struct, shared.rb,
unsigned long, shared.rb_subtree_last,
vma_start_pgoff, vma_last_pgoff,, vma_interval_tree)
+EXPORT_SYMBOL_GPL(vma_interval_tree_insert);
+
/* Insert node immediately after prev in the interval tree */
void vma_interval_tree_insert_after(struct vm_area_struct *node,
struct vm_area_struct *prev,
diff --git a/mm/memory.c b/mm/memory.c
index 96f1d473c89a..9f2fa2138287 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -3360,6 +3360,7 @@ vm_fault_t alloc_set_pte(struct vm_fault *vmf, struct mem_cgroup *memcg,
return 0;
}
+EXPORT_SYMBOL_GPL(alloc_set_pte);
/**
diff --git a/security/security.c b/security/security.c
index 3af886e8fced..af758dc71e24 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1929,6 +1929,15 @@ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen)
}
EXPORT_SYMBOL(security_inode_getsecctx);
+#ifdef CONFIG_WATCH_QUEUE
+int security_post_notification(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n)
+{
+ return call_int_hook(post_notification, 0, q_cred, cred, n);
+}
+#endif
+
#ifdef CONFIG_SECURITY_NETWORK
int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk)
^ permalink raw reply related
* [RFC][PATCH 0/7] Mount, FS, Block and Keyrings notifications
From: David Howells @ 2019-05-28 16:01 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
Hi Al,
Here's a set of patches to add a general variable-length notification queue
concept and to add sources of events for:
(1) Mount topology events, such as mounting, unmounting, mount expiry,
mount reconfiguration.
(2) Superblock events, such as R/W<->R/O changes, quota overrun and I/O
errors (not complete yet).
(3) Block layer events, such as I/O errors.
(4) Key/keyring events, such as creating, linking and removal of keys.
One of the reasons for this is so that we can remove the issue of processes
having to repeatedly and regularly scan /proc/mounts, which has proven to
be a system performance problem. To further aid this, the fsinfo() syscall
on which this patch series depends, provides a way to access superblock and
mount information in binary form without the need to parse /proc/mounts.
Design decisions:
(1) A misc chardev is used to create and open a ring buffer:
fd = open("/dev/watch_queue", O_RDWR);
which is then configured and mmap'd into userspace:
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE);
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
buf = mmap(NULL, BUF_SIZE * page_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
The fd cannot be read or written (though there is a facility to use
write to inject records for debugging) and userspace just pulls data
directly out of the buffer.
(2) The ring index pointers are stored inside the ring and are thus
accessible to userspace. Userspace should only update the tail
pointer and never the head pointer or risk breaking the buffer. The
kernel checks that the pointers appear valid before trying to use
them. A 'skip' record is maintained around the pointers.
(3) poll() can be used to wait for data to appear in the buffer.
(4) Records in the buffer are binary, typed and have a length so that they
can be of varying size.
This means that multiple heterogeneous sources can share a common
buffer. Tags may be specified when a watchpoint is created to help
distinguish the sources.
(5) The queue is reusable as there are 16 million types available, of
which I've used 4, so there is scope for others to be used.
(6) Records are filterable as types have up to 256 subtypes that can be
individually filtered. Other filtration is also available.
(7) Each time the buffer is opened, a new buffer is created - this means
that there's no interference between watchers.
(8) When recording a notification, the kernel will not sleep, but will
rather mark a queue as overrun if there's insufficient space, thereby
avoiding userspace causing the kernel to hang.
(9) The 'watchpoint' should be specific where possible, meaning that you
specify the object that you want to watch.
(10) The buffer is created and then watchpoints are attached to it, using
one of:
keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01);
mount_notify(AT_FDCWD, "/", 0, fd, 0x02);
sb_notify(AT_FDCWD, "/mnt", 0, fd, 0x03);
where in all three cases, fd indicates the queue and the number after
is a tag between 0 and 255.
(11) The watch must be removed if either the watch buffer is destroyed or
the watched object is destroyed.
Things I want to avoid:
(1) Introducing features that make the core VFS dependent on the network
stack or networking namespaces (ie. usage of netlink).
(2) Dumping all this stuff into dmesg and having a daemon that sits there
parsing the output and distributing it as this then puts the
responsibility for security into userspace and makes handling
namespaces tricky. Further, dmesg might not exist or might be
inaccessible inside a container.
(3) Letting users see events they shouldn't be able to see.
Further things that could be considered:
(1) Adding a keyctl call to allow a watch on a keyring to be extended to
"children" of that keyring, such that the watch is removed from the
child if it is unlinked from the keyring.
(2) Adding global superblock event queue.
(3) Propagating watches to child superblock over automounts.
The patches can be found here also:
http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=notifications
David
---
David Howells (7):
General notification queue with user mmap()'able ring buffer
keys: Add a notification facility
vfs: Add a mount-notification facility
vfs: Add superblock notifications
fsinfo: Export superblock notification counter
block: Add block layer notifications
Add sample notification program
Documentation/security/keys/core.rst | 58 ++
Documentation/watch_queue.rst | 311 +++++++++++
arch/x86/entry/syscalls/syscall_32.tbl | 3
arch/x86/entry/syscalls/syscall_64.tbl | 3
block/Kconfig | 9
block/Makefile | 1
block/blk-core.c | 28 +
block/blk-notify.c | 83 +++
drivers/misc/Kconfig | 13
drivers/misc/Makefile | 1
drivers/misc/watch_queue.c | 877 ++++++++++++++++++++++++++++++++
fs/Kconfig | 21 +
fs/Makefile | 1
fs/fsinfo.c | 12
fs/mount.h | 33 +
fs/mount_notify.c | 178 ++++++
fs/namespace.c | 9
fs/super.c | 116 ++++
include/linux/blkdev.h | 10
include/linux/dcache.h | 1
include/linux/fs.h | 78 +++
include/linux/key.h | 4
include/linux/lsm_hooks.h | 15 +
include/linux/security.h | 14 +
include/linux/syscalls.h | 5
include/linux/watch_queue.h | 86 +++
include/uapi/linux/fsinfo.h | 10
include/uapi/linux/keyctl.h | 1
include/uapi/linux/watch_queue.h | 185 +++++++
kernel/sys_ni.c | 6
mm/interval_tree.c | 2
mm/memory.c | 1
samples/Kconfig | 6
samples/Makefile | 1
samples/vfs/test-fsinfo.c | 13
samples/watch_queue/Makefile | 9
samples/watch_queue/watch_test.c | 284 ++++++++++
security/keys/Kconfig | 10
security/keys/compat.c | 2
security/keys/gc.c | 5
security/keys/internal.h | 30 +
security/keys/key.c | 37 +
security/keys/keyctl.c | 88 +++
security/keys/keyring.c | 17 -
security/keys/request_key.c | 4
security/security.c | 9
46 files changed, 2652 insertions(+), 38 deletions(-)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 block/blk-notify.c
create mode 100644 drivers/misc/watch_queue.c
create mode 100644 fs/mount_notify.c
create mode 100644 include/linux/watch_queue.h
create mode 100644 include/uapi/linux/watch_queue.h
create mode 100644 samples/watch_queue/Makefile
create mode 100644 samples/watch_queue/watch_test.c
^ permalink raw reply
* [PATCH] LSM: Fix formatting errors in SafeSetID LSM docs
From: Micah Morton @ 2019-05-28 15:58 UTC (permalink / raw)
To: jmorris, linux-security-module; +Cc: Micah Morton
This fixes the unintended occurrences of ??? in the text.
Signed-off-by: Micah Morton <mortonm@chromium.org>
---
Documentation/admin-guide/LSM/SafeSetID.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Documentation/admin-guide/LSM/SafeSetID.rst b/Documentation/admin-guide/LSM/SafeSetID.rst
index 212434ef65ad..aa41fdb211ff 100644
--- a/Documentation/admin-guide/LSM/SafeSetID.rst
+++ b/Documentation/admin-guide/LSM/SafeSetID.rst
@@ -56,7 +56,7 @@ setid capabilities from the application completely and refactor the process
spawning semantics in the application (e.g. by using a privileged helper program
to do process spawning and UID/GID transitions). Unfortunately, there are a
number of semantics around process spawning that would be affected by this, such
-as fork() calls where the program doesn???t immediately call exec() after the
+as fork() calls where the program does not immediately call exec() after the
fork(), parent processes specifying custom environment variables or command line
args for spawned child processes, or inheritance of file handles across a
fork()/exec(). Because of this, as solution that uses a privileged helper in
@@ -72,7 +72,7 @@ own user namespace, and only approved UIDs/GIDs could be mapped back to the
initial system user namespace, affectively preventing privilege escalation.
Unfortunately, it is not generally feasible to use user namespaces in isolation,
without pairing them with other namespace types, which is not always an option.
-Linux checks for capabilities based off of the user namespace that ???owns??? some
+Linux checks for capabilities based off of the user namespace that "owns" some
entity. For example, Linux has the notion that network namespaces are owned by
the user namespace in which they were created. A consequence of this is that
capability checks for access to a given network namespace are done by checking
--
2.22.0.rc1.257.g3120a18244-goog
^ permalink raw reply related
* Re: [PATCH] Smack: Restore the smackfsdef mount option
From: Casey Schaufler @ 2019-05-28 15:51 UTC (permalink / raw)
To: David Howells
Cc: LKML, Al Viro, jose.bollo, Linux Security Module list, casey
In-Reply-To: <11440.1559046181@warthog.procyon.org.uk>
On 5/28/2019 5:23 AM, David Howells wrote:
> Casey Schaufler <casey@schaufler-ca.com> wrote:
>
>> The change was made in commit c3300aaf95fb4 from Al Viro.
> This should be in a "Fixes:" tag?
Thanks. I wasn't sure how to properly apply that.
>
>> + fsparam_string("fsdef", Opt_fsdefault),
>> fsparam_string("fsdefault", Opt_fsdefault),
>> fsparam_string("fsfloor", Opt_fsfloor),
>> fsparam_string("fshat", Opt_fshat),
> Would it be better to delete the "fsdefault" line?
If it hadn't slipped into the 5.1 release I would
say to remove it, but now it would be a regression.
>
> Also, should all of these be prefixed with "smack"? So:
>
> fsparam_string("smackfsdef", Opt_fsdefault),
> fsparam_string("smackfsfloor", Opt_fsfloor),
> fsparam_string("smackfshat", Opt_fshat),
No. smack_fs_parameters takes care of that.
>
> David
^ permalink raw reply
* [PATCH] apparmor: enforce nullbyte at end of tag string
From: Jann Horn @ 2019-05-28 15:32 UTC (permalink / raw)
To: John Johansen, jannh; +Cc: Serge E. Hallyn, linux-security-module, linux-kernel
A packed AppArmor policy contains null-terminated tag strings that are read
by unpack_nameX(). However, unpack_nameX() uses string functions on them
without ensuring that they are actually null-terminated, potentially
leading to out-of-bounds accesses.
Make sure that the tag string is null-terminated before passing it to
strcmp().
Cc: stable@vger.kernel.org
Signed-off-by: Jann Horn <jannh@google.com>
---
Warning: The existence of this bug has not been verified at runtime, and
the patch is compile-tested only. I noticed this while browsing through
the code, but didn't want to spend the time necessary to figure out how
to actually test this at runtime.
security/apparmor/policy_unpack.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index f6c2bcb2ab14..33041c4fb69f 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -276,7 +276,7 @@ static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
char *tag = NULL;
size_t size = unpack_u16_chunk(e, &tag);
/* if a name is specified it must match. otherwise skip tag */
- if (name && (!size || strcmp(name, tag)))
+ if (name && (!size || tag[size-1] != '\0' || strcmp(name, tag)))
goto fail;
} else if (name) {
/* if a name is specified and there is no name tag fail */
--
2.22.0.rc1.257.g3120a18244-goog
^ permalink raw reply related
* Purchase_rfq
From: aishatu @ 2019-05-28 14:42 UTC (permalink / raw)
To: linux-security-module
[-- Attachment #1: INQUIRY_4178916..xlsx --]
[-- Type: application/octet-stream, Size: 9404 bytes --]
^ permalink raw reply
* Re: [PATCH v10 12/12] ima: Store the measurement again when appraising a modsig
From: Mimi Zohar @ 2019-05-28 14:09 UTC (permalink / raw)
To: Thiago Jung Bauermann, linux-integrity
Cc: linux-security-module, keyrings, linux-crypto, linuxppc-dev,
linux-doc, linux-kernel, Dmitry Kasatkin, James Morris,
Serge E. Hallyn, David Howells, David Woodhouse, Jessica Yu,
Herbert Xu, David S. Miller, Jonathan Corbet, AKASHI, Takahiro
In-Reply-To: <20190418035120.2354-13-bauerman@linux.ibm.com>
Hi Thiago,
On Thu, 2019-04-18 at 00:51 -0300, Thiago Jung Bauermann wrote:
> If the IMA template contains the "modsig" or "d-modsig" field, then the
> modsig should be added to the measurement list when the file is appraised.
>
> And that is what normally happens, but if a measurement rule caused a file
> containing a modsig to be measured before a different rule causes it to be
> appraised, the resulting measurement entry will not contain the modsig
> because it is only fetched during appraisal. When the appraisal rule
> triggers, it won't store a new measurement containing the modsig because
> the file was already measured.
>
> We need to detect that situation and store an additional measurement with
> the modsig. This is done by adding an IMA_MEASURE action flag if we read a
> modsig and the IMA template contains a modsig field.
With the new per policy rule "template" support being added, this
patch needs to be modified so that the per policy "template" format is
checked. ima_template_has_modsig() should be called with the
template_desc being used.
thanks,
Mimi
> diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
> index 8e6475854351..f91ed4189f98 100644
> --- a/security/integrity/ima/ima_main.c
> +++ b/security/integrity/ima/ima_main.c
> @@ -282,9 +282,17 @@ static int process_measurement(struct file *file, const struct cred *cred,
> /* read 'security.ima' */
> xattr_len = ima_read_xattr(file_dentry(file), &xattr_value);
>
> - /* Read the appended modsig if allowed by the policy. */
> - if (iint->flags & IMA_MODSIG_ALLOWED)
> - ima_read_modsig(func, buf, size, &modsig);
> + /*
> + * Read the appended modsig, if allowed by the policy, and allow
> + * an additional measurement list entry, if needed, based on the
> + * template format.
> + */
> + if (iint->flags & IMA_MODSIG_ALLOWED) {
> + rc = ima_read_modsig(func, buf, size, &modsig);
> +
> + if (!rc && ima_template_has_modsig())
> + action |= IMA_MEASURE;
> + }
>
^ permalink raw reply
* Re: [PATCH] Smack: Restore the smackfsdef mount option
From: David Howells @ 2019-05-28 12:23 UTC (permalink / raw)
To: Casey Schaufler
Cc: dhowells, LKML, Al Viro, jose.bollo, Linux Security Module list
In-Reply-To: <1ebab7e7-f7ee-b910-9cc8-5d826eee8e97@schaufler-ca.com>
Casey Schaufler <casey@schaufler-ca.com> wrote:
> The change was made in commit c3300aaf95fb4 from Al Viro.
This should be in a "Fixes:" tag?
> + fsparam_string("fsdef", Opt_fsdefault),
> fsparam_string("fsdefault", Opt_fsdefault),
> fsparam_string("fsfloor", Opt_fsfloor),
> fsparam_string("fshat", Opt_fshat),
Would it be better to delete the "fsdefault" line?
Also, should all of these be prefixed with "smack"? So:
fsparam_string("smackfsdef", Opt_fsdefault),
fsparam_string("smackfsfloor", Opt_fsfloor),
fsparam_string("smackfshat", Opt_fshat),
David
^ permalink raw reply
* [PATCH 2/3][V2] treewide: rename match_string() -> __match_string()
From: Alexandru Ardelean @ 2019-05-28 7:39 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel, linux-ide, linux-clk,
linux-rpi-kernel, linux-arm-kernel, linux-rockchip, linux-pm,
linux-gpio, dri-devel, intel-gfx, linux-omap, linux-mmc,
linux-wireless, netdev, linux-pci, linux-tegra, devel, linux-usb,
kvm, linux-fbdev, linux-mtd, cgroups, linux-mm,
linux-security-module, linux-integrity, alsa-devel
Cc: heikki.krogerus, gregkh, andriy.shevchenko, Alexandru Ardelean
In-Reply-To: <20190528073932.25365-1-alexandru.ardelean@analog.com>
This change does a rename of match_string() -> __match_string().
There are a few parts to the intention here (with this change):
1. Align with sysfs_match_string()/__sysfs_match_string()
2. This helps to group users of `match_string()`:
a. those that use ARRAY_SIZE(_a) to specify the number of elements
b. those that use -1 to pass a NULL terminated array of strings
c. special users, which (after eliminating 1 & 2) are not that many
This change is done treewide. Updates to the new match_string() helper will
be done on a per-subsystem basis, as the cadence of each subsystem differs.
Signed-off-by: Alexandru Ardelean <alexandru.ardelean@analog.com>
---
arch/powerpc/xmon/xmon.c | 2 +-
arch/x86/kernel/cpu/mtrr/if.c | 2 +-
drivers/ata/pata_hpt366.c | 2 +-
drivers/ata/pata_hpt37x.c | 2 +-
drivers/base/devcon.c | 2 +-
drivers/base/property.c | 2 +-
drivers/clk/bcm/clk-bcm2835.c | 6 +++---
drivers/clk/rockchip/clk.c | 4 ++--
drivers/cpufreq/intel_pstate.c | 2 +-
drivers/gpio/gpiolib-of.c | 2 +-
drivers/gpu/drm/drm_edid_load.c | 2 +-
drivers/gpu/drm/drm_panel_orientation_quirks.c | 2 +-
drivers/gpu/drm/i915/intel_pipe_crc.c | 2 +-
drivers/ide/hpt366.c | 2 +-
drivers/mfd/omap-usb-host.c | 2 +-
drivers/mmc/host/sdhci-xenon-phy.c | 2 +-
drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c | 2 +-
drivers/pci/pcie/aer.c | 2 +-
drivers/phy/tegra/xusb.c | 4 ++--
drivers/pinctrl/mvebu/pinctrl-armada-37xx.c | 4 ++--
drivers/pinctrl/pinmux.c | 2 +-
drivers/power/supply/ab8500_btemp.c | 2 +-
drivers/power/supply/ab8500_charger.c | 2 +-
drivers/power/supply/ab8500_fg.c | 2 +-
drivers/power/supply/abx500_chargalg.c | 2 +-
drivers/power/supply/charger-manager.c | 4 ++--
drivers/staging/gdm724x/gdm_tty.c | 4 ++--
drivers/usb/common/common.c | 4 ++--
drivers/usb/typec/class.c | 10 +++++-----
drivers/usb/typec/tps6598x.c | 2 +-
drivers/vfio/vfio.c | 6 +++---
drivers/video/fbdev/pxafb.c | 2 +-
fs/ubifs/auth.c | 4 ++--
include/linux/string.h | 2 +-
kernel/cgroup/rdma.c | 2 +-
kernel/sched/debug.c | 2 +-
kernel/trace/trace.c | 2 +-
lib/string.c | 8 ++++----
mm/mempolicy.c | 2 +-
mm/vmpressure.c | 4 ++--
security/apparmor/lsm.c | 4 ++--
security/integrity/ima/ima_main.c | 2 +-
sound/firewire/oxfw/oxfw.c | 2 +-
sound/pci/oxygen/oxygen_mixer.c | 2 +-
sound/soc/codecs/max98088.c | 2 +-
sound/soc/codecs/max98095.c | 2 +-
sound/soc/soc-dapm.c | 2 +-
47 files changed, 67 insertions(+), 67 deletions(-)
diff --git a/arch/powerpc/xmon/xmon.c b/arch/powerpc/xmon/xmon.c
index 1b0149b2bb6c..8039759a9e82 100644
--- a/arch/powerpc/xmon/xmon.c
+++ b/arch/powerpc/xmon/xmon.c
@@ -3264,7 +3264,7 @@ scanhex(unsigned long *vp)
regname[i] = c;
}
regname[i] = 0;
- i = match_string(regnames, N_PTREGS, regname);
+ i = __match_string(regnames, N_PTREGS, regname);
if (i < 0) {
printf("invalid register name '%%%s'\n", regname);
return 0;
diff --git a/arch/x86/kernel/cpu/mtrr/if.c b/arch/x86/kernel/cpu/mtrr/if.c
index 4d36dcc1cf87..4ec7a5f7b94c 100644
--- a/arch/x86/kernel/cpu/mtrr/if.c
+++ b/arch/x86/kernel/cpu/mtrr/if.c
@@ -142,7 +142,7 @@ mtrr_write(struct file *file, const char __user *buf, size_t len, loff_t * ppos)
return -EINVAL;
ptr = skip_spaces(ptr + 5);
- i = match_string(mtrr_strings, MTRR_NUM_TYPES, ptr);
+ i = __match_string(mtrr_strings, MTRR_NUM_TYPES, ptr);
if (i < 0)
return i;
diff --git a/drivers/ata/pata_hpt366.c b/drivers/ata/pata_hpt366.c
index 2574d6fbb1ad..a23ec26cc95f 100644
--- a/drivers/ata/pata_hpt366.c
+++ b/drivers/ata/pata_hpt366.c
@@ -181,7 +181,7 @@ static int hpt_dma_blacklisted(const struct ata_device *dev, char *modestr,
ata_id_c_string(dev->id, model_num, ATA_ID_PROD, sizeof(model_num));
- i = match_string(list, -1, model_num);
+ i = __match_string(list, -1, model_num);
if (i >= 0) {
pr_warn("%s is not supported for %s\n", modestr, list[i]);
return 1;
diff --git a/drivers/ata/pata_hpt37x.c b/drivers/ata/pata_hpt37x.c
index fad6c6a87313..ac0499e4ae4b 100644
--- a/drivers/ata/pata_hpt37x.c
+++ b/drivers/ata/pata_hpt37x.c
@@ -229,7 +229,7 @@ static int hpt_dma_blacklisted(const struct ata_device *dev, char *modestr,
ata_id_c_string(dev->id, model_num, ATA_ID_PROD, sizeof(model_num));
- i = match_string(list, -1, model_num);
+ i = __match_string(list, -1, model_num);
if (i >= 0) {
pr_warn("%s is not supported for %s\n", modestr, list[i]);
return 1;
diff --git a/drivers/base/devcon.c b/drivers/base/devcon.c
index 04db9ae235e4..7bc1c619b721 100644
--- a/drivers/base/devcon.c
+++ b/drivers/base/devcon.c
@@ -70,7 +70,7 @@ void *device_connection_find_match(struct device *dev, const char *con_id,
mutex_lock(&devcon_lock);
list_for_each_entry(con, &devcon_list, list) {
- ep = match_string(con->endpoint, 2, devname);
+ ep = __match_string(con->endpoint, 2, devname);
if (ep < 0)
continue;
diff --git a/drivers/base/property.c b/drivers/base/property.c
index 348b37e64944..67195d6bfdca 100644
--- a/drivers/base/property.c
+++ b/drivers/base/property.c
@@ -443,7 +443,7 @@ int fwnode_property_match_string(const struct fwnode_handle *fwnode,
if (ret < 0)
goto out;
- ret = match_string(values, nval, string);
+ ret = __match_string(values, nval, string);
if (ret < 0)
ret = -ENODATA;
out:
diff --git a/drivers/clk/bcm/clk-bcm2835.c b/drivers/clk/bcm/clk-bcm2835.c
index 770bb01f523e..91bb94d68798 100644
--- a/drivers/clk/bcm/clk-bcm2835.c
+++ b/drivers/clk/bcm/clk-bcm2835.c
@@ -1391,9 +1391,9 @@ static struct clk_hw *bcm2835_register_clock(struct bcm2835_cprman *cprman,
for (i = 0; i < data->num_mux_parents; i++) {
parents[i] = data->parents[i];
- ret = match_string(cprman_parent_names,
- ARRAY_SIZE(cprman_parent_names),
- parents[i]);
+ ret = __match_string(cprman_parent_names,
+ ARRAY_SIZE(cprman_parent_names),
+ parents[i]);
if (ret >= 0)
parents[i] = cprman->real_parent_names[ret];
}
diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c
index d5fac5a8a3d7..2163bb54a663 100644
--- a/drivers/clk/rockchip/clk.c
+++ b/drivers/clk/rockchip/clk.c
@@ -280,8 +280,8 @@ static struct clk *rockchip_clk_register_frac_branch(
struct clk *mux_clk;
int ret;
- frac->mux_frac_idx = match_string(child->parent_names,
- child->num_parents, name);
+ frac->mux_frac_idx = __match_string(child->parent_names,
+ child->num_parents, name);
frac->mux_ops = &clk_mux_ops;
frac->clk_nb.notifier_call = rockchip_clk_frac_notifier_cb;
diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c
index 34b54df41aaa..a1f79451308c 100644
--- a/drivers/cpufreq/intel_pstate.c
+++ b/drivers/cpufreq/intel_pstate.c
@@ -703,7 +703,7 @@ static ssize_t store_energy_performance_preference(
if (ret != 1)
return -EINVAL;
- ret = match_string(energy_perf_strings, -1, str_preference);
+ ret = __match_string(energy_perf_strings, -1, str_preference);
if (ret < 0)
return ret;
diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c
index aec7bd86ae7e..527be82e1bac 100644
--- a/drivers/gpio/gpiolib-of.c
+++ b/drivers/gpio/gpiolib-of.c
@@ -278,7 +278,7 @@ static struct gpio_desc *of_find_regulator_gpio(struct device *dev, const char *
if (!con_id)
return ERR_PTR(-ENOENT);
- i = match_string(whitelist, ARRAY_SIZE(whitelist), con_id);
+ i = __match_string(whitelist, ARRAY_SIZE(whitelist), con_id);
if (i < 0)
return ERR_PTR(-ENOENT);
diff --git a/drivers/gpu/drm/drm_edid_load.c b/drivers/gpu/drm/drm_edid_load.c
index 1e5593575d23..53c55fc8b8c2 100644
--- a/drivers/gpu/drm/drm_edid_load.c
+++ b/drivers/gpu/drm/drm_edid_load.c
@@ -174,7 +174,7 @@ static void *edid_load(struct drm_connector *connector, const char *name,
int i, valid_extensions = 0;
bool print_bad_edid = !connector->bad_edid_counter || (drm_debug & DRM_UT_KMS);
- builtin = match_string(generic_edid_name, GENERIC_EDIDS, name);
+ builtin = __match_string(generic_edid_name, GENERIC_EDIDS, name);
if (builtin >= 0) {
fwdata = generic_edid[builtin];
fwsize = sizeof(generic_edid[builtin]);
diff --git a/drivers/gpu/drm/drm_panel_orientation_quirks.c b/drivers/gpu/drm/drm_panel_orientation_quirks.c
index 521aff99b08a..063553adb22d 100644
--- a/drivers/gpu/drm/drm_panel_orientation_quirks.c
+++ b/drivers/gpu/drm/drm_panel_orientation_quirks.c
@@ -213,7 +213,7 @@ int drm_get_panel_orientation_quirk(int width, int height)
if (!bios_date)
continue;
- i = match_string(data->bios_dates, -1, bios_date);
+ i = __match_string(data->bios_dates, -1, bios_date);
if (i >= 0)
return data->orientation;
}
diff --git a/drivers/gpu/drm/i915/intel_pipe_crc.c b/drivers/gpu/drm/i915/intel_pipe_crc.c
index e7c7be4911c1..3e6af7600c25 100644
--- a/drivers/gpu/drm/i915/intel_pipe_crc.c
+++ b/drivers/gpu/drm/i915/intel_pipe_crc.c
@@ -440,7 +440,7 @@ display_crc_ctl_parse_source(const char *buf, enum intel_pipe_crc_source *s)
return 0;
}
- i = match_string(pipe_crc_sources, ARRAY_SIZE(pipe_crc_sources), buf);
+ i = __match_string(pipe_crc_sources, ARRAY_SIZE(pipe_crc_sources), buf);
if (i < 0)
return i;
diff --git a/drivers/ide/hpt366.c b/drivers/ide/hpt366.c
index fd3b5da44619..5e880a1ebcde 100644
--- a/drivers/ide/hpt366.c
+++ b/drivers/ide/hpt366.c
@@ -534,7 +534,7 @@ static const struct hpt_info hpt371n = {
static bool check_in_drive_list(ide_drive_t *drive, const char **list)
{
- return match_string(list, -1, (char *)&drive->id[ATA_ID_PROD]) >= 0;
+ return __match_string(list, -1, (char *)&drive->id[ATA_ID_PROD]) >= 0;
}
static struct hpt_info *hpt3xx_get_info(struct device *dev)
diff --git a/drivers/mfd/omap-usb-host.c b/drivers/mfd/omap-usb-host.c
index 800986a79704..9aaacb5bdb26 100644
--- a/drivers/mfd/omap-usb-host.c
+++ b/drivers/mfd/omap-usb-host.c
@@ -509,7 +509,7 @@ static int usbhs_omap_get_dt_pdata(struct device *dev,
continue;
/* get 'enum usbhs_omap_port_mode' from port mode string */
- ret = match_string(port_modes, ARRAY_SIZE(port_modes), mode);
+ ret = __match_string(port_modes, ARRAY_SIZE(port_modes), mode);
if (ret < 0) {
dev_warn(dev, "Invalid port%d-mode \"%s\" in device tree\n",
i, mode);
diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c
index 8d07ee1b8f08..59b7a6cac995 100644
--- a/drivers/mmc/host/sdhci-xenon-phy.c
+++ b/drivers/mmc/host/sdhci-xenon-phy.c
@@ -821,7 +821,7 @@ static int xenon_add_phy(struct device_node *np, struct sdhci_host *host,
struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
int ret;
- priv->phy_type = match_string(phy_types, NR_PHY_TYPES, phy_name);
+ priv->phy_type = __match_string(phy_types, NR_PHY_TYPES, phy_name);
if (priv->phy_type < 0) {
dev_err(mmc_dev(host->mmc),
"Unable to determine PHY name %s. Use default eMMC 5.1 PHY\n",
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c
index d4ff6b44de2c..969f09a56ba7 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c
@@ -667,7 +667,7 @@ iwl_dbgfs_bt_force_ant_write(struct iwl_mvm *mvm, char *buf,
};
int ret, bt_force_ant_mode;
- ret = match_string(modes_str, ARRAY_SIZE(modes_str), buf);
+ ret = __match_string(modes_str, ARRAY_SIZE(modes_str), buf);
if (ret < 0)
return ret;
diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c
index b45bc47d04fe..02f54802fca0 100644
--- a/drivers/pci/pcie/aer.c
+++ b/drivers/pci/pcie/aer.c
@@ -206,7 +206,7 @@ void pcie_ecrc_get_policy(char *str)
{
int i;
- i = match_string(ecrc_policy_str, ARRAY_SIZE(ecrc_policy_str), str);
+ i = __match_string(ecrc_policy_str, ARRAY_SIZE(ecrc_policy_str), str);
if (i < 0)
return;
diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c
index 0417213ed68b..060ba6a0a031 100644
--- a/drivers/phy/tegra/xusb.c
+++ b/drivers/phy/tegra/xusb.c
@@ -119,7 +119,7 @@ int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane,
if (err < 0)
return err;
- err = match_string(lane->soc->funcs, lane->soc->num_funcs, function);
+ err = __match_string(lane->soc->funcs, lane->soc->num_funcs, function);
if (err < 0) {
dev_err(dev, "invalid function \"%s\" for lane \"%pOFn\"\n",
function, np);
@@ -568,7 +568,7 @@ static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2)
usb2->internal = of_property_read_bool(np, "nvidia,internal");
if (!of_property_read_string(np, "mode", &mode)) {
- int err = match_string(modes, ARRAY_SIZE(modes), mode);
+ int err = __match_string(modes, ARRAY_SIZE(modes), mode);
if (err < 0) {
dev_err(&port->dev, "invalid value %s for \"mode\"\n",
mode);
diff --git a/drivers/pinctrl/mvebu/pinctrl-armada-37xx.c b/drivers/pinctrl/mvebu/pinctrl-armada-37xx.c
index 6462d3ca7ceb..07a5bcaa0067 100644
--- a/drivers/pinctrl/mvebu/pinctrl-armada-37xx.c
+++ b/drivers/pinctrl/mvebu/pinctrl-armada-37xx.c
@@ -348,7 +348,7 @@ static int armada_37xx_pmx_set_by_name(struct pinctrl_dev *pctldev,
dev_dbg(info->dev, "enable function %s group %s\n",
name, grp->name);
- func = match_string(grp->funcs, NB_FUNCS, name);
+ func = __match_string(grp->funcs, NB_FUNCS, name);
if (func < 0)
return -ENOTSUPP;
@@ -938,7 +938,7 @@ static int armada_37xx_fill_func(struct armada_37xx_pinctrl *info)
struct armada_37xx_pin_group *gp = &info->groups[g];
int f;
- f = match_string(gp->funcs, NB_FUNCS, name);
+ f = __match_string(gp->funcs, NB_FUNCS, name);
if (f < 0)
continue;
diff --git a/drivers/pinctrl/pinmux.c b/drivers/pinctrl/pinmux.c
index 4d0cc1889dd9..041326d0ab7b 100644
--- a/drivers/pinctrl/pinmux.c
+++ b/drivers/pinctrl/pinmux.c
@@ -348,7 +348,7 @@ int pinmux_map_to_setting(const struct pinctrl_map *map,
}
if (map->data.mux.group) {
group = map->data.mux.group;
- ret = match_string(groups, num_groups, group);
+ ret = __match_string(groups, num_groups, group);
if (ret < 0) {
dev_err(pctldev->dev,
"invalid group \"%s\" for function \"%s\"\n",
diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c
index 708fd58cd62b..1cf3b43a41e4 100644
--- a/drivers/power/supply/ab8500_btemp.c
+++ b/drivers/power/supply/ab8500_btemp.c
@@ -858,7 +858,7 @@ static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data)
* For all psy where the name of your driver
* appears in any supplied_to
*/
- j = match_string(supplicants, ext->num_supplicants, psy->desc->name);
+ j = __match_string(supplicants, ext->num_supplicants, psy->desc->name);
if (j < 0)
return 0;
diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index 98b335042ba6..8094f38e4085 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -1876,7 +1876,7 @@ static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data)
di = to_ab8500_charger_usb_device_info(usb_chg);
/* For all psy where the driver name appears in any supplied_to */
- j = match_string(supplicants, ext->num_supplicants, psy->desc->name);
+ j = __match_string(supplicants, ext->num_supplicants, psy->desc->name);
if (j < 0)
return 0;
diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index 776102c31305..408339c5a4a8 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -2174,7 +2174,7 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
* For all psy where the name of your driver
* appears in any supplied_to
*/
- j = match_string(supplicants, ext->num_supplicants, psy->desc->name);
+ j = __match_string(supplicants, ext->num_supplicants, psy->desc->name);
if (j < 0)
return 0;
diff --git a/drivers/power/supply/abx500_chargalg.c b/drivers/power/supply/abx500_chargalg.c
index 947709cdd14e..b2fcd0ba379d 100644
--- a/drivers/power/supply/abx500_chargalg.c
+++ b/drivers/power/supply/abx500_chargalg.c
@@ -946,7 +946,7 @@ static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data)
psy = (struct power_supply *)data;
di = power_supply_get_drvdata(psy);
/* For all psy where the driver name appears in any supplied_to */
- j = match_string(supplicants, ext->num_supplicants, psy->desc->name);
+ j = __match_string(supplicants, ext->num_supplicants, psy->desc->name);
if (j < 0)
return 0;
diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c
index a6900aa0d2ed..70e758e4f4f4 100644
--- a/drivers/power/supply/charger-manager.c
+++ b/drivers/power/supply/charger-manager.c
@@ -2022,8 +2022,8 @@ void cm_notify_event(struct power_supply *psy, enum cm_event_types type,
mutex_lock(&cm_list_mtx);
list_for_each_entry(cm, &cm_list, entry) {
- if (match_string(cm->desc->psy_charger_stat, -1,
- psy->desc->name) >= 0) {
+ if (__match_string(cm->desc->psy_charger_stat, -1,
+ psy->desc->name) >= 0) {
found_power_supply = true;
break;
}
diff --git a/drivers/staging/gdm724x/gdm_tty.c b/drivers/staging/gdm724x/gdm_tty.c
index 6e813693a766..6e147a324652 100644
--- a/drivers/staging/gdm724x/gdm_tty.c
+++ b/drivers/staging/gdm724x/gdm_tty.c
@@ -56,8 +56,8 @@ static int gdm_tty_install(struct tty_driver *driver, struct tty_struct *tty)
struct gdm *gdm = NULL;
int ret;
- ret = match_string(DRIVER_STRING, TTY_MAX_COUNT,
- tty->driver->driver_name);
+ ret = __match_string(DRIVER_STRING, TTY_MAX_COUNT,
+ tty->driver->driver_name);
if (ret < 0)
return -ENODEV;
diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c
index 18f5dcf58b0d..97f87d758e8a 100644
--- a/drivers/usb/common/common.c
+++ b/drivers/usb/common/common.c
@@ -84,7 +84,7 @@ enum usb_device_speed usb_get_maximum_speed(struct device *dev)
if (ret < 0)
return USB_SPEED_UNKNOWN;
- ret = match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed);
+ ret = __match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed);
return (ret < 0) ? USB_SPEED_UNKNOWN : ret;
}
@@ -122,7 +122,7 @@ static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str)
{
int ret;
- ret = match_string(usb_dr_modes, ARRAY_SIZE(usb_dr_modes), str);
+ ret = __match_string(usb_dr_modes, ARRAY_SIZE(usb_dr_modes), str);
return (ret < 0) ? USB_DR_MODE_UNKNOWN : ret;
}
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 2eb623841847..4abc5a76ec51 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -1409,8 +1409,8 @@ EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
*/
int typec_find_port_power_role(const char *name)
{
- return match_string(typec_port_power_roles,
- ARRAY_SIZE(typec_port_power_roles), name);
+ return __match_string(typec_port_power_roles,
+ ARRAY_SIZE(typec_port_power_roles), name);
}
EXPORT_SYMBOL_GPL(typec_find_port_power_role);
@@ -1424,7 +1424,7 @@ EXPORT_SYMBOL_GPL(typec_find_port_power_role);
*/
int typec_find_power_role(const char *name)
{
- return match_string(typec_roles, ARRAY_SIZE(typec_roles), name);
+ return __match_string(typec_roles, ARRAY_SIZE(typec_roles), name);
}
EXPORT_SYMBOL_GPL(typec_find_power_role);
@@ -1438,8 +1438,8 @@ EXPORT_SYMBOL_GPL(typec_find_power_role);
*/
int typec_find_port_data_role(const char *name)
{
- return match_string(typec_port_data_roles,
- ARRAY_SIZE(typec_port_data_roles), name);
+ return __match_string(typec_port_data_roles,
+ ARRAY_SIZE(typec_port_data_roles), name);
}
EXPORT_SYMBOL_GPL(typec_find_port_data_role);
diff --git a/drivers/usb/typec/tps6598x.c b/drivers/usb/typec/tps6598x.c
index c674abe3cf99..0389e4391faf 100644
--- a/drivers/usb/typec/tps6598x.c
+++ b/drivers/usb/typec/tps6598x.c
@@ -423,7 +423,7 @@ static int tps6598x_check_mode(struct tps6598x *tps)
if (ret)
return ret;
- switch (match_string(modes, ARRAY_SIZE(modes), mode)) {
+ switch (__match_string(modes, ARRAY_SIZE(modes), mode)) {
case TPS_MODE_APP:
return 0;
case TPS_MODE_BOOT:
diff --git a/drivers/vfio/vfio.c b/drivers/vfio/vfio.c
index 82fcf07fa9ea..01c7bb7316fb 100644
--- a/drivers/vfio/vfio.c
+++ b/drivers/vfio/vfio.c
@@ -638,9 +638,9 @@ static bool vfio_dev_whitelisted(struct device *dev, struct device_driver *drv)
return true;
}
- return match_string(vfio_driver_whitelist,
- ARRAY_SIZE(vfio_driver_whitelist),
- drv->name) >= 0;
+ return __match_string(vfio_driver_whitelist,
+ ARRAY_SIZE(vfio_driver_whitelist),
+ drv->name) >= 0;
}
/*
diff --git a/drivers/video/fbdev/pxafb.c b/drivers/video/fbdev/pxafb.c
index d59c8a59f582..0025781e6e1e 100644
--- a/drivers/video/fbdev/pxafb.c
+++ b/drivers/video/fbdev/pxafb.c
@@ -2129,7 +2129,7 @@ static int of_get_pxafb_display(struct device *dev, struct device_node *disp,
if (ret)
s = "color-tft";
- i = match_string(lcd_types, -1, s);
+ i = __match_string(lcd_types, -1, s);
if (i < 0) {
dev_err(dev, "lcd-type %s is unknown\n", s);
return i;
diff --git a/fs/ubifs/auth.c b/fs/ubifs/auth.c
index 60f43b93d06e..076feb5a9cb6 100644
--- a/fs/ubifs/auth.c
+++ b/fs/ubifs/auth.c
@@ -216,8 +216,8 @@ int ubifs_init_authentication(struct ubifs_info *c)
return -EINVAL;
}
- c->auth_hash_algo = match_string(hash_algo_name, HASH_ALGO__LAST,
- c->auth_hash_name);
+ c->auth_hash_algo = __match_string(hash_algo_name, HASH_ALGO__LAST,
+ c->auth_hash_name);
if ((int)c->auth_hash_algo < 0) {
ubifs_err(c, "Unknown hash algo %s specified",
c->auth_hash_name);
diff --git a/include/linux/string.h b/include/linux/string.h
index 4deb11f7976b..7149fcdf62df 100644
--- a/include/linux/string.h
+++ b/include/linux/string.h
@@ -195,7 +195,7 @@ static inline int strtobool(const char *s, bool *res)
return kstrtobool(s, res);
}
-int match_string(const char * const *array, size_t n, const char *string);
+int __match_string(const char * const *array, size_t n, const char *string);
int __sysfs_match_string(const char * const *array, size_t n, const char *s);
/**
diff --git a/kernel/cgroup/rdma.c b/kernel/cgroup/rdma.c
index 1d75ae7f1cb7..65d4df148603 100644
--- a/kernel/cgroup/rdma.c
+++ b/kernel/cgroup/rdma.c
@@ -367,7 +367,7 @@ static int parse_resource(char *c, int *intval)
if (!name || !value)
return -EINVAL;
- i = match_string(rdmacg_resource_names, RDMACG_RESOURCE_MAX, name);
+ i = __match_string(rdmacg_resource_names, RDMACG_RESOURCE_MAX, name);
if (i < 0)
return i;
diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c
index 678bfb9bd87f..ef89323c1541 100644
--- a/kernel/sched/debug.c
+++ b/kernel/sched/debug.c
@@ -111,7 +111,7 @@ static int sched_feat_set(char *cmp)
cmp += 3;
}
- i = match_string(sched_feat_names, __SCHED_FEAT_NR, cmp);
+ i = __match_string(sched_feat_names, __SCHED_FEAT_NR, cmp);
if (i < 0)
return i;
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 1c80521fd436..a818c6145d94 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4625,7 +4625,7 @@ static int trace_set_options(struct trace_array *tr, char *option)
mutex_lock(&trace_types_lock);
- ret = match_string(trace_options, -1, cmp);
+ ret = __match_string(trace_options, -1, cmp);
/* If no option could be set, test the specific tracer options */
if (ret < 0)
ret = set_tracer_option(tr, cmp, neg);
diff --git a/lib/string.c b/lib/string.c
index e2cf5acc83bd..1797cf31760c 100644
--- a/lib/string.c
+++ b/lib/string.c
@@ -666,7 +666,7 @@ bool sysfs_streq(const char *s1, const char *s2)
EXPORT_SYMBOL(sysfs_streq);
/**
- * match_string - matches given string in an array
+ * __match_string - matches given string in an array
* @array: array of strings
* @n: number of strings in the array or -1 for NULL terminated arrays
* @string: string to match with
@@ -674,7 +674,7 @@ EXPORT_SYMBOL(sysfs_streq);
* Return:
* index of a @string in the @array if matches, or %-EINVAL otherwise.
*/
-int match_string(const char * const *array, size_t n, const char *string)
+int __match_string(const char * const *array, size_t n, const char *string)
{
int index;
const char *item;
@@ -692,7 +692,7 @@ int match_string(const char * const *array, size_t n, const char *string)
return -EINVAL;
}
-EXPORT_SYMBOL(match_string);
+EXPORT_SYMBOL(__match_string);
/**
* __sysfs_match_string - matches given string in an array
@@ -700,7 +700,7 @@ EXPORT_SYMBOL(match_string);
* @n: number of strings in the array or -1 for NULL terminated arrays
* @str: string to match with
*
- * Returns index of @str in the @array or -EINVAL, just like match_string().
+ * Returns index of @str in the @array or -EINVAL, just like __match_string().
* Uses sysfs_streq instead of strcmp for matching.
*/
int __sysfs_match_string(const char * const *array, size_t n, const char *str)
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 2219e747df49..97bcf4658317 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -2755,7 +2755,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
if (flags)
*flags++ = '\0'; /* terminate mode string */
- mode = match_string(policy_modes, MPOL_MAX, str);
+ mode = __match_string(policy_modes, MPOL_MAX, str);
if (mode < 0)
goto out;
diff --git a/mm/vmpressure.c b/mm/vmpressure.c
index 4854584ec436..d43f33139568 100644
--- a/mm/vmpressure.c
+++ b/mm/vmpressure.c
@@ -378,7 +378,7 @@ int vmpressure_register_event(struct mem_cgroup *memcg,
/* Find required level */
token = strsep(&spec, ",");
- level = match_string(vmpressure_str_levels, VMPRESSURE_NUM_LEVELS, token);
+ level = __match_string(vmpressure_str_levels, VMPRESSURE_NUM_LEVELS, token);
if (level < 0) {
ret = level;
goto out;
@@ -387,7 +387,7 @@ int vmpressure_register_event(struct mem_cgroup *memcg,
/* Find optional mode */
token = strsep(&spec, ",");
if (token) {
- mode = match_string(vmpressure_str_modes, VMPRESSURE_NUM_MODES, token);
+ mode = __match_string(vmpressure_str_modes, VMPRESSURE_NUM_MODES, token);
if (mode < 0) {
ret = mode;
goto out;
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 87500bde5a92..45d28db85e5a 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -1480,7 +1480,7 @@ static int param_set_audit(const char *val, const struct kernel_param *kp)
if (apparmor_initialized && !policy_admin_capable(NULL))
return -EPERM;
- i = match_string(audit_mode_names, AUDIT_MAX_INDEX, val);
+ i = __match_string(audit_mode_names, AUDIT_MAX_INDEX, val);
if (i < 0)
return -EINVAL;
@@ -1509,7 +1509,7 @@ static int param_set_mode(const char *val, const struct kernel_param *kp)
if (apparmor_initialized && !policy_admin_capable(NULL))
return -EPERM;
- i = match_string(aa_profile_mode_names, APPARMOR_MODE_NAMES_MAX_INDEX,
+ i = __match_string(aa_profile_mode_names, APPARMOR_MODE_NAMES_MAX_INDEX,
val);
if (i < 0)
return -EINVAL;
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 357edd140c09..618842f85f2d 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -61,7 +61,7 @@ static int __init hash_setup(char *str)
goto out;
}
- i = match_string(hash_algo_name, HASH_ALGO__LAST, str);
+ i = __match_string(hash_algo_name, HASH_ALGO__LAST, str);
if (i < 0)
return 1;
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 3d27f3378d5d..9ec5316f3bb5 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -57,7 +57,7 @@ static bool detect_loud_models(struct fw_unit *unit)
if (err < 0)
return false;
- return match_string(models, ARRAY_SIZE(models), model) >= 0;
+ return __match_string(models, ARRAY_SIZE(models), model) >= 0;
}
static int name_card(struct snd_oxfw *oxfw)
diff --git a/sound/pci/oxygen/oxygen_mixer.c b/sound/pci/oxygen/oxygen_mixer.c
index 81af21ac1439..13c2fb75fd71 100644
--- a/sound/pci/oxygen/oxygen_mixer.c
+++ b/sound/pci/oxygen/oxygen_mixer.c
@@ -1086,7 +1086,7 @@ static int add_controls(struct oxygen *chip,
err = snd_ctl_add(chip->card, ctl);
if (err < 0)
return err;
- j = match_string(known_ctl_names, CONTROL_COUNT, ctl->id.name);
+ j = __match_string(known_ctl_names, CONTROL_COUNT, ctl->id.name);
if (j >= 0) {
chip->controls[j] = ctl;
ctl->private_free = oxygen_any_ctl_free;
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
index ca172a4b6849..3ef743075bda 100644
--- a/sound/soc/codecs/max98088.c
+++ b/sound/soc/codecs/max98088.c
@@ -1405,7 +1405,7 @@ static int max98088_get_channel(struct snd_soc_component *component, const char
{
int ret;
- ret = match_string(eq_mode_name, ARRAY_SIZE(eq_mode_name), name);
+ ret = __match_string(eq_mode_name, ARRAY_SIZE(eq_mode_name), name);
if (ret < 0)
dev_err(component->dev, "Bad EQ channel name '%s'\n", name);
return ret;
diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c
index 3b3a10da7f40..cd69916d5dcb 100644
--- a/sound/soc/codecs/max98095.c
+++ b/sound/soc/codecs/max98095.c
@@ -1636,7 +1636,7 @@ static int max98095_get_bq_channel(struct snd_soc_component *component,
{
int ret;
- ret = match_string(bq_mode_name, ARRAY_SIZE(bq_mode_name), name);
+ ret = __match_string(bq_mode_name, ARRAY_SIZE(bq_mode_name), name);
if (ret < 0)
dev_err(component->dev, "Bad biquad channel name '%s'\n", name);
return ret;
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 81a7a12196ff..33ccea0518b1 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -753,7 +753,7 @@ static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
item = 0;
}
- i = match_string(e->texts, e->items, control_name);
+ i = __match_string(e->texts, e->items, control_name);
if (i < 0)
return -ENODEV;
--
2.20.1
^ permalink raw reply related
* [PATCH 3/3][V2] lib: re-introduce new match_string() helper/macro
From: Alexandru Ardelean @ 2019-05-28 7:39 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel, linux-ide, linux-clk,
linux-rpi-kernel, linux-arm-kernel, linux-rockchip, linux-pm,
linux-gpio, dri-devel, intel-gfx, linux-omap, linux-mmc,
linux-wireless, netdev, linux-pci, linux-tegra, devel, linux-usb,
kvm, linux-fbdev, linux-mtd, cgroups, linux-mm,
linux-security-module, linux-integrity, alsa-devel
Cc: heikki.krogerus, gregkh, andriy.shevchenko, Alexandru Ardelean
In-Reply-To: <20190528073932.25365-1-alexandru.ardelean@analog.com>
This change re-introduces `match_string()` as a macro that uses
ARRAY_SIZE() to compute the size of the array.
After this change, work can start on migrating subsystems to use this new
helper. Since the original helper is pretty used, migrating to this new one
will take a while, and will be reviewed by each subsystem.
Signed-off-by: Alexandru Ardelean <alexandru.ardelean@analog.com>
---
include/linux/string.h | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/include/linux/string.h b/include/linux/string.h
index 7149fcdf62df..34491b075449 100644
--- a/include/linux/string.h
+++ b/include/linux/string.h
@@ -198,6 +198,15 @@ static inline int strtobool(const char *s, bool *res)
int __match_string(const char * const *array, size_t n, const char *string);
int __sysfs_match_string(const char * const *array, size_t n, const char *s);
+/**
+ * match_string - matches given string in an array
+ * @_a: array of strings
+ * @_s: string to match with
+ *
+ * Helper for __match_string(). Calculates the size of @a automatically.
+ */
+#define match_string(_a, _s) __match_string(_a, ARRAY_SIZE(_a), _s)
+
/**
* sysfs_match_string - matches given string in an array
* @_a: array of strings
--
2.20.1
^ permalink raw reply related
* [PATCH 1/3][V2] lib: fix match_string() helper on -1 array size
From: Alexandru Ardelean @ 2019-05-28 7:39 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel, linux-ide, linux-clk,
linux-rpi-kernel, linux-arm-kernel, linux-rockchip, linux-pm,
linux-gpio, dri-devel, intel-gfx, linux-omap, linux-mmc,
linux-wireless, netdev, linux-pci, linux-tegra, devel, linux-usb,
kvm, linux-fbdev, linux-mtd, cgroups, linux-mm,
linux-security-module, linux-integrity, alsa-devel
Cc: heikki.krogerus, gregkh, andriy.shevchenko, Alexandru Ardelean
In-Reply-To: <20190508112842.11654-1-alexandru.ardelean@analog.com>
The documentation the `_match_string()` helper mentions that `n`
should be:
* @n: number of strings in the array or -1 for NULL terminated arrays
The behavior of the function is different, in the sense that it exits on
the first NULL element in the array, regardless of whether `n` is -1 or a
positive number.
This patch changes the behavior, to exit the loop when a NULL element is
found and n == -1. Essentially, this aligns the behavior with the
doc-string.
There are currently many users of `match_string()`, and so, in order to go
through them, the next patches in the series will focus on doing some
cosmetic changes, which are aimed at grouping the users of
`match_string()`.
Signed-off-by: Alexandru Ardelean <alexandru.ardelean@analog.com>
---
Changelog v1 -> v2:
* split the initial series into just 3 patches that fix the
`match_string()` helper and start introducing a new version of this
helper, which computes array-size of static arrays
lib/string.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/lib/string.c b/lib/string.c
index 6016eb3ac73d..e2cf5acc83bd 100644
--- a/lib/string.c
+++ b/lib/string.c
@@ -681,8 +681,11 @@ int match_string(const char * const *array, size_t n, const char *string)
for (index = 0; index < n; index++) {
item = array[index];
- if (!item)
+ if (!item) {
+ if (n != (size_t)-1)
+ continue;
break;
+ }
if (!strcmp(item, string))
return index;
}
--
2.20.1
^ permalink raw reply related
* Re: KASAN: invalid-free in tomoyo_realpath_from_path
From: Tetsuo Handa @ 2019-05-28 7:38 UTC (permalink / raw)
To: syzbot
Cc: jmorris, linux-kernel, linux-security-module, netdev, serge,
syzkaller-bugs, takedakn
In-Reply-To: <000000000000785e9d0589ec359a@google.com>
Well, I don't think this is a TOMOYO's problem.
On 2019/05/28 14:48, syzbot wrote:
> CPU: 1 PID: 11697 Comm: syz-executor.3 Not tainted 5.2.0-rc1+ #2
> Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
> Call Trace:
(...snipped...)
> kfree+0xcf/0x220 mm/slab.c:3755
> tomoyo_realpath_from_path+0x1de/0x7a0 security/tomoyo/realpath.c:319
(...snipped...)
> Allocated by task 11696:
(...snipped...)
> kmalloc include/linux/slab.h:552 [inline]
> tomoyo_realpath_from_path+0xcd/0x7a0 security/tomoyo/realpath.c:277
(...snipped...)
>
> Freed by task 11696:
(...snipped...)
> kfree+0xcf/0x220 mm/slab.c:3755
> tomoyo_realpath_from_path+0x1de/0x7a0 security/tomoyo/realpath.c:319
Since the "buf" variable is a local variable, it cannot be shared between
two threads. Since "buf" is assigned as
buf = kmalloc(buf_len, GFP_NOFS);
and nobody else is reassigning "buf",
kfree(buf);
can't become an invalid free.
Let's wait for a reproducer...
^ permalink raw reply
* KASAN: invalid-free in tomoyo_realpath_from_path
From: syzbot @ 2019-05-28 5:48 UTC (permalink / raw)
To: jmorris, linux-kernel, linux-security-module, netdev,
penguin-kernel, serge, syzkaller-bugs, takedakn
Hello,
syzbot found the following crash on:
HEAD commit: f4aa8012 cxgb4: Make t4_get_tp_e2c_map static
git tree: net-next
console output: https://syzkaller.appspot.com/x/log.txt?x=173328baa00000
kernel config: https://syzkaller.appspot.com/x/.config?x=d137eb988ffd93c3
dashboard link: https://syzkaller.appspot.com/bug?extid=9742b1c6c7aedf18beda
compiler: gcc (GCC) 9.0.0 20181231 (experimental)
Unfortunately, I don't have any reproducer for this crash yet.
IMPORTANT: if you fix the bug, please add the following tag to the commit:
Reported-by: syzbot+9742b1c6c7aedf18beda@syzkaller.appspotmail.com
==================================================================
BUG: KASAN: double-free or invalid-free in
tomoyo_realpath_from_path+0x1de/0x7a0 security/tomoyo/realpath.c:319
CPU: 1 PID: 11697 Comm: syz-executor.3 Not tainted 5.2.0-rc1+ #2
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS
Google 01/01/2011
Call Trace:
__dump_stack lib/dump_stack.c:77 [inline]
dump_stack+0x172/0x1f0 lib/dump_stack.c:113
print_address_description.cold+0x7c/0x20d mm/kasan/report.c:188
kasan_report_invalid_free+0x65/0xa0 mm/kasan/report.c:279
__kasan_slab_free+0x13a/0x150 mm/kasan/common.c:430
kasan_slab_free+0xe/0x10 mm/kasan/common.c:459
__cache_free mm/slab.c:3432 [inline]
kfree+0xcf/0x220 mm/slab.c:3755
tomoyo_realpath_from_path+0x1de/0x7a0 security/tomoyo/realpath.c:319
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_check_open_permission+0x2a8/0x3f0 security/tomoyo/file.c:771
tomoyo_file_open security/tomoyo/tomoyo.c:319 [inline]
tomoyo_file_open+0xa9/0xd0 security/tomoyo/tomoyo.c:314
security_file_open+0x71/0x300 security/security.c:1458
do_dentry_open+0x373/0x1250 fs/open.c:765
vfs_open+0xa0/0xd0 fs/open.c:887
do_last fs/namei.c:3416 [inline]
path_openat+0x10e9/0x46d0 fs/namei.c:3533
do_filp_open+0x1a1/0x280 fs/namei.c:3563
do_sys_open+0x3fe/0x5d0 fs/open.c:1070
__do_sys_open fs/open.c:1088 [inline]
__se_sys_open fs/open.c:1083 [inline]
__x64_sys_open+0x7e/0xc0 fs/open.c:1083
do_syscall_64+0xfd/0x680 arch/x86/entry/common.c:301
entry_SYSCALL_64_after_hwframe+0x49/0xbe
RIP: 0033:0x4571f0
Code: 31 c0 e9 45 ff ff ff 0f 1f 00 80 3f 00 0f 84 f7 00 00 00 55 53 b9 02
00 00 00 be 00 08 09 00 89 c8 48 81 ec 98 00 00 00 0f 05 <48> 3d 00 f0 ff
ff 48 89 c3 0f 87 e9 00 00 00 85 db 0f 88 2f 01 00
RSP: 002b:00007ffee56b06a0 EFLAGS: 00000206 ORIG_RAX: 0000000000000002
RAX: ffffffffffffffda RBX: 00000000000cb440 RCX: 00000000004571f0
RDX: 000000000000000c RSI: 0000000000090800 RDI: 00007ffee56b1880
RBP: 0000000000000002 R08: 0000000000000001 R09: 0000555557133940
R10: 0000000000000000 R11: 0000000000000206 R12: 00007ffee56b1880
R13: 00007ffee56b1870 R14: 0000000000000000 R15: 00007ffee56b1880
Allocated by task 11696:
save_stack+0x23/0x90 mm/kasan/common.c:71
set_track mm/kasan/common.c:79 [inline]
__kasan_kmalloc mm/kasan/common.c:489 [inline]
__kasan_kmalloc.constprop.0+0xcf/0xe0 mm/kasan/common.c:462
kasan_kmalloc+0x9/0x10 mm/kasan/common.c:503
__do_kmalloc mm/slab.c:3660 [inline]
__kmalloc+0x15c/0x740 mm/slab.c:3669
kmalloc include/linux/slab.h:552 [inline]
tomoyo_realpath_from_path+0xcd/0x7a0 security/tomoyo/realpath.c:277
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_check_open_permission+0x2a8/0x3f0 security/tomoyo/file.c:771
tomoyo_file_open security/tomoyo/tomoyo.c:319 [inline]
tomoyo_file_open+0xa9/0xd0 security/tomoyo/tomoyo.c:314
security_file_open+0x71/0x300 security/security.c:1458
do_dentry_open+0x373/0x1250 fs/open.c:765
vfs_open+0xa0/0xd0 fs/open.c:887
do_last fs/namei.c:3416 [inline]
path_openat+0x10e9/0x46d0 fs/namei.c:3533
do_filp_open+0x1a1/0x280 fs/namei.c:3563
do_sys_open+0x3fe/0x5d0 fs/open.c:1070
__do_sys_open fs/open.c:1088 [inline]
__se_sys_open fs/open.c:1083 [inline]
__x64_sys_open+0x7e/0xc0 fs/open.c:1083
do_syscall_64+0xfd/0x680 arch/x86/entry/common.c:301
entry_SYSCALL_64_after_hwframe+0x49/0xbe
Freed by task 11696:
save_stack+0x23/0x90 mm/kasan/common.c:71
set_track mm/kasan/common.c:79 [inline]
__kasan_slab_free+0x102/0x150 mm/kasan/common.c:451
kasan_slab_free+0xe/0x10 mm/kasan/common.c:459
__cache_free mm/slab.c:3432 [inline]
kfree+0xcf/0x220 mm/slab.c:3755
tomoyo_realpath_from_path+0x1de/0x7a0 security/tomoyo/realpath.c:319
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_check_open_permission+0x2a8/0x3f0 security/tomoyo/file.c:771
tomoyo_file_open security/tomoyo/tomoyo.c:319 [inline]
tomoyo_file_open+0xa9/0xd0 security/tomoyo/tomoyo.c:314
security_file_open+0x71/0x300 security/security.c:1458
do_dentry_open+0x373/0x1250 fs/open.c:765
vfs_open+0xa0/0xd0 fs/open.c:887
do_last fs/namei.c:3416 [inline]
path_openat+0x10e9/0x46d0 fs/namei.c:3533
do_filp_open+0x1a1/0x280 fs/namei.c:3563
do_sys_open+0x3fe/0x5d0 fs/open.c:1070
__do_sys_open fs/open.c:1088 [inline]
__se_sys_open fs/open.c:1083 [inline]
__x64_sys_open+0x7e/0xc0 fs/open.c:1083
do_syscall_64+0xfd/0x680 arch/x86/entry/common.c:301
entry_SYSCALL_64_after_hwframe+0x49/0xbe
The buggy address belongs to the object at ffff88808b756780
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1792 bytes inside of
4096-byte region [ffff88808b756780, ffff88808b757780)
The buggy address belongs to the page:
page:ffffea00022dd580 refcount:1 mapcount:0 mapping:ffff8880aa400dc0
index:0x0 compound_mapcount: 0
flags: 0x1fffc0000010200(slab|head)
raw: 01fffc0000010200 ffffea0002924e08 ffffea00027a6588 ffff8880aa400dc0
raw: 0000000000000000 ffff88808b756780 0000000100000001 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff88808b756d80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88808b756e00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
> ffff88808b756e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
^
ffff88808b756f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ffff88808b756f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
==================================================================
---
This bug is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
syzbot will keep track of this bug report. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
^ permalink raw reply
* Help to get traffic
From: Alex Walker @ 2019-05-27 11:59 UTC (permalink / raw)
To: linux-security-module
Hi
Do you need traffic for your website, or ecommerce store?
We can bring 1-2 thousands of visitors to your website daily.
No matter what you are selling, products or service. Getting more traffic
is the key to your business.
Please reply if interested, we will go options for you.
Thanks,
Alex Walker
Whatsapp: +8617199402387
^ permalink raw reply
* [PATCH] smack: remove redundant assignment to variable m
From: Colin King @ 2019-05-27 17:52 UTC (permalink / raw)
To: Casey Schaufler, James Morris, Serge E . Hallyn,
linux-security-module
Cc: kernel-janitors, linux-kernel
From: Colin Ian King <colin.king@canonical.com>
Variable m is assigned a value that is never read and m is later
reassigned in for-loop. This the assignment is redundant and can
be mored.
Addresses-Coverity: ("Unused value")
Signed-off-by: Colin Ian King <colin.king@canonical.com>
---
security/smack/smackfs.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c
index 47f73a0dabb1..b66adafeb5fa 100644
--- a/security/smack/smackfs.c
+++ b/security/smack/smackfs.c
@@ -1174,7 +1174,6 @@ static ssize_t smk_write_net4addr(struct file *file, const char __user *buf,
rc = -EINVAL;
goto free_out;
}
- m = BEBITS;
masks = 32;
}
if (masks > BEBITS) {
--
2.20.1
^ permalink raw reply related
* Re: [PATCH V7 0/4] Add support for crypto agile logs
From: Jarkko Sakkinen @ 2019-05-27 14:31 UTC (permalink / raw)
To: James Morris
Cc: Matthew Garrett, linux-integrity, peterhuewe, jgg, roberto.sassu,
linux-efi, linux-security-module, linux-kernel, tweek, bsz,
Linus Torvalds
In-Reply-To: <alpine.LRH.2.21.1905250506320.7233@namei.org>
On Sat, May 25, 2019 at 05:22:34AM +1000, James Morris wrote:
> On Fri, 24 May 2019, Jarkko Sakkinen wrote:
>
> > I'm referring to these:
> >
> > https://lore.kernel.org/linux-integrity/20190329115544.GA27351@linux.intel.com/
> >
> > I got response from you that those were applied and there is another
> > response in that thread that they are being sent to Linus. That is why I
> > haven't done anything since. Most of them are critical fixes to v5.1
> > changes.
>
> These are in Linus' tree.
>
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a556810d8e06aa2da8bbe22da3d105eb5a0d0c7d
>
> I initially queued them in the next-tpm branch, but forgot to drop them
> from there after sending them to Linus as a v5.1 fix. Linus was not happy
> to see them again in the v5.2 merge window.
>
> Apologies for the confusion.
OK, just to confirm, my next PR will go straight to Linus?
/Jarkko
^ permalink raw reply
* Re: SGX vs LSM (Re: [PATCH v20 00/28] Intel SGX1 support)
From: Jarkko Sakkinen @ 2019-05-27 13:48 UTC (permalink / raw)
To: Andy Lutomirski
Cc: Sean Christopherson, Stephen Smalley, James Morris,
Serge E. Hallyn, LSM List, Paul Moore, Eric Paris, selinux,
Jethro Beekman, Xing, Cedric, Hansen, Dave, Thomas Gleixner,
Dr. Greg, Linus Torvalds, LKML, X86 ML, linux-sgx@vger.kernel.org,
Andrew Morton, nhorman@redhat.com, npmccallum@redhat.com,
Ayoun, Serge, Katz-zamir, Shay, Huang, Haitao, Andy Shevchenko,
Svahn, Kai, Borislav Petkov, Josh Triplett, Huang, Kai,
David Rientjes
In-Reply-To: <CALCETrUzx3LPAKCLFf75P-XshAkRcr+JLET3LA_kHDs9MA11FA@mail.gmail.com>
On Thu, May 23, 2019 at 08:38:17AM -0700, Andy Lutomirski wrote:
> On Thu, May 23, 2019 at 7:17 AM Sean Christopherson
> <sean.j.christopherson@intel.com> wrote:
> >
> > On Thu, May 23, 2019 at 01:26:28PM +0300, Jarkko Sakkinen wrote:
> > > On Wed, May 22, 2019 at 07:35:17PM -0700, Sean Christopherson wrote:
> > > > But actually, there's no need to disallow mmap() after ECREATE since the
> > > > LSM checks also apply to mmap(), e.g. FILE__EXECUTE would be needed to
> > > > mmap() any enclave pages PROT_EXEC. I guess my past self thought mmap()
> > > > bypassed LSM checks? The real problem is that mmap()'ng an existing
> > > > enclave would require FILE__WRITE and FILE__EXECUTE, which puts us back
> > > > at square one.
> > >
> > > I'm lost with the constraints we want to set.
> >
> > As is today, SELinux policies would require enclave loaders to have
> > FILE__WRITE and FILE__EXECUTE permissions on /dev/sgx/enclave. Presumably
> > other LSMs have similar requirements. Requiring all processes to have
> > FILE__{WRITE,EXECUTE} permissions means the permissions don't add much
> > value, e.g. they can't be used to distinguish between an enclave that is
> > being loaded from an unmodified file and an enclave that is being
> > generated on the fly, e.g. Graphene.
> >
> > Looking back at Andy's mail, he was talking about requiring FILE__EXECUTE
> > to run an enclave, so perhaps it's only FILE__WRITE that we're trying to
> > special case.
> >
>
> I thought about this some more, and I have a new proposal that helps
> address the ELRANGE alignment issue and the permission issue at the
> cost of some extra verbosity. Maybe you all can poke holes in it :)
> The basic idea is to make everything more explicit from a user's
> perspective. Here's how it works:
>
> Opening /dev/sgx/enclave gives an enclave_fd that, by design, doesn't
> give EXECUTE or WRITE. mmap() on the enclave_fd only works if you
> pass PROT_NONE and gives the correct alignment. The resulting VMA
> cannot be mprotected or mremapped. It can't be mmapped at all until
> after ECREATE because the alignment isn't known before that.
How to deny mprotect()? struct file_operations does not have callback
for that (AFAIK).
> Associated with the enclave are a bunch (up to 7) "enclave segment
> inodes". These are anon_inodes that are created automagically. An
> enclave segment is a group of pages, not necessary contiguous, with an
> upper bound on the memory permissions. Each enclave page belongs to a
> segment. When you do EADD, you tell the driver what segment you're
> adding to. [0] This means that EADD gets an extra argument that is a
> permission mask for the page -- in addition to the initial SECINFO,
> you also pass to EADD something to the effect of "I promise never to
> map this with permissions greater than RX".
>
> Then we just need some way to mmap a region from an enclave segment.
> This could be done by having a way to get an fd for an enclave segment
> or it could be done by having a new ioctl SGX_IOC_MAP_SEGMENT. User
> code would use this operation to replace, MAP_FIXED-style, ranges from
> the big PROT_NONE mapping with the relevant pages from the enclave
> segment. The resulting vma would only have VM_MAYWRITE if the segment
> is W, only have VM_MAYEXEC if the segment is X, and only have
> VM_MAYREAD if the segment is R. Depending on implementation details,
> the VMAs might need to restrict mremap() to avoid mapping pages that
> aren't part of the segment in question.
>
> It's plausible that this whole thing works without the magic segment
> inodes under the hood, but figuring that out would need a careful look
> at how all the core mm bits and LSM bits work together.
>
> To get all the LSM stuff to work, SELinux will need some way to
> automatically assign an appropriate label to the segment inodes. I
> assume that such a mechanism already exists and gets used for things
> like sockets, but I haven't actually confirmed this.
>
> [0] There needs to be some vaguely intelligent semantics if you EADD
> the *same* address more than once. A simple solution would be to
> disallow it if the segments don't match.
What if instead simply:
- Require to do PROT_NONE mmap() for the ELRANGE before ECREATE.
- Disallow mprotect() up until EINIT.
- Given that we have a callback for mprotect() check that permissions
match EADD'd permissions.
/Jarkko
^ permalink raw reply
* Re: SGX vs LSM (Re: [PATCH v20 00/28] Intel SGX1 support)
From: Jarkko Sakkinen @ 2019-05-27 13:38 UTC (permalink / raw)
To: Sean Christopherson
Cc: Andy Lutomirski, Stephen Smalley, James Morris, Serge E. Hallyn,
LSM List, Paul Moore, Eric Paris, selinux, Jethro Beekman,
Xing, Cedric, Hansen, Dave, Thomas Gleixner, Dr. Greg,
Linus Torvalds, LKML, X86 ML, linux-sgx@vger.kernel.org,
Andrew Morton, nhorman@redhat.com, npmccallum@redhat.com,
Ayoun, Serge, Katz-zamir, Shay, Huang, Haitao, Andy Shevchenko,
Svahn, Kai, Borislav Petkov, Josh Triplett, Huang, Kai,
David Rientjes
In-Reply-To: <20190527133418.GA9732@linux.intel.com>
On Mon, May 27, 2019 at 04:34:31PM +0300, Jarkko Sakkinen wrote:
> On Thu, May 23, 2019 at 07:17:52AM -0700, Sean Christopherson wrote:
> > 1. Do nothing. Userspace would essentially be required to mmap() the
> > enclave after EINIT, which is ugly but not breaking since userspace
> > could mmap() the enclave with a placeholder VMA prior to building
> > the enclave, and then a series of mmap() to establish its "real"
> > mapping.
>
> What it'd break to return error if mmap() is done before EINIT?
>
> > 2. Propagate the permissions from EADD to the VMAs of the current mm
> > if the entire EADD range is mapped and the mapping is PROT_NONE.
>
> Right now you can do multiple mmap's. If the mmap's must be done after
> EINIT, the driver could check that permissions match the permissions in
> that range.
>
> This leaves open how to deal with mprotect() but if the process does not
> have FILE__WRITE I guess you cannot do much.
>
> > 3. Propagate the permissions from EADD to the VMAs of all mm structs
> > that have mapped some piece of the enclave, following the matching
> > rules from #2.
>
> For me it looks that allowing mmap's only after EINIT would result the
> least confusing implemntation.
Obvious problem is of course the requirement of fixed mapping, which is
of course nasty.
/Jarkko
^ permalink raw reply
* Re: SGX vs LSM (Re: [PATCH v20 00/28] Intel SGX1 support)
From: Jarkko Sakkinen @ 2019-05-27 13:34 UTC (permalink / raw)
To: Sean Christopherson
Cc: Andy Lutomirski, Stephen Smalley, James Morris, Serge E. Hallyn,
LSM List, Paul Moore, Eric Paris, selinux, Jethro Beekman,
Xing, Cedric, Hansen, Dave, Thomas Gleixner, Dr. Greg,
Linus Torvalds, LKML, X86 ML, linux-sgx@vger.kernel.org,
Andrew Morton, nhorman@redhat.com, npmccallum@redhat.com,
Ayoun, Serge, Katz-zamir, Shay, Huang, Haitao, Andy Shevchenko,
Svahn, Kai, Borislav Petkov, Josh Triplett, Huang, Kai,
David Rientjes
In-Reply-To: <20190523141752.GA12078@linux.intel.com>
On Thu, May 23, 2019 at 07:17:52AM -0700, Sean Christopherson wrote:
> 1. Do nothing. Userspace would essentially be required to mmap() the
> enclave after EINIT, which is ugly but not breaking since userspace
> could mmap() the enclave with a placeholder VMA prior to building
> the enclave, and then a series of mmap() to establish its "real"
> mapping.
What it'd break to return error if mmap() is done before EINIT?
> 2. Propagate the permissions from EADD to the VMAs of the current mm
> if the entire EADD range is mapped and the mapping is PROT_NONE.
Right now you can do multiple mmap's. If the mmap's must be done after
EINIT, the driver could check that permissions match the permissions in
that range.
This leaves open how to deal with mprotect() but if the process does not
have FILE__WRITE I guess you cannot do much.
> 3. Propagate the permissions from EADD to the VMAs of all mm structs
> that have mapped some piece of the enclave, following the matching
> rules from #2.
For me it looks that allowing mmap's only after EINIT would result the
least confusing implemntation.
/Jarkko
^ permalink raw reply
* Re: [PATCH 10/10] docs: fix broken documentation links
From: Rafael J. Wysocki @ 2019-05-27 8:43 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Linux Doc Mailing List, Mauro Carvalho Chehab,
Linux Kernel Mailing List, Jonathan Corbet,
the arch/x86 maintainers, ACPI Devel Maling List,
open list:EDAC-CORE, netdev, devicetree@vger.kernel.org,
Linux PCI, Linux ARM, linux-amlogic, linux-arm-msm, linux-gpio,
linux-i2c, linuxppc-dev, xen-devel, Platform Driver, devel, kvm,
virtualization, open list:ACPI COMPONENT ARCHITECTURE (ACPICA),
Linux Memory Management List, linux-security-module,
linux-kselftest
In-Reply-To: <4fd1182b4a41feb2447c7ccde4d7f0a6b3c92686.1558362030.git.mchehab+samsung@kernel.org>
On Mon, May 20, 2019 at 4:48 PM Mauro Carvalho Chehab
<mchehab+samsung@kernel.org> wrote:
>
> Mostly due to x86 and acpi conversion, several documentation
> links are still pointing to the old file. Fix them.
>
> Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
For the ACPI part:
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
^ permalink raw reply
* RE: SGX vs LSM (Re: [PATCH v20 00/28] Intel SGX1 support)
From: Xing, Cedric @ 2019-05-26 6:09 UTC (permalink / raw)
To: Andy Lutomirski
Cc: Christopherson, Sean J, Stephen Smalley, Jarkko Sakkinen,
James Morris, Serge E. Hallyn, LSM List, Paul Moore, Eric Paris,
selinux@vger.kernel.org, Jethro Beekman, Hansen, Dave,
Thomas Gleixner, Dr. Greg, Linus Torvalds, LKML, X86 ML,
linux-sgx@vger.kernel.org, Andrew Morton, nhorman@redhat.com,
npmccallum@redhat.com, Ayoun, Serge, Katz-zamir, Shay,
Huang, Haitao, Andy Shevchenko, Svahn, Kai, Borislav Petkov,
Josh Triplett, Huang, Kai, David Rientjes
In-Reply-To: <CALCETrXXVMutX8eZk6nnkOAeS+Tj0sQd0FkW+wk6Rx8hQxCe6w@mail.gmail.com>
> From: Andy Lutomirski [mailto:luto@kernel.org]
> Sent: Saturday, May 25, 2019 5:58 PM
>
> On Sat, May 25, 2019 at 3:40 PM Xing, Cedric <cedric.xing@intel.com> wrote:
> >
> > > From: Andy Lutomirski [mailto:luto@amacapital.net]
> > > Sent: Friday, May 24, 2019 4:42 PM
> > >
> > > > On May 24, 2019, at 3:41 PM, Sean Christopherson <sean.j.christopherson@intel.com>
> wrote:
> > > >
> > > >> On Fri, May 24, 2019 at 02:27:34PM -0700, Andy Lutomirski wrote:
> > > >> On Fri, May 24, 2019 at 1:03 PM Sean Christopherson
> > > >> <sean.j.christopherson@intel.com> wrote:
> > > >>>
> > > >>>> On Fri, May 24, 2019 at 12:37:44PM -0700, Andy Lutomirski wrote:
> > > >>>>> On Fri, May 24, 2019 at 11:34 AM Xing, Cedric <cedric.xing@intel.com> wrote:
> > > >>>>>
> > > >>>>> If "initial permissions" for enclaves are less restrictive
> > > >>>>> than shared objects, then it'd become a backdoor for
> > > >>>>> circumventing LSM when enclave whitelisting is *not* in place.
> > > >>>>> For example, an adversary may load a page, which would
> > > >>>>> otherwise never be executable, as an executable
> > > page in EPC.
> > > >>>>>
> > > >>>>> In the case a RWX page is needed, the calling process has to
> > > >>>>> have a RWX page serving as the source for EADD so
> > > >>>>> PROCESS__EXECMEM will have been checked. For SGX2, changing an
> > > >>>>> EPC page to RWX is subject to FILE__EXECMEM on
> > > >>>>> /dev/sgx/enclave, which I see as a security benefit because it
> > > >>>>> only affects the enclave but not the whole process hosting
> > > it.
> > > >>>>
> > > >>>> So the permission would be like FILE__EXECMOD on the source
> > > >>>> enclave page, because it would be mapped MAP_ANONYMOUS, PROT_WRITE?
> > > >>>> MAP_SHARED, PROT_WRITE isn't going to work because that means
> > > >>>> you can modify the file.
> > > >>>
> > > >>> Was this in response to Cedric's comment, or to my comment?
> > > >>
> > > >> Yours. I think that requiring source pages to be actually mapped
> > > >> W is not such a great idea.
> > > >
> > > > I wasn't requiring source pages to be mapped W. At least I didn't
> > > > intend to require W. What I was trying to say is that SGX could
> > > > trigger an EXECMEM check if userspace attempted to EADD or EAUG an
> > > > enclave page with RWX permissions, e.g.:
> > > >
> > > > if ((SECINFO.PERMS & RWX) == RWX) {
> > > > ret = security_mmap_file(NULL, RWX, ???);
> > > > if (ret)
> > > > return ret;
> > > > }
> > > >
> > > > But that's a moot point if we add security_enclave_load() or whatever.
> > > >
> > > >>
> > > >>>
> > > >>>> I'm starting to think that looking at the source VMA permission
> > > >>>> bits or source PTE permission bits is putting a bit too much
> > > >>>> policy into the driver as opposed to the LSM. How about
> > > >>>> delegating the whole thing to an LSM hook? The EADD operation
> > > >>>> would invoke a new hook, something like:
> > > >>>>
> > > >>>> int security_enclave_load_bytes(void *source_addr, struct
> > > >>>> vm_area_struct *source_vma, loff_t source_offset, unsigned int
> > > >>>> maxperm);
> > > >>>>
> > > >>>> Then you don't have to muck with mapping anything PROT_EXEC.
> > > >>>> Instead you load from a mapping of a file and the LSM applies
> > > >>>> whatever policy it feels appropriate. If the first pass gets
> > > >>>> something wrong, the application or library authors can take it
> > > >>>> up with the SELinux folks without breaking the whole ABI :)
> > > >>>>
> > > >>>> (I'm proposing passing in the source_vma because this hook
> > > >>>> would be called with mmap_sem held for read to avoid a TOCTOU
> > > >>>> race.)
> > > >>>>
> > > >>>> If we go this route, the only substantial change to the
> > > >>>> existing driver that's needed for an initial upstream merge is
> > > >>>> the maxperm mechanism and whatever hopefully minimal API
> > > >>>> changes are needed to allow users to conveniently set up the
> > > >>>> mappings. And we don't need to worry about how to hack around
> > > >>>> mprotect() calling into the LSM, because the LSM will actually
> > > >>>> be aware of SGX and can just do the right thing.
> > > >>>
> > > >>> This doesn't address restricting which processes can run which
> > > >>> enclaves, it only allows restricting the build flow. Or are you
> > > >>> suggesting this be done in addition to whitelisting sigstructs?
> > > >>
> > > >> In addition.
> > > >>
> > > >> But I named the function badly and gave it a bad signature, which
> > > >> confused you. Let's try again:
> > > >>
> > > >> int security_enclave_load_from_memory(const struct vm_area_struct
> > > >> *source, unsigned int maxperm);
> > > >
> > > > I prefer security_enclave_load(), "from_memory" seems redundant at best.
> > >
> > > Fine with me.
> >
> > If we think of EADD as a way of mmap()'ing an enclave file into memory, would this
> security_enclave_load() be the same as security_mmap_file(source_vma->vm_file, maxperm,
> MAP_PRIVATE), except that the target is now EPC instead of regular pages?
>
> Hmm, that's clever. Although it seems plausible that an LSM would want to allow RX or RWX
> of a given file page but only in the context of an approved enclave, so I think it should
> still be its own hook.
What do you mean by "in the context of an approved enclave"? EPC pages are *inaccessible* to any software until after EINIT. So it would never be a security concern to EADD a page with wrong permissions as long as the enclave would be denied eventually by LSM at EINIT.
But I acknowledge the difference between loading a page into regular memory vs. into EPC. So it's beneficial to have a separate hook, which if not hooked, would pass through to security_mmap_file() by default?
>
> >
> > >
> > > >
> > > >> Maybe some really fancy future LSM would also want loff_t
> > > >> source_offset, but it's probably not terribly useful. This same
> > > >> callback would be used for EAUG.
> >
> > EAUG always zeroes the EPC page before making it available to an enclave. So I don't
> think there's anything needed to done here.
>
> Duh. So security_enclave_load_zeros() for EAUG. See below.
>
> >
> > > >>
> > > >> Following up on your discussion with Cedric about sigstruct, the
> > > >> other callback would be something like:
> > > >>
> > > >> int security_enclave_init(struct file *sigstruct_file);
> >
> > I'd still insist in using a pointer rather than a file, for reasons that we've discussed
> before. For those who can't recall, the major reason is that most implementation would
> embed SIGSTRUCT into the same file as the enclave (or at least I don't want to prevent
> anyone from doing so), which could also be part of another file, such as a shared object
> or even the main executable itself. It could be difficult to obtain a fd in those cases.
> memfd won't work because it can't retain the same attributes of the original file
> containing the SIGSTRUCT.
> >
> > After all, what matters is the attributes associated with the backing file, which could
> be easily retrieve from vm_file of the covering VMA. So for the sake of flexibility, let's
> stay with what we've agreed before - a pointer to SIGSTRUCT.
>
> I'm okay with this, except for one nastiness: there's a big difference between a file that
> is just a sigstruct and a file that contains essentially arbitrary data plus a sigstruct
> at an arbitrary offset.
> We could do something tricky like saying that SIGSTRUCT can be in a file that's just a
> SIGSTRUCT or it can be in a special SIGSTRUCT ELF note in a file that isn't just a
> SIGSTRUCT, but that could be annoyingly restrictive.
Agreed. Approving a file implies approving all SIGSTRUCTs within that file. But I guess it wouldn't cause practical problems.
>
> If it's going to be in an arbitrary file, then I think the signature needs to be more like:
>
> int security_enclave_init(struct vm_area_struct *sigstruct_vma, loff_t sigstruct_offset,
> const sgx_sigstruct *sigstruct);
>
> So that the LSM still has the opportunity to base its decision on the contents of the
> SIGSTRUCT. Actually, we need that change regardless.
Wouldn't the pair of { sigstruct_vma, sigstruct_offset } be the same as just a pointer, because the VMA could be looked up using the pointer and the offset would then be (pointer - vma->vm_start)?
>
> >
> > > >>
> > > >> The main issue I see is that we also want to control the
> > > >> enclave's ability to have RWX pages or to change a W page to X.
> > > >> We might also
> > > >> want:
> > > >>
> > > >> int security_enclave_load_zeros(unsigned int maxperm);
> > > >
> > > > What's the use case for this? @maxperm will always be at least RW
> > > > in this case, otherwise the page is useless to the enclave, and if
> > > > the enclave can write the page, the fact that it started as zeros
> > > > is irrelevant.
> > >
> > > This is how EAUG could ask if RWX is okay. If an enclave is
> > > internally doing dynamic loading, the it will need a heap page with
> > > maxperm = RWX. (If it’s well designed, it will make it RW and then
> > > RX, either by changing SECINFO or by asking the host to mprotect() it, but it still
> needs the overall RWX mask.).
> >
> > Any new page EAUG'ed will start in RW (as dictated by SGX ISA). EACCEPTCOPY will then
> change it to RX. RWX is never needed for all practical purposes. This in fact could be
> gated by mprotect() and the attributes associated with /dev/sgx/enclave. In the case of
> SELinux, FILE__EXECMOD is the right attribute and mprotect() will take care of all the
> rest. I don't see why the driver need a role here.
>
> I find the SDM's discussion of EAUG, EACCEPT, and EACCEPTCOPY to be extremely confusing.
> My copy of the SDM has EACCEPT's SECINFO argument as "Read access permitted by Non
> Enclave". Is that an error?
I'm confused by those descriptions too. Guess I cannot comment if that's an error or not.
Anyway, per our internal documents, for EAUG, SECINFO has to be set to PT_REG|RW. For EACCEPT, SGX ISA compares supplied SECINFO with EPCM attributes and returns an error if they don't match. EACCEPTCOPY only works on pending pages (i.e. SECINFO.P must be set), and sets EPCM access permissions to whatever supplied in SECINFO.
> And is EACCEPTCOPY just EACCEPT + memcpy or is there some other fundamental difference?
> 38.5.7 doesn't even mention EACCEPTCOPY.
2 differences: 1) EACCEPT only *compares* but EACCEPTCOPY *sets* EPCM permissions; and 2) EACCEPTCOPY does EACCEPT+memcpy atomically.
>
> Anyway, all my confusion aside, I was talking about the page table, not the EPCM. I think
> the enclave should need permission to write its own content into a page that will ever
> become X, and the enclave's untrusted host library would do this by adding the page with
> MAXPERM=RWX and then mapping/mprotecting it as PROT_WRITE and then (later or
> simultaneously) PROT_EXEC.
I was talking about the same thing. A code page in EPC will start in RW (both EPCM and PTE) and end in RX (both EPCM and PTE). EACCEPTCOPY takes care of EPCM, while mprotect() could take care of PTE as long as /dev/sgx/enclave has FILE__EXECMOD.
I understand your intention to enclave pages to segments with different MAXPERMs. My concern is though the host process may not always have a priori knowledge on which ranges to be used as code vs. data. After all, only the weakest link matters in security so I think what a host process cares would be whether the enclave loads code dynamically, or expands its data segments only, or neither. And for that reason, I think it more "user friendly" to keep just one MAXPERM - i.e. the most permissive one. Then we could associate that with /dev/sgx/enclave so as to relieve the driver from keeping track of too many things.
>
> Since SGX2 doesn't seem to have a way to add an initialized page to EPC after an enclave
> starts, I guess that it's impossible to have the enclave do something like dlopen()
> without MAXPERM=RWX. So be it.
That's true. The reason behind it is SGX doesn’t trust anything from outside. So non-predetermined contents must be measured (e.g. EADD+EEXTEND), but it's hard to measure (or attest to) dynamically added contents so we decided to allow predetermined contents (i.e. all zeros in the case of EAUG) only.
> Maybe someone will find this annoying someday and SGX3 will add EAUG-but-don't-zero and
> EACCEPT-with-existing-contents.
From security perspective, accepting a page that is measured/hashed to XYZ is equivalent to overwriting that page with content hashed to XYZ. EACCEPTCOPY actually does the latter. The annoying part is due to the mismatch between SGX ISA and the s/w model adopted by LSM, but that has nothing to do with security.
>
> >
> > >
> > > Also, do real SGX1 enclave formats have BSS? If so, then either we
> > > need an ioctl or load zeros or user code is going to load from
> > > /dev/zero or just from the heap, but the LSM is going to play better
> > > with an ioctl, I suspect :)
> >
> > Yes, it does. But an enclave would either measure BSS, in which case the initial bytes
> have to be zero or MRENCLAVE will change; or zero BSS explicitly in its initialization
> code.
> >
> > But from LSM's perspective it makes no difference than EADD'ing a page with non-zero
> content. And security_enclave_load(NULL, RW) would take care of it in exactly in the same
> way.
>
> Sure, I suppose the same hook with NULL parameters would be equivalent.
>
> >
> > >
> > > >
> > > >> An enclave that's going to modify its own code will need memory
> > > >> with maxperm = RWX or WX.
> >
> > With SGX2/EDMM, RWX is *never* needed for all practical purposes.
> >
> > In theory, in terms of security, no page shall be made executable while it is still
> being prepared. So W and X shall always be mutually exclusive, regardless it's in EPC or
> regular memory.
> >
> > RWX is only needed in SGX1, as a workaround for certain usages, because EPCM permissions
> can never change at runtime.
>
> As above, I think I disagree. MAXPERM is intended as an upper bound on the permissions
> that a page can ever have, at least until it's EREMOVEd and re-added. Since there's no
> EAUG-but-don't-zero, EAUG with MAXPERM.W=0 is basically useless because the page can never
> contain anything other than zeros, so a dynamically allocated page that is ever executed
> has to have MAXPERM=RWX or MAXPERM=WX. And that will need special permissions, which I
> think is consistent with your recent emails on how this could all map to SELinux
> permissions.
I'm totally with you. What I was trying to say was that only W or X would be needed at any given time. That said, MAXPERM=RWX but PROCESS__EXECMEM will not be needed, while FILE__EXECMOD will be needed only on /dev/sgx/enclave. So the inherent risk is contained.
>
> >
> > > >>
> > > >> But this is a bit awkward if the LSM's decision depends on the
> > > >> sigstruct. We could get fancy and require that the sigstruct be
> > > >> supplied before any EADD operations so that the maxperm decisions
> > > >> can depend on the sigstruct.
> > > >>
> > > >> Am I making more sense now?
> > > >
> > > > Yep. Requiring .sigstruct at ECREATE would be trivial. If we
> > > > wanted flexibility we could do:
> > > >
> > > > int security_enclave_load(struct file *file, struct vm_area_struct *vma,
> > > > unsigned long prot);
> > > >
> > > > And for ultimate flexibility we could pass both .sigstruct and the
> > > > file pointer for /dev/sgx/enclave, but that seems a bit ridiculous.
> > >
> > > I agree.
> >
> > Loosely speaking, an enclave (including initial contents of all of its pages and their
> permissions) and its MRENCLAVE are a 1-to-1 correspondence (given the collision resistant
> property of SHA-2). So only one is needed for a decision, and either one would lead to the
> same decision. So I don't see anything making any sense here.
> >
> > Theoretically speaking, if LSM can make a decision at EINIT by means of
> security_enclave_load(), then security_enclave_load() is never needed.
> >
> > In practice, I support keeping both because security_enclave_load() can only approve an
> enumerable set while security_enclave_load() can approve a non-enumerable set of enclaves.
> Moreover, in order to determine the validity of a MRENCLAVE (as in development of a policy
> or in creation of a white/black list), system admins will need the audit log produced by
> security_enclave_load().
>
> I'm confused. Things like MRSIGNER aren't known until the SIGSTRUCT shows up. Also,
> security_enclave_load() provides no protection against loading a mishmash of two different
> enclave files. I see
> security_enclave_init() as "verify this SIGSTRUCT against your policy on who may sign
> enclaves and/or grant EXECMOD depending on SIGSTRUCT"
> and security_enclave_load() as "implement your EXECMOD / EXECUTE / WRITE / whatever policy
> and possibly check enclave files for some label."
Sorry for the confusion. I was saying the same thing except that the decision of security_enclave_load() doesn't have to depend on SIGSTRUCT. Given your prototype of security_enclave_load(), I think we are on the same page. I made the above comment to object to the idea of "require that the sigstruct be supplied before any EADD operations so that the maxperm decisions can depend on the sigstruct".
>
> >
> > >
> > > >
> > > > Passing both would allow tying EXECMOD to /dev/sgx/enclave as
> > > > Cedric wanted (without having to play games and pass
> > > > /dev/sgx/enclave to security_enclave_load()), but I don't think
> > > > there's anything fundamentally broken with using .sigstruct for
> > > > EXECMOD. It requires more verbose labeling, but that's not a bad thing.
> > >
> > > The benefit of putting it on .sigstruct is that it can be per-enclave.
> > >
> > > As I understand it from Fedora packaging, the way this works on
> > > distros is generally that a package will include some files and
> > > their associated labels, and, if the package needs EXECMOD, then the
> > > files are labeled with EXECMOD and the author of the relevant code might get a dirty
> look.
> > >
> > > This could translate to the author of an exclave that needs RWX
> > > regions getting a dirty look without leaking this permission into other enclaves.
> > >
> > > (In my opinion, the dirty looks are actually the best security
> > > benefit of the entire concept of LSMs making RWX difficult. A
> > > sufficiently creative attacker can almost always bypass W^X
> > > restrictions once they’ve pwned you, but W^X makes it harder to pwn
> > > you in the first place, and SELinux makes it really obvious when
> > > packaging a program that doesn’t respect W^X. The upshot is that a
> > > lot of programs got fixed.)
> >
> > I'm lost here. Dynamically linked enclaves, if running on SGX2, would need RW->RX, i.e.
> FILE__EXECMOD on /dev/sgx/enclave. But they never need RWX, i.e. PROCESS__EXECMEM.
>
> Hmm. If we want to make this distinction, we need something a big richer than my proposed
> callbacks. A check of the actual mprotect() /
> mmap() permissions would also be needed. Specifically, allowing MAXPERM=RWX wouldn't
> imply that PROT_WRITE | PROT_EXEC is allowed.
If we keep only one MAXPERM, wouldn't this be the current behavior of mmap()/mprotect()?
To be a bit more clear, system admin sets MAXPERM upper bound in the form of FILE__{READ|WRITE|EXECUTE|EXECMOD} of /dev/sgx/enclave. Then for a process/enclave, if what it requires falls below what's allowed on /dev/sgx/enclave, then everything will just work. Otherwise, it fails in the form of -EPERM returned from mmap()/mprotect(). Please note that MAXPERM here applies to "runtime" permissions, while "initial" permissions are taken care of by security_enclave_{load|init}. "initial" permissions could be more permissive than "runtime" permissions, e.g., RX is still required for initial code pages even though system admins could disable dynamically loaded code pages by *not* giving FILE__{EXECUTE|EXECMOD}. Therefore, the "initial" mapping would still have to be done by the driver (to bypass LSM), either via a new ioctl or as part of IOC_EINIT.
^ 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