Hi all,
Here's v4 of the seccomp trap to userspace series. v3 is here:
https://lkml.org/lkml/2018/5/31/527
I believe we've addressed the two burning questions I had about v3: 1.
it seems ok not to use netlink, since there's not a great way to re-use
the API without a lot of unnecessary code and 2. only having return
capability for fds seems fine with people. Or at least I haven't heard
any strong objections.
I've re-worked a bunch of things in this version based on feedback from
the last series. See patch notes for details. At this point I'm not
aware of anything that needs to be addressed, but of course that is
subject to change :)
Tycho
Tycho Andersen (4):
seccomp: add a return code to trap to userspace
seccomp: make get_nth_filter available outside of CHECKPOINT_RESTORE
seccomp: add a way to get a listener fd from ptrace
seccomp: add support for passing fds via USER_NOTIF
.../userspace-api/seccomp_filter.rst | 79 +++
arch/Kconfig | 7 +
include/linux/seccomp.h | 18 +-
include/uapi/linux/ptrace.h | 2 +
include/uapi/linux/seccomp.h | 23 +-
kernel/ptrace.c | 4 +
kernel/seccomp.c | 491 ++++++++++++++-
tools/testing/selftests/seccomp/seccomp_bpf.c | 560 +++++++++++++++++-
8 files changed, 1172 insertions(+), 12 deletions(-)
--
2.17.1
In the next commit we'll use this same mnemonic to get a listener for the
nth filter, so we need it available outside of CHECKPOINT_RESTORE. This is
slightly looser than necessary, because it really could be
CHECKPOINT_RESTORE || USER_NOTIFICATION, but it's declared static and this
complicates the code less, so hopefully it's ok.
v2: new in v2
v3: no changes
Signed-off-by: Tycho Andersen <[email protected]>
CC: Kees Cook <[email protected]>
CC: Andy Lutomirski <[email protected]>
CC: Oleg Nesterov <[email protected]>
CC: Eric W. Biederman <[email protected]>
CC: "Serge E. Hallyn" <[email protected]>
CC: Christian Brauner <[email protected]>
CC: Tyler Hicks <[email protected]>
CC: Akihiro Suda <[email protected]>
---
kernel/seccomp.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 24949478a812..bbc24938c51d 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -1184,7 +1184,7 @@ long prctl_set_seccomp(unsigned long seccomp_mode, char __user *filter)
return do_seccomp(op, 0, uargs);
}
-#if defined(CONFIG_SECCOMP_FILTER) && defined(CONFIG_CHECKPOINT_RESTORE)
+#if defined(CONFIG_SECCOMP_FILTER)
static struct seccomp_filter *get_nth_filter(struct task_struct *task,
unsigned long filter_off)
{
@@ -1231,6 +1231,7 @@ static struct seccomp_filter *get_nth_filter(struct task_struct *task,
return filter;
}
+#if defined(CONFIG_CHECKPOINT_RESTORE)
long seccomp_get_filter(struct task_struct *task, unsigned long filter_off,
void __user *data)
{
@@ -1303,7 +1304,8 @@ long seccomp_get_metadata(struct task_struct *task,
__put_seccomp_filter(filter);
return ret;
}
-#endif
+#endif /* CONFIG_CHECKPOINT_RESTORE */
+#endif /* CONFIG_SECCOMP_FILTER */
#ifdef CONFIG_SYSCTL
--
2.17.1
As an alternative to SECCOMP_FILTER_FLAG_GET_LISTENER, perhaps a ptrace()
version which can acquire filters is useful. There are at least two reasons
this is preferable, even though it uses ptrace:
1. You can control tasks that aren't cooperating with you
2. You can control tasks whose filters block sendmsg() and socket(); if the
task installs a filter which blocks these calls, there's no way with
SECCOMP_FILTER_FLAG_GET_LISTENER to get the fd out to the privileged task.
v2: fix a bug where listener mode was not unset when an unused fd was not
available
v3: fix refcounting bug (Oleg)
v4: * change the listener's fd flags to be 0
* rename GET_LISTENER to NEW_LISTENER (Matthew)
Signed-off-by: Tycho Andersen <[email protected]>
CC: Kees Cook <[email protected]>
CC: Andy Lutomirski <[email protected]>
CC: Oleg Nesterov <[email protected]>
CC: Eric W. Biederman <[email protected]>
CC: "Serge E. Hallyn" <[email protected]>
CC: Christian Brauner <[email protected]>
CC: Tyler Hicks <[email protected]>
CC: Akihiro Suda <[email protected]>
---
include/linux/seccomp.h | 11 ++++
include/uapi/linux/ptrace.h | 2 +
kernel/ptrace.c | 4 ++
kernel/seccomp.c | 28 ++++++++
tools/testing/selftests/seccomp/seccomp_bpf.c | 66 +++++++++++++++++++
5 files changed, 111 insertions(+)
diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h
index 017444b5efed..c17c7d051af0 100644
--- a/include/linux/seccomp.h
+++ b/include/linux/seccomp.h
@@ -112,4 +112,15 @@ static inline long seccomp_get_metadata(struct task_struct *task,
return -EINVAL;
}
#endif /* CONFIG_SECCOMP_FILTER && CONFIG_CHECKPOINT_RESTORE */
+
+#ifdef CONFIG_SECCOMP_USER_NOTIFICATION
+extern long seccomp_new_listener(struct task_struct *task,
+ unsigned long filter_off);
+#else
+static inline long seccomp_new_listener(struct task_struct *task,
+ unsigned long filter_off)
+{
+ return -EINVAL;
+}
+#endif/* CONFIG_SECCOMP_USER_NOTIFICATION */
#endif /* _LINUX_SECCOMP_H */
diff --git a/include/uapi/linux/ptrace.h b/include/uapi/linux/ptrace.h
index d5a1b8a492b9..e80ecb1bd427 100644
--- a/include/uapi/linux/ptrace.h
+++ b/include/uapi/linux/ptrace.h
@@ -73,6 +73,8 @@ struct seccomp_metadata {
__u64 flags; /* Output: filter's flags */
};
+#define PTRACE_SECCOMP_NEW_LISTENER 0x420e
+
/* Read signals from a shared (process wide) queue */
#define PTRACE_PEEKSIGINFO_SHARED (1 << 0)
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index 21fec73d45d4..289960ac181b 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -1096,6 +1096,10 @@ int ptrace_request(struct task_struct *child, long request,
ret = seccomp_get_metadata(child, addr, datavp);
break;
+ case PTRACE_SECCOMP_NEW_LISTENER:
+ ret = seccomp_new_listener(child, addr);
+ break;
+
default:
break;
}
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index bbc24938c51d..b68a5d4a15cd 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -1743,6 +1743,34 @@ static struct file *init_listener(struct task_struct *task,
return ret;
}
+
+long seccomp_new_listener(struct task_struct *task,
+ unsigned long filter_off)
+{
+ struct seccomp_filter *filter;
+ struct file *listener;
+ int fd;
+
+ filter = get_nth_filter(task, filter_off);
+ if (IS_ERR(filter))
+ return PTR_ERR(filter);
+
+ fd = get_unused_fd_flags(0);
+ if (fd < 0) {
+ __put_seccomp_filter(filter);
+ return fd;
+ }
+
+ listener = init_listener(task, task->seccomp.filter);
+ __put_seccomp_filter(filter);
+ if (IS_ERR(listener)) {
+ put_unused_fd(fd);
+ return PTR_ERR(listener);
+ }
+
+ fd_install(fd, listener);
+ return fd;
+}
#else
static struct file *init_listener(struct task_struct *task,
struct seccomp_filter *filter)
diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c
index 328f90fe6ee2..dde64178593b 100644
--- a/tools/testing/selftests/seccomp/seccomp_bpf.c
+++ b/tools/testing/selftests/seccomp/seccomp_bpf.c
@@ -182,6 +182,10 @@ int seccomp(unsigned int op, unsigned int flags, void *args)
}
#endif
+#ifndef PTRACE_SECCOMP_NEW_LISTENER
+#define PTRACE_SECCOMP_NEW_LISTENER 0x420e
+#endif
+
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define syscall_arg(_n) (offsetof(struct seccomp_data, args[_n]))
#elif __BYTE_ORDER == __BIG_ENDIAN
@@ -3147,6 +3151,68 @@ TEST(get_user_notification_syscall)
EXPECT_EQ(0, WEXITSTATUS(status));
}
+TEST(get_user_notification_ptrace)
+{
+ pid_t pid;
+ int status, listener;
+ int sk_pair[2];
+ char c;
+ struct seccomp_notif req = {};
+ struct seccomp_notif_resp resp = {};
+
+ ASSERT_EQ(socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair), 0);
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ EXPECT_EQ(user_trap_syscall(__NR_getpid, 0), 0);
+
+ /* Test that we get ENOSYS while not attached */
+ EXPECT_EQ(syscall(__NR_getpid), -1);
+ EXPECT_EQ(errno, ENOSYS);
+
+ /* Signal we're ready and have installed the filter. */
+ EXPECT_EQ(write(sk_pair[1], "J", 1), 1);
+
+ EXPECT_EQ(read(sk_pair[1], &c, 1), 1);
+ EXPECT_EQ(c, 'H');
+
+ exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC);
+ }
+
+ EXPECT_EQ(read(sk_pair[0], &c, 1), 1);
+ EXPECT_EQ(c, 'J');
+
+ EXPECT_EQ(ptrace(PTRACE_ATTACH, pid), 0);
+ EXPECT_EQ(waitpid(pid, NULL, 0), pid);
+ listener = ptrace(PTRACE_SECCOMP_NEW_LISTENER, pid, 0);
+ EXPECT_GE(listener, 0);
+
+ /* EBUSY for second listener */
+ EXPECT_EQ(ptrace(PTRACE_SECCOMP_NEW_LISTENER, pid, 0), -1);
+ EXPECT_EQ(errno, EBUSY);
+
+ EXPECT_EQ(ptrace(PTRACE_DETACH, pid, NULL, 0), 0);
+
+ /* Now signal we are done and respond with magic */
+ EXPECT_EQ(write(sk_pair[0], "H", 1), 1);
+
+ EXPECT_EQ(read(listener, &req, sizeof(req)), sizeof(req));
+
+ resp.id = req.id;
+ resp.error = 0;
+ resp.val = USER_NOTIF_MAGIC;
+
+ EXPECT_EQ(write(listener, &resp, sizeof(resp)), sizeof(resp));
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ close(listener);
+}
+
/*
* Check that a pid in a child namespace still shows up as valid in ours.
*/
--
2.17.1
The idea here is that the userspace handler should be able to pass an fd
back to the trapped task, for example so it can be returned from socket().
I've proposed one API here, but I'm open to other options. In particular,
this only lets you return an fd from a syscall, which may not be enough in
all cases. For example, if an fd is written to an output parameter instead
of returned, the current API can't handle this. Another case is that
netlink takes as input fds sometimes (IFLA_NET_NS_FD, e.g.). If netlink
ever decides to install an fd and output it, we wouldn't be able to handle
this either.
Still, the vast majority of interesting cases are covered by this API, so
perhaps it is Enough.
I've left it as a separate commit for two reasons:
* It illustrates the way in which we would grow struct seccomp_notif and
struct seccomp_notif_resp without using netlink
* It shows just how little code is needed to accomplish this :)
v2: new in v2
v3: no changes
v4: * pass fd flags back from userspace as well (Jann)
* update same cgroup data on fd pass as SCM_RIGHTS (Alban)
* only set the REPLIED state /after/ successful fdget (Alban)
* reflect GET_LISTENER -> NEW_LISTENER changes
* add to the new Documentation/ on user notifications about fd replies
Signed-off-by: Tycho Andersen <[email protected]>
CC: Kees Cook <[email protected]>
CC: Andy Lutomirski <[email protected]>
CC: Oleg Nesterov <[email protected]>
CC: Eric W. Biederman <[email protected]>
CC: "Serge E. Hallyn" <[email protected]>
CC: Christian Brauner <[email protected]>
CC: Tyler Hicks <[email protected]>
CC: Akihiro Suda <[email protected]>
---
.../userspace-api/seccomp_filter.rst | 11 ++
include/uapi/linux/seccomp.h | 3 +
kernel/seccomp.c | 51 +++++++-
tools/testing/selftests/seccomp/seccomp_bpf.c | 114 ++++++++++++++++++
4 files changed, 177 insertions(+), 2 deletions(-)
diff --git a/Documentation/userspace-api/seccomp_filter.rst b/Documentation/userspace-api/seccomp_filter.rst
index e51422559dd6..3db93df254fb 100644
--- a/Documentation/userspace-api/seccomp_filter.rst
+++ b/Documentation/userspace-api/seccomp_filter.rst
@@ -232,6 +232,9 @@ The interface for a seccomp notification fd consists of two structures:
__u64 id;
__s32 error;
__s64 val;
+ __u8 return_fd;
+ __u32 fd;
+ __u32 fd_flags;
};
Users can ``read()`` or ``poll()`` on a seccomp notification fd to receive a
@@ -251,6 +254,14 @@ mentioned above in this document: all arguments being read from the tracee's
memory should be read into the tracer's memory before any policy decisions are
made. This allows for an atomic decision on syscall arguments.
+Userspace can also return file descriptors. For example, one may decide to
+intercept ``socket()`` syscalls, and return some file descriptor from those
+based on some policy. To return a file descriptor, the ``return_fd`` member
+should be non-zero, the ``fd`` argument should be the fd in the listener's
+table to send to the tracee (similar to how ``SCM_RIGHTS`` works), and
+``fd_flags`` should be the flags that the fd in the tracee's table is opened
+with (e.g. ``O_EXCL`` or similar).
+
Sysctls
=======
diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h
index 8836a3b25500..ed2a475e0fe6 100644
--- a/include/uapi/linux/seccomp.h
+++ b/include/uapi/linux/seccomp.h
@@ -72,6 +72,9 @@ struct seccomp_notif_resp {
__u64 id;
__s32 error;
__s64 val;
+ __u8 return_fd;
+ __u32 fd;
+ __u32 fd_flags;
};
#endif /* _UAPI_LINUX_SECCOMP_H */
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index b68a5d4a15cd..abd6e8c7e64e 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -43,6 +43,7 @@
#ifdef CONFIG_SECCOMP_USER_NOTIFICATION
#include <linux/anon_inodes.h>
+#include <net/cls_cgroup.h>
enum notify_state {
SECCOMP_NOTIFY_INIT,
@@ -77,6 +78,8 @@ struct seccomp_knotif {
/* The return values, only valid when in SECCOMP_NOTIFY_REPLIED */
int error;
long val;
+ struct file *file;
+ unsigned int flags;
/* Signals when this has entered SECCOMP_NOTIFY_REPLIED */
struct completion ready;
@@ -796,10 +799,44 @@ static void seccomp_do_user_notification(int this_syscall,
goto remove_list;
}
- ret = n.val;
- err = n.error;
+ if (n.file) {
+ int fd;
+ struct socket *sock;
+
+ fd = get_unused_fd_flags(n.flags);
+ if (fd < 0) {
+ err = fd;
+ ret = -1;
+ goto remove_list;
+ }
+
+ /*
+ * Similar to what SCM_RIGHTS does, let's re-set the cgroup
+ * data to point ot the tracee's cgroups instead of the
+ * listener's.
+ */
+ sock = sock_from_file(n.file, &err);
+ if (sock) {
+ sock_update_netprioidx(&sock->sk->sk_cgrp_data);
+ sock_update_classid(&sock->sk->sk_cgrp_data);
+ }
+
+ ret = fd;
+ err = 0;
+
+ fd_install(fd, n.file);
+ /* Don't fput, since fd has a reference now */
+ n.file = NULL;
+ } else {
+ ret = n.val;
+ err = n.error;
+ }
+
remove_list:
+ if (n.file)
+ fput(n.file);
+
list_del(&n.list);
out:
mutex_unlock(&match->notify_lock);
@@ -1669,10 +1706,20 @@ static ssize_t seccomp_notify_write(struct file *file, const char __user *buf,
goto out;
}
+ if (resp.return_fd) {
+ knotif->flags = resp.fd_flags;
+ knotif->file = fget(resp.fd);
+ if (!knotif->file) {
+ ret = -EBADF;
+ goto out;
+ }
+ }
+
ret = size;
knotif->state = SECCOMP_NOTIFY_REPLIED;
knotif->error = resp.error;
knotif->val = resp.val;
+
complete(&knotif->ready);
out:
mutex_unlock(&filter->notify_lock);
diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c
index dde64178593b..c80ccb21f3a1 100644
--- a/tools/testing/selftests/seccomp/seccomp_bpf.c
+++ b/tools/testing/selftests/seccomp/seccomp_bpf.c
@@ -171,6 +171,9 @@ struct seccomp_notif_resp {
__u64 id;
__s32 error;
__s64 val;
+ __u8 return_fd;
+ __u32 fd;
+ __u32 fd_flags;
};
#endif
@@ -3213,6 +3216,117 @@ TEST(get_user_notification_ptrace)
close(listener);
}
+TEST(user_notification_pass_fd)
+{
+ pid_t pid;
+ int status, listener;
+ int sk_pair[2];
+ char c;
+ struct seccomp_notif req = {};
+ struct seccomp_notif_resp resp = {};
+ long ret;
+
+ ASSERT_EQ(socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair), 0);
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ int fd;
+ char buf[16];
+
+ EXPECT_EQ(user_trap_syscall(__NR_getpid, 0), 0);
+
+ /* Signal we're ready and have installed the filter. */
+ EXPECT_EQ(write(sk_pair[1], "J", 1), 1);
+
+ EXPECT_EQ(read(sk_pair[1], &c, 1), 1);
+ EXPECT_EQ(c, 'H');
+ close(sk_pair[1]);
+
+ /* An fd from getpid(). Let the games begin. */
+ fd = syscall(__NR_getpid);
+ EXPECT_GT(fd, 0);
+ EXPECT_EQ(read(fd, buf, sizeof(buf)), 12);
+ close(fd);
+
+ exit(strcmp("hello world", buf));
+ }
+
+ EXPECT_EQ(read(sk_pair[0], &c, 1), 1);
+ EXPECT_EQ(c, 'J');
+
+ EXPECT_EQ(ptrace(PTRACE_ATTACH, pid), 0);
+ EXPECT_EQ(waitpid(pid, NULL, 0), pid);
+ listener = ptrace(PTRACE_SECCOMP_NEW_LISTENER, pid, 0);
+ EXPECT_GE(listener, 0);
+ EXPECT_EQ(ptrace(PTRACE_DETACH, pid, NULL, 0), 0);
+
+ /* Now signal we are done installing so it can do a getpid */
+ EXPECT_EQ(write(sk_pair[0], "H", 1), 1);
+ close(sk_pair[0]);
+
+ /* Make a new socket pair so we can send half across */
+ EXPECT_EQ(socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair), 0);
+
+ ret = read_notif(listener, &req);
+ EXPECT_EQ(ret, sizeof(req));
+ EXPECT_EQ(errno, 0);
+
+ resp.id = req.id;
+ resp.return_fd = 1;
+ resp.fd = sk_pair[1];
+ resp.fd_flags = 0;
+ EXPECT_EQ(write(listener, &resp, sizeof(resp)), sizeof(resp));
+ close(sk_pair[1]);
+
+ EXPECT_EQ(write(sk_pair[0], "hello world\0", 12), 12);
+ close(sk_pair[0]);
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+ close(listener);
+}
+
+TEST(user_notification_struct_size_mismatch)
+{
+ pid_t pid;
+ long ret;
+ int status, listener, len;
+ struct seccomp_notif req;
+ struct seccomp_notif_resp resp;
+
+ listener = user_trap_syscall(__NR_getpid,
+ SECCOMP_FILTER_FLAG_NEW_LISTENER);
+ EXPECT_GE(listener, 0);
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = syscall(__NR_getpid);
+ exit(ret != USER_NOTIF_MAGIC);
+ }
+
+ EXPECT_EQ(read(listener, &req, sizeof(req)), sizeof(req));
+
+ resp.id = req.id;
+ resp.error = 0;
+ resp.val = USER_NOTIF_MAGIC;
+
+ /*
+ * Only write a partial structure: this is what was available before we
+ * had fd support.
+ */
+ len = offsetof(struct seccomp_notif_resp, val) + sizeof(resp.val);
+ EXPECT_EQ(write(listener, &resp, len), len);
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
/*
* Check that a pid in a child namespace still shows up as valid in ours.
*/
--
2.17.1
This patch introduces a means for syscalls matched in seccomp to notify
some other task that a particular filter has been triggered.
The motivation for this is primarily for use with containers. For example,
if a container does an init_module(), we obviously don't want to load this
untrusted code, which may be compiled for the wrong version of the kernel
anyway. Instead, we could parse the module image, figure out which module
the container is trying to load and load it on the host.
As another example, containers cannot mknod(), since this checks
capable(CAP_SYS_ADMIN). However, harmless devices like /dev/null or
/dev/zero should be ok for containers to mknod, but we'd like to avoid hard
coding some whitelist in the kernel. Another example is mount(), which has
many security restrictions for good reason, but configuration or runtime
knowledge could potentially be used to relax these restrictions.
This patch adds functionality that is already possible via at least two
other means that I know about, both of which involve ptrace(): first, one
could ptrace attach, and then iterate through syscalls via PTRACE_SYSCALL.
Unfortunately this is slow, so a faster version would be to install a
filter that does SECCOMP_RET_TRACE, which triggers a PTRACE_EVENT_SECCOMP.
Since ptrace allows only one tracer, if the container runtime is that
tracer, users inside the container (or outside) trying to debug it will not
be able to use ptrace, which is annoying. It also means that older
distributions based on Upstart cannot boot inside containers using ptrace,
since upstart itself uses ptrace to start services.
The actual implementation of this is fairly small, although getting the
synchronization right was/is slightly complex.
Finally, it's worth noting that the classic seccomp TOCTOU of reading
memory data from the task still applies here, but can be avoided with
careful design of the userspace handler: if the userspace handler reads all
of the task memory that is necessary before applying its security policy,
the tracee's subsequent memory edits will not be read by the tracer.
v2: * make id a u64; the idea here being that it will never overflow,
because 64 is huge (one syscall every nanosecond => wrap every 584
years) (Andy)
* prevent nesting of user notifications: if someone is already attached
the tree in one place, nobody else can attach to the tree (Andy)
* notify the listener of signals the tracee receives as well (Andy)
* implement poll
v3: * lockdep fix (Oleg)
* drop unnecessary WARN()s (Christian)
* rearrange error returns to be more rpetty (Christian)
* fix build in !CONFIG_SECCOMP_USER_NOTIFICATION case
v4: * fix implementation of poll to use poll_wait() (Jann)
* change listener's fd flags to be 0 (Jann)
* hoist filter initialization out of ifdefs to its own function
init_user_notification()
* add some more testing around poll() and closing the listener while a
syscall is in action
* s/GET_LISTENER/NEW_LISTENER, since you can't _get_ a listener, but it
creates a new one (Matthew)
* correctly handle pid namespaces, add some testcases (Matthew)
* use EINPROGRESS instead of EINVAL when a notification response is
written twice (Matthew)
* fix comment typo from older version (SEND vs READ) (Matthew)
* whitespace and logic simplification (Tobin)
* add some Documentation/ bits on userspace trapping
Signed-off-by: Tycho Andersen <[email protected]>
CC: Kees Cook <[email protected]>
CC: Andy Lutomirski <[email protected]>
CC: Oleg Nesterov <[email protected]>
CC: Eric W. Biederman <[email protected]>
CC: "Serge E. Hallyn" <[email protected]>
CC: Christian Brauner <[email protected]>
CC: Tyler Hicks <[email protected]>
CC: Akihiro Suda <[email protected]>
---
.../userspace-api/seccomp_filter.rst | 68 +++
arch/Kconfig | 7 +
include/linux/seccomp.h | 7 +-
include/uapi/linux/seccomp.h | 20 +-
kernel/seccomp.c | 410 +++++++++++++++++-
tools/testing/selftests/seccomp/seccomp_bpf.c | 380 +++++++++++++++-
6 files changed, 882 insertions(+), 10 deletions(-)
diff --git a/Documentation/userspace-api/seccomp_filter.rst b/Documentation/userspace-api/seccomp_filter.rst
index 82a468bc7560..e51422559dd6 100644
--- a/Documentation/userspace-api/seccomp_filter.rst
+++ b/Documentation/userspace-api/seccomp_filter.rst
@@ -122,6 +122,11 @@ In precedence order, they are:
Results in the lower 16-bits of the return value being passed
to userland as the errno without executing the system call.
+``SECCOMP_RET_USER_NOTIF``:
+ Results in a ``struct seccomp_notif`` message sent on the userspace
+ notification fd, if it is attached, or ``-ENOSYS`` if it is not. See below
+ on discussion of how to handle user notifications.
+
``SECCOMP_RET_TRACE``:
When returned, this value will cause the kernel to attempt to
notify a ``ptrace()``-based tracer prior to executing the system
@@ -183,6 +188,69 @@ The ``samples/seccomp/`` directory contains both an x86-specific example
and a more generic example of a higher level macro interface for BPF
program generation.
+Userspace Notification
+======================
+
+The ``SECCOMP_RET_USER_NOTIF`` return code lets seccomp filters pass a
+particular syscall to userspace to be handled. This may be useful for
+applications like container managers, which whish to intercept particular
+syscalls (``mount()``, ``finit_module()``, etc.) and change their behavior.
+
+There are currently two APIs to acquire a userspace notification fd for a
+particular filter. The first is when the filter is installed, the task
+installing the filter can ask the ``seccomp()`` syscall:
+
+.. code-block::
+
+ fd = seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog);
+
+which (on success) will return a listener fd for the filter, which can them be
+passed around via ``SCM_RIGHTS`` or similar. Alternativley, a filter fd can be
+acquired via:
+
+.. code-block::
+
+ fd = ptrace(PTRACE_SECCOMP_NEW_LISTENER, pid, 0);
+
+which grabs the 0th filter for some task which the tracer has privilege over.
+Note that filter fds correspond to a particular filter, and not a particular
+task. So if this task then forks, notifications from both tasks will appear on
+the same filter fd. Reads and writes to/from a filter fd are also synchronized,
+so a filter fd can safely have many readers.
+
+The interface for a seccomp notification fd consists of two structures:
+
+.. code-block::
+
+ struct seccomp_notif {
+ __u64 id;
+ pid_t pid;
+ struct seccomp_data data;
+ };
+
+ struct seccomp_notif_resp {
+ __u64 id;
+ __s32 error;
+ __s64 val;
+ };
+
+Users can ``read()`` or ``poll()`` on a seccomp notification fd to receive a
+``struct seccomp_notif``, which contains three members: a globally unique
+``id``, the ``pid`` of the task which triggered this request (which may be 0 if
+the task is in a pid ns not visible from the listener's pid namespace), and the
+``data`` passed to seccomp. Userspace can then make a decision based on this
+information about what to do, and ``write()`` a response, indicating what
+should be returned to userspace. The ``id`` member of ``struct
+seccomp_notif_resp`` should be the same ``id`` as in ``struct seccomp_notif``.
+
+It is worth noting that ``struct seccomp_data`` contains the values of register
+arguments to the syscall, but does not contain pointers to memory. The task's
+memory is accessiable to suitably privileged traces via via ``ptrace()`` or
+``/proc/pid/map_files/``. However, care should be taken to avoid the TOCTOU
+mentioned above in this document: all arguments being read from the tracee's
+memory should be read into the tracer's memory before any policy decisions are
+made. This allows for an atomic decision on syscall arguments.
+
Sysctls
=======
diff --git a/arch/Kconfig b/arch/Kconfig
index 1aa59063f1fd..2f0c97bf4bc8 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -414,6 +414,13 @@ config PLUGIN_HOSTCC
Host compiler used to build GCC plugins. This can be $(HOSTCXX),
$(HOSTCC), or a null string if GCC plugin is unsupported.
+config SECCOMP_USER_NOTIFICATION
+ bool "Enable the SECCOMP_RET_USER_NOTIF seccomp action"
+ depends on SECCOMP_FILTER
+ help
+ Enable SECCOMP_RET_USER_NOTIF, a return code which can be used by seccomp
+ programs to notify a userspace listener that a particular event happened.
+
config HAVE_GCC_PLUGINS
bool
help
diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h
index e5320f6c8654..017444b5efed 100644
--- a/include/linux/seccomp.h
+++ b/include/linux/seccomp.h
@@ -4,9 +4,10 @@
#include <uapi/linux/seccomp.h>
-#define SECCOMP_FILTER_FLAG_MASK (SECCOMP_FILTER_FLAG_TSYNC | \
- SECCOMP_FILTER_FLAG_LOG | \
- SECCOMP_FILTER_FLAG_SPEC_ALLOW)
+#define SECCOMP_FILTER_FLAG_MASK (SECCOMP_FILTER_FLAG_TSYNC | \
+ SECCOMP_FILTER_FLAG_LOG | \
+ SECCOMP_FILTER_FLAG_SPEC_ALLOW | \
+ SECCOMP_FILTER_FLAG_NEW_LISTENER)
#ifdef CONFIG_SECCOMP
diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h
index 9efc0e73d50b..8836a3b25500 100644
--- a/include/uapi/linux/seccomp.h
+++ b/include/uapi/linux/seccomp.h
@@ -17,9 +17,10 @@
#define SECCOMP_GET_ACTION_AVAIL 2
/* Valid flags for SECCOMP_SET_MODE_FILTER */
-#define SECCOMP_FILTER_FLAG_TSYNC (1UL << 0)
-#define SECCOMP_FILTER_FLAG_LOG (1UL << 1)
-#define SECCOMP_FILTER_FLAG_SPEC_ALLOW (1UL << 2)
+#define SECCOMP_FILTER_FLAG_TSYNC (1UL << 0)
+#define SECCOMP_FILTER_FLAG_LOG (1UL << 1)
+#define SECCOMP_FILTER_FLAG_SPEC_ALLOW (1UL << 2)
+#define SECCOMP_FILTER_FLAG_NEW_LISTENER (1UL << 3)
/*
* All BPF programs must return a 32-bit value.
@@ -35,6 +36,7 @@
#define SECCOMP_RET_KILL SECCOMP_RET_KILL_THREAD
#define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */
#define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */
+#define SECCOMP_RET_USER_NOTIF 0x7fc00000U /* notifies userspace */
#define SECCOMP_RET_TRACE 0x7ff00000U /* pass to a tracer or disallow */
#define SECCOMP_RET_LOG 0x7ffc0000U /* allow after logging */
#define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */
@@ -60,4 +62,16 @@ struct seccomp_data {
__u64 args[6];
};
+struct seccomp_notif {
+ __u64 id;
+ pid_t pid;
+ struct seccomp_data data;
+};
+
+struct seccomp_notif_resp {
+ __u64 id;
+ __s32 error;
+ __s64 val;
+};
+
#endif /* _UAPI_LINUX_SECCOMP_H */
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index fd023ac24e10..24949478a812 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -33,6 +33,7 @@
#endif
#ifdef CONFIG_SECCOMP_FILTER
+#include <linux/file.h>
#include <linux/filter.h>
#include <linux/pid.h>
#include <linux/ptrace.h>
@@ -40,6 +41,50 @@
#include <linux/tracehook.h>
#include <linux/uaccess.h>
+#ifdef CONFIG_SECCOMP_USER_NOTIFICATION
+#include <linux/anon_inodes.h>
+
+enum notify_state {
+ SECCOMP_NOTIFY_INIT,
+ SECCOMP_NOTIFY_SENT,
+ SECCOMP_NOTIFY_REPLIED,
+};
+
+struct seccomp_knotif {
+ /* The struct pid of the task whose filter triggered the notification */
+ struct pid *pid;
+
+ /* The "cookie" for this request; this is unique for this filter. */
+ u32 id;
+
+ /*
+ * The seccomp data. This pointer is valid the entire time this
+ * notification is active, since it comes from __seccomp_filter which
+ * eclipses the entire lifecycle here.
+ */
+ const struct seccomp_data *data;
+
+ /*
+ * Notification states. When SECCOMP_RET_USER_NOTIF is returned, a
+ * struct seccomp_knotif is created and starts out in INIT. Once the
+ * handler reads the notification off of an FD, it transitions to SENT.
+ * If a signal is received the state transitions back to INIT and
+ * another message is sent. When the userspace handler replies, state
+ * transitions to REPLIED.
+ */
+ enum notify_state state;
+
+ /* The return values, only valid when in SECCOMP_NOTIFY_REPLIED */
+ int error;
+ long val;
+
+ /* Signals when this has entered SECCOMP_NOTIFY_REPLIED */
+ struct completion ready;
+
+ struct list_head list;
+};
+#endif
+
/**
* struct seccomp_filter - container for seccomp BPF programs
*
@@ -66,6 +111,30 @@ struct seccomp_filter {
bool log;
struct seccomp_filter *prev;
struct bpf_prog *prog;
+
+#ifdef CONFIG_SECCOMP_USER_NOTIFICATION
+ /*
+ * A semaphore that users of this notification can wait on for
+ * changes. Actual reads and writes are still controlled with
+ * filter->notify_lock.
+ */
+ struct semaphore request;
+
+ /* A lock for all notification-related accesses. */
+ struct mutex notify_lock;
+
+ /* Is there currently an attached listener? */
+ bool has_listener;
+
+ /* The id of the next request. */
+ u64 next_id;
+
+ /* A list of struct seccomp_knotif elements. */
+ struct list_head notifications;
+
+ /* A wait queue for poll. */
+ wait_queue_head_t wqh;
+#endif
};
/* Limit any path through the tree to 256KB worth of instructions. */
@@ -359,6 +428,19 @@ static inline void seccomp_sync_threads(unsigned long flags)
}
}
+#ifdef CONFIG_SECCOMP_USER_NOTIFICATION
+static void init_user_notification(struct seccomp_filter *sfilter)
+{
+ mutex_init(&sfilter->notify_lock);
+ sema_init(&sfilter->request, 0);
+ INIT_LIST_HEAD(&sfilter->notifications);
+ sfilter->next_id = get_random_u64();
+ init_waitqueue_head(&sfilter->wqh);
+}
+#else
+static inline void init_user_notification(struct seccomp_filter *sfilter) { }
+#endif
+
/**
* seccomp_prepare_filter: Prepares a seccomp filter for use.
* @fprog: BPF program to install
@@ -392,6 +474,8 @@ static struct seccomp_filter *seccomp_prepare_filter(struct sock_fprog *fprog)
if (!sfilter)
return ERR_PTR(-ENOMEM);
+ init_user_notification(sfilter);
+
ret = bpf_prog_create_from_user(&sfilter->prog, fprog,
seccomp_check_filter, save_orig);
if (ret < 0) {
@@ -556,13 +640,15 @@ static void seccomp_send_sigsys(int syscall, int reason)
#define SECCOMP_LOG_TRACE (1 << 4)
#define SECCOMP_LOG_LOG (1 << 5)
#define SECCOMP_LOG_ALLOW (1 << 6)
+#define SECCOMP_LOG_USER_NOTIF (1 << 7)
static u32 seccomp_actions_logged = SECCOMP_LOG_KILL_PROCESS |
SECCOMP_LOG_KILL_THREAD |
SECCOMP_LOG_TRAP |
SECCOMP_LOG_ERRNO |
SECCOMP_LOG_TRACE |
- SECCOMP_LOG_LOG;
+ SECCOMP_LOG_LOG |
+ SECCOMP_LOG_USER_NOTIF;
static inline void seccomp_log(unsigned long syscall, long signr, u32 action,
bool requested)
@@ -581,6 +667,9 @@ static inline void seccomp_log(unsigned long syscall, long signr, u32 action,
case SECCOMP_RET_TRACE:
log = requested && seccomp_actions_logged & SECCOMP_LOG_TRACE;
break;
+ case SECCOMP_RET_USER_NOTIF:
+ log = requested && seccomp_actions_logged & SECCOMP_LOG_USER_NOTIF;
+ break;
case SECCOMP_RET_LOG:
log = seccomp_actions_logged & SECCOMP_LOG_LOG;
break;
@@ -651,6 +740,82 @@ void secure_computing_strict(int this_syscall)
}
#else
+#ifdef CONFIG_SECCOMP_USER_NOTIFICATION
+static u64 seccomp_next_notify_id(struct seccomp_filter *filter)
+{
+ /* Note: overflow is ok here, the id just needs to be unique */
+ return filter->next_id++;
+}
+
+static void seccomp_do_user_notification(int this_syscall,
+ struct seccomp_filter *match,
+ const struct seccomp_data *sd)
+{
+ int err;
+ long ret = 0;
+ struct seccomp_knotif n = {};
+
+ mutex_lock(&match->notify_lock);
+ err = -ENOSYS;
+ if (!match->has_listener)
+ goto out;
+
+ n.pid = task_pid(current);
+ n.state = SECCOMP_NOTIFY_INIT;
+ n.data = sd;
+ n.id = seccomp_next_notify_id(match);
+ init_completion(&n.ready);
+
+ list_add(&n.list, &match->notifications);
+ wake_up_poll(&match->wqh, EPOLLIN | EPOLLRDNORM);
+
+ mutex_unlock(&match->notify_lock);
+ up(&match->request);
+
+ err = wait_for_completion_interruptible(&n.ready);
+ mutex_lock(&match->notify_lock);
+
+ /*
+ * Here it's possible we got a signal and then had to wait on the mutex
+ * while the reply was sent, so let's be sure there wasn't a response
+ * in the meantime.
+ */
+ if (err < 0 && n.state != SECCOMP_NOTIFY_REPLIED) {
+ /*
+ * We got a signal. Let's tell userspace about it (potentially
+ * again, if we had already notified them about the first one).
+ */
+ if (n.state == SECCOMP_NOTIFY_SENT) {
+ n.state = SECCOMP_NOTIFY_INIT;
+ up(&match->request);
+ }
+ mutex_unlock(&match->notify_lock);
+ err = wait_for_completion_killable(&n.ready);
+ mutex_lock(&match->notify_lock);
+ if (err < 0)
+ goto remove_list;
+ }
+
+ ret = n.val;
+ err = n.error;
+
+remove_list:
+ list_del(&n.list);
+out:
+ mutex_unlock(&match->notify_lock);
+ syscall_set_return_value(current, task_pt_regs(current),
+ err, ret);
+}
+#else
+static void seccomp_do_user_notification(int this_syscall,
+ struct seccomp_filter *match,
+ const struct seccomp_data *sd)
+{
+ seccomp_log(this_syscall, SIGSYS, SECCOMP_RET_USER_NOTIF, true);
+ do_exit(SIGSYS);
+}
+#endif
+
#ifdef CONFIG_SECCOMP_FILTER
static int __seccomp_filter(int this_syscall, const struct seccomp_data *sd,
const bool recheck_after_trace)
@@ -728,6 +893,9 @@ static int __seccomp_filter(int this_syscall, const struct seccomp_data *sd,
return 0;
+ case SECCOMP_RET_USER_NOTIF:
+ seccomp_do_user_notification(this_syscall, match, sd);
+ goto skip;
case SECCOMP_RET_LOG:
seccomp_log(this_syscall, 0, action, true);
return 0;
@@ -834,6 +1002,9 @@ static long seccomp_set_mode_strict(void)
}
#ifdef CONFIG_SECCOMP_FILTER
+static struct file *init_listener(struct task_struct *,
+ struct seccomp_filter *);
+
/**
* seccomp_set_mode_filter: internal function for setting seccomp filter
* @flags: flags to change filter behavior
@@ -853,6 +1024,8 @@ static long seccomp_set_mode_filter(unsigned int flags,
const unsigned long seccomp_mode = SECCOMP_MODE_FILTER;
struct seccomp_filter *prepared = NULL;
long ret = -EINVAL;
+ int listener = 0;
+ struct file *listener_f = NULL;
/* Validate flags. */
if (flags & ~SECCOMP_FILTER_FLAG_MASK)
@@ -863,13 +1036,28 @@ static long seccomp_set_mode_filter(unsigned int flags,
if (IS_ERR(prepared))
return PTR_ERR(prepared);
+ if (flags & SECCOMP_FILTER_FLAG_NEW_LISTENER) {
+ listener = get_unused_fd_flags(0);
+ if (listener < 0) {
+ ret = listener;
+ goto out_free;
+ }
+
+ listener_f = init_listener(current, prepared);
+ if (IS_ERR(listener_f)) {
+ put_unused_fd(listener);
+ ret = PTR_ERR(listener_f);
+ goto out_free;
+ }
+ }
+
/*
* Make sure we cannot change seccomp or nnp state via TSYNC
* while another thread is in the middle of calling exec.
*/
if (flags & SECCOMP_FILTER_FLAG_TSYNC &&
mutex_lock_killable(¤t->signal->cred_guard_mutex))
- goto out_free;
+ goto out_put_fd;
spin_lock_irq(¤t->sighand->siglock);
@@ -887,6 +1075,16 @@ static long seccomp_set_mode_filter(unsigned int flags,
spin_unlock_irq(¤t->sighand->siglock);
if (flags & SECCOMP_FILTER_FLAG_TSYNC)
mutex_unlock(¤t->signal->cred_guard_mutex);
+out_put_fd:
+ if (flags & SECCOMP_FILTER_FLAG_NEW_LISTENER) {
+ if (ret < 0) {
+ fput(listener_f);
+ put_unused_fd(listener);
+ } else {
+ fd_install(listener, listener_f);
+ ret = listener;
+ }
+ }
out_free:
seccomp_filter_free(prepared);
return ret;
@@ -915,6 +1113,9 @@ static long seccomp_get_action_avail(const char __user *uaction)
case SECCOMP_RET_LOG:
case SECCOMP_RET_ALLOW:
break;
+ case SECCOMP_RET_USER_NOTIF:
+ if (IS_ENABLED(CONFIG_SECCOMP_USER_NOTIFICATION))
+ break;
default:
return -EOPNOTSUPP;
}
@@ -1111,6 +1312,7 @@ long seccomp_get_metadata(struct task_struct *task,
#define SECCOMP_RET_KILL_THREAD_NAME "kill_thread"
#define SECCOMP_RET_TRAP_NAME "trap"
#define SECCOMP_RET_ERRNO_NAME "errno"
+#define SECCOMP_RET_USER_NOTIF_NAME "user_notif"
#define SECCOMP_RET_TRACE_NAME "trace"
#define SECCOMP_RET_LOG_NAME "log"
#define SECCOMP_RET_ALLOW_NAME "allow"
@@ -1120,6 +1322,7 @@ static const char seccomp_actions_avail[] =
SECCOMP_RET_KILL_THREAD_NAME " "
SECCOMP_RET_TRAP_NAME " "
SECCOMP_RET_ERRNO_NAME " "
+ SECCOMP_RET_USER_NOTIF_NAME " "
SECCOMP_RET_TRACE_NAME " "
SECCOMP_RET_LOG_NAME " "
SECCOMP_RET_ALLOW_NAME;
@@ -1137,6 +1340,7 @@ static const struct seccomp_log_name seccomp_log_names[] = {
{ SECCOMP_LOG_TRACE, SECCOMP_RET_TRACE_NAME },
{ SECCOMP_LOG_LOG, SECCOMP_RET_LOG_NAME },
{ SECCOMP_LOG_ALLOW, SECCOMP_RET_ALLOW_NAME },
+ { SECCOMP_LOG_USER_NOTIF, SECCOMP_RET_USER_NOTIF_NAME },
{ }
};
@@ -1342,3 +1546,205 @@ static int __init seccomp_sysctl_init(void)
device_initcall(seccomp_sysctl_init)
#endif /* CONFIG_SYSCTL */
+
+#ifdef CONFIG_SECCOMP_USER_NOTIFICATION
+static int seccomp_notify_release(struct inode *inode, struct file *file)
+{
+ struct seccomp_filter *filter = file->private_data;
+ struct seccomp_knotif *knotif;
+
+ mutex_lock(&filter->notify_lock);
+
+ /*
+ * If this file is being closed because e.g. the task who owned it
+ * died, let's wake everyone up who was waiting on us.
+ */
+ list_for_each_entry(knotif, &filter->notifications, list) {
+ if (knotif->state == SECCOMP_NOTIFY_REPLIED)
+ continue;
+
+ knotif->state = SECCOMP_NOTIFY_REPLIED;
+ knotif->error = -ENOSYS;
+ knotif->val = 0;
+
+ complete(&knotif->ready);
+ }
+
+ wake_up_all(&filter->wqh);
+ filter->has_listener = false;
+ mutex_unlock(&filter->notify_lock);
+ __put_seccomp_filter(filter);
+ return 0;
+}
+
+static ssize_t seccomp_notify_read(struct file *f, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ struct seccomp_filter *filter = f->private_data;
+ struct seccomp_knotif *knotif = NULL, *cur;
+ struct seccomp_notif unotif = {};
+ ssize_t ret;
+
+ /* No offset reads. */
+ if (*ppos != 0)
+ return -EINVAL;
+
+ ret = down_interruptible(&filter->request);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&filter->notify_lock);
+ list_for_each_entry(cur, &filter->notifications, list) {
+ if (cur->state == SECCOMP_NOTIFY_INIT) {
+ knotif = cur;
+ break;
+ }
+ }
+
+ /*
+ * If we didn't find a notification, it could be that the task was
+ * interrupted between the time we were woken and when we were able to
+ * acquire the rw lock. Should we retry here or just -ENOENT? -ENOENT
+ * for now.
+ */
+ if (!knotif) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ unotif.id = knotif->id;
+ unotif.pid = pid_vnr(knotif->pid);
+ unotif.data = *(knotif->data);
+
+ size = min_t(size_t, size, sizeof(unotif));
+ if (copy_to_user(buf, &unotif, size)) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ ret = sizeof(unotif);
+ knotif->state = SECCOMP_NOTIFY_SENT;
+ wake_up_poll(&filter->wqh, EPOLLOUT | EPOLLWRNORM);
+
+out:
+ mutex_unlock(&filter->notify_lock);
+ return ret;
+}
+
+static ssize_t seccomp_notify_write(struct file *file, const char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ struct seccomp_filter *filter = file->private_data;
+ struct seccomp_notif_resp resp = {};
+ struct seccomp_knotif *knotif = NULL;
+ ssize_t ret = -EINVAL;
+
+ /* No partial writes. */
+ if (*ppos != 0)
+ return -EINVAL;
+
+ size = min_t(size_t, size, sizeof(resp));
+ if (copy_from_user(&resp, buf, size))
+ return -EFAULT;
+
+ ret = mutex_lock_interruptible(&filter->notify_lock);
+ if (ret < 0)
+ return ret;
+
+ list_for_each_entry(knotif, &filter->notifications, list) {
+ if (knotif->id == resp.id)
+ break;
+ }
+
+ if (!knotif || knotif->id != resp.id) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Allow exactly one reply. */
+ if (knotif->state != SECCOMP_NOTIFY_SENT) {
+ ret = -EINPROGRESS;
+ goto out;
+ }
+
+ ret = size;
+ knotif->state = SECCOMP_NOTIFY_REPLIED;
+ knotif->error = resp.error;
+ knotif->val = resp.val;
+ complete(&knotif->ready);
+out:
+ mutex_unlock(&filter->notify_lock);
+ return ret;
+}
+
+static __poll_t seccomp_notify_poll(struct file *file,
+ struct poll_table_struct *poll_tab)
+{
+ struct seccomp_filter *filter = file->private_data;
+ __poll_t ret = 0;
+ struct seccomp_knotif *cur;
+
+ poll_wait(file, &filter->wqh, poll_tab);
+
+ ret = mutex_lock_interruptible(&filter->notify_lock);
+ if (ret < 0)
+ return ret;
+
+ list_for_each_entry(cur, &filter->notifications, list) {
+ if (cur->state == SECCOMP_NOTIFY_INIT)
+ ret |= EPOLLIN | EPOLLRDNORM;
+ if (cur->state == SECCOMP_NOTIFY_SENT)
+ ret |= EPOLLOUT | EPOLLWRNORM;
+ if (ret & EPOLLIN && ret & EPOLLOUT)
+ break;
+ }
+
+ mutex_unlock(&filter->notify_lock);
+
+ return ret;
+}
+
+static const struct file_operations seccomp_notify_ops = {
+ .read = seccomp_notify_read,
+ .write = seccomp_notify_write,
+ .poll = seccomp_notify_poll,
+ .release = seccomp_notify_release,
+};
+
+static struct file *init_listener(struct task_struct *task,
+ struct seccomp_filter *filter)
+{
+ struct file *ret = ERR_PTR(-EBUSY);
+ struct seccomp_filter *cur;
+ int filter_nesting = 0;
+
+ for (cur = task->seccomp.filter; cur; cur = cur->prev) {
+ mutex_lock_nested(&cur->notify_lock, filter_nesting);
+ filter_nesting++;
+ if (cur->has_listener)
+ goto out;
+ }
+
+ ret = anon_inode_getfile("seccomp notify", &seccomp_notify_ops,
+ filter, O_RDWR);
+ if (IS_ERR(ret))
+ goto out;
+
+
+ /* The file has a reference to it now */
+ __get_seccomp_filter(filter);
+ filter->has_listener = true;
+
+out:
+ for (cur = task->seccomp.filter; cur; cur = cur->prev)
+ mutex_unlock(&cur->notify_lock);
+
+ return ret;
+}
+#else
+static struct file *init_listener(struct task_struct *task,
+ struct seccomp_filter *filter)
+{
+ return ERR_PTR(-EINVAL);
+}
+#endif
diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c
index e1473234968d..328f90fe6ee2 100644
--- a/tools/testing/selftests/seccomp/seccomp_bpf.c
+++ b/tools/testing/selftests/seccomp/seccomp_bpf.c
@@ -5,6 +5,7 @@
* Test code for seccomp bpf.
*/
+#define _GNU_SOURCE
#include <sys/types.h>
/*
@@ -40,10 +41,11 @@
#include <sys/fcntl.h>
#include <sys/mman.h>
#include <sys/times.h>
+#include <sys/socket.h>
-#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
+#include <poll.h>
#include "../kselftest_harness.h"
@@ -154,6 +156,24 @@ struct seccomp_metadata {
};
#endif
+#ifndef SECCOMP_FILTER_FLAG_NEW_LISTENER
+#define SECCOMP_FILTER_FLAG_NEW_LISTENER (1UL << 3)
+
+#define SECCOMP_RET_USER_NOTIF 0x7fc00000U
+
+struct seccomp_notif {
+ __u64 id;
+ pid_t pid;
+ struct seccomp_data data;
+};
+
+struct seccomp_notif_resp {
+ __u64 id;
+ __s32 error;
+ __s64 val;
+};
+#endif
+
#ifndef seccomp
int seccomp(unsigned int op, unsigned int flags, void *args)
{
@@ -2077,7 +2097,8 @@ TEST(detect_seccomp_filter_flags)
{
unsigned int flags[] = { SECCOMP_FILTER_FLAG_TSYNC,
SECCOMP_FILTER_FLAG_LOG,
- SECCOMP_FILTER_FLAG_SPEC_ALLOW };
+ SECCOMP_FILTER_FLAG_SPEC_ALLOW,
+ SECCOMP_FILTER_FLAG_NEW_LISTENER };
unsigned int flag, all_flags;
int i;
long ret;
@@ -2933,6 +2954,361 @@ TEST(get_metadata)
ASSERT_EQ(0, kill(pid, SIGKILL));
}
+static int user_trap_syscall(int nr, unsigned int flags)
+{
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS,
+ offsetof(struct seccomp_data, nr)),
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, nr, 0, 1),
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_USER_NOTIF),
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
+ };
+
+ struct sock_fprog prog = {
+ .len = (unsigned short)ARRAY_SIZE(filter),
+ .filter = filter,
+ };
+
+ return seccomp(SECCOMP_SET_MODE_FILTER, flags, &prog);
+}
+
+static int read_notif(int listener, struct seccomp_notif *req)
+{
+ int ret;
+
+ do {
+ errno = 0;
+ ret = read(listener, req, sizeof(*req));
+ } while (ret == -1 && errno == ENOENT);
+ return ret;
+}
+
+static void signal_handler(int signal)
+{
+}
+
+#define USER_NOTIF_MAGIC 116983961184613L
+TEST(get_user_notification_syscall)
+{
+ pid_t pid;
+ long ret;
+ int status, listener;
+ struct seccomp_notif req = {};
+ struct seccomp_notif_resp resp = {};
+ struct pollfd pollfd;
+
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW),
+ };
+ struct sock_fprog prog = {
+ .len = (unsigned short)ARRAY_SIZE(filter),
+ .filter = filter,
+ };
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ /* Check that we get -ENOSYS with no listener attached */
+ if (pid == 0) {
+ if (user_trap_syscall(__NR_getpid, 0) < 0)
+ exit(1);
+ ret = syscall(__NR_getpid);
+ exit(ret >= 0 || errno != ENOSYS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* Add some no-op filters so that we (don't) trigger lockdep. */
+ EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
+ EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
+ EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
+ EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
+
+ /* Check that the basic notification machinery works */
+ listener = user_trap_syscall(__NR_getpid,
+ SECCOMP_FILTER_FLAG_NEW_LISTENER);
+ EXPECT_GE(listener, 0);
+
+ /* Installing a second listener in the chain should EBUSY */
+ EXPECT_EQ(user_trap_syscall(__NR_getpid,
+ SECCOMP_FILTER_FLAG_NEW_LISTENER),
+ -1);
+ EXPECT_EQ(errno, EBUSY);
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = syscall(__NR_getpid);
+ exit(ret != USER_NOTIF_MAGIC);
+ }
+
+ pollfd.fd = listener;
+ pollfd.events = POLLIN | POLLOUT;
+
+ EXPECT_GT(poll(&pollfd, 1, -1), 0);
+ EXPECT_EQ(pollfd.revents, POLLIN);
+
+ EXPECT_EQ(read(listener, &req, sizeof(req)), sizeof(req));
+
+ pollfd.fd = listener;
+ pollfd.events = POLLIN | POLLOUT;
+
+ EXPECT_GT(poll(&pollfd, 1, -1), 0);
+ EXPECT_EQ(pollfd.revents, POLLOUT);
+
+ EXPECT_EQ(req.data.nr, __NR_getpid);
+
+ resp.id = req.id;
+ resp.error = 0;
+ resp.val = USER_NOTIF_MAGIC;
+
+ EXPECT_EQ(write(listener, &resp, sizeof(resp)), sizeof(resp));
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /*
+ * Check that nothing bad happens when we kill the task in the middle
+ * of a syscall.
+ */
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = syscall(__NR_getpid);
+ exit(ret != USER_NOTIF_MAGIC);
+ }
+
+ ret = read(listener, &req, sizeof(req));
+ EXPECT_EQ(ret, sizeof(req));
+
+ EXPECT_EQ(kill(pid, SIGKILL), 0);
+ EXPECT_EQ(waitpid(pid, NULL, 0), pid);
+
+ resp.id = req.id;
+ ret = write(listener, &resp, sizeof(resp));
+ EXPECT_EQ(ret, -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ /*
+ * Check that we get another notification about a signal in the middle
+ * of a syscall.
+ */
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ if (signal(SIGUSR1, signal_handler) == SIG_ERR) {
+ perror("signal");
+ exit(1);
+ }
+ ret = syscall(__NR_getpid);
+ exit(ret != USER_NOTIF_MAGIC);
+ }
+
+ ret = read_notif(listener, &req);
+ EXPECT_EQ(ret, sizeof(req));
+ EXPECT_EQ(errno, 0);
+
+ EXPECT_EQ(kill(pid, SIGUSR1), 0);
+
+ ret = read_notif(listener, &req);
+ EXPECT_EQ(ret, sizeof(req));
+ EXPECT_EQ(errno, 0);
+
+ resp.id = req.id;
+ ret = write(listener, &resp, sizeof(resp));
+ EXPECT_EQ(ret, sizeof(resp));
+ EXPECT_EQ(errno, 0);
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /*
+ * Check that we get an ENOSYS when the listener is closed.
+ */
+ pid = fork();
+ ASSERT_GE(pid, 0);
+ if (pid == 0) {
+ close(listener);
+ ret = syscall(__NR_getpid);
+ exit(ret != -1 && errno != ENOSYS);
+ }
+
+ close(listener);
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+/*
+ * Check that a pid in a child namespace still shows up as valid in ours.
+ */
+TEST(user_notification_child_pid_ns)
+{
+ pid_t pid;
+ int status, listener;
+ int sk_pair[2];
+ char c;
+ struct seccomp_notif req = {};
+ struct seccomp_notif_resp resp = {};
+
+ ASSERT_EQ(socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair), 0);
+ ASSERT_EQ(unshare(CLONE_NEWPID), 0);
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ EXPECT_EQ(user_trap_syscall(__NR_getpid, 0), 0);
+
+ /* Signal we're ready and have installed the filter. */
+ EXPECT_EQ(write(sk_pair[1], "J", 1), 1);
+
+ EXPECT_EQ(read(sk_pair[1], &c, 1), 1);
+ EXPECT_EQ(c, 'H');
+
+ exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC);
+ }
+
+ EXPECT_EQ(read(sk_pair[0], &c, 1), 1);
+ EXPECT_EQ(c, 'J');
+
+ EXPECT_EQ(ptrace(PTRACE_ATTACH, pid), 0);
+ EXPECT_EQ(waitpid(pid, NULL, 0), pid);
+ listener = ptrace(PTRACE_SECCOMP_NEW_LISTENER, pid, 0);
+ EXPECT_GE(listener, 0);
+ EXPECT_EQ(ptrace(PTRACE_DETACH, pid, NULL, 0), 0);
+
+ /* Now signal we are done and respond with magic */
+ EXPECT_EQ(write(sk_pair[0], "H", 1), 1);
+
+ EXPECT_EQ(read(listener, &req, sizeof(req)), sizeof(req));
+ EXPECT_EQ(req.pid, pid);
+
+ resp.id = req.id;
+ resp.error = 0;
+ resp.val = USER_NOTIF_MAGIC;
+
+ EXPECT_EQ(write(listener, &resp, sizeof(resp)), sizeof(resp));
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+ close(listener);
+}
+
+/*
+ * Check that a pid in a sibling (i.e. unrelated) namespace shows up as 0, i.e.
+ * invalid.
+ */
+TEST(user_notification_sibling_pid_ns)
+{
+ pid_t pid, pid2;
+ int status, listener;
+ int sk_pair[2];
+ char c;
+ struct seccomp_notif req = {};
+ struct seccomp_notif_resp resp = {};
+
+ ASSERT_EQ(socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair), 0);
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ int child_pair[2];
+ ASSERT_EQ(unshare(CLONE_NEWPID), 0);
+
+ ASSERT_EQ(socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, child_pair), 0);
+
+ pid2 = fork();
+ ASSERT_GE(pid2, 0);
+
+ if (pid2 == 0) {
+ close(child_pair[0]);
+ EXPECT_EQ(user_trap_syscall(__NR_getpid, 0), 0);
+
+ /* Signal we're ready and have installed the filter. */
+ EXPECT_EQ(write(child_pair[1], "J", 1), 1);
+
+ EXPECT_EQ(read(child_pair[1], &c, 1), 1);
+ EXPECT_EQ(c, 'H');
+
+ exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC);
+ }
+
+ /* check that child has installed the filter */
+ EXPECT_EQ(read(child_pair[0], &c, 1), 1);
+ EXPECT_EQ(c, 'J');
+
+ /* tell parent who child is */
+ EXPECT_EQ(write(sk_pair[1], &pid2, sizeof(pid2)), sizeof(pid2));
+
+ /* parent has installed listener, tell child to call syscall */
+ EXPECT_EQ(read(sk_pair[1], &c, 1), 1);
+ EXPECT_EQ(c, 'H');
+ EXPECT_EQ(write(child_pair[0], "H", 1), 1);
+
+ EXPECT_EQ(waitpid(pid2, &status, 0), pid2);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+ exit(WEXITSTATUS(status));
+ }
+
+ EXPECT_EQ(read(sk_pair[0], &pid2, sizeof(pid2)), sizeof(pid2));
+
+ EXPECT_EQ(ptrace(PTRACE_ATTACH, pid2), 0);
+ EXPECT_EQ(waitpid(pid2, NULL, 0), pid2);
+ listener = ptrace(PTRACE_SECCOMP_NEW_LISTENER, pid2, 0);
+ EXPECT_GE(listener, 0);
+ EXPECT_EQ(errno, 0);
+ EXPECT_EQ(ptrace(PTRACE_DETACH, pid2, NULL, 0), 0);
+
+ /* Create the sibling ns, and sibling in it. */
+ EXPECT_EQ(unshare(CLONE_NEWPID), 0);
+ EXPECT_EQ(errno, 0);
+
+ pid2 = fork();
+ EXPECT_GE(pid2, 0);
+
+ if (pid2 == 0) {
+ ASSERT_EQ(read(listener, &req, sizeof(req)), sizeof(req));
+ /*
+ * The pid should be 0, i.e. the task is in some namespace that
+ * we can't "see".
+ */
+ ASSERT_EQ(req.pid, 0);
+
+ resp.id = req.id;
+ resp.error = 0;
+ resp.val = USER_NOTIF_MAGIC;
+
+ ASSERT_EQ(write(listener, &resp, sizeof(resp)), sizeof(resp));
+ exit(0);
+ }
+
+ close(listener);
+
+ /* Now signal we are done setting up sibling listener. */
+ EXPECT_EQ(write(sk_pair[0], "H", 1), 1);
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ EXPECT_EQ(waitpid(pid2, &status, 0), pid2);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+
/*
* TODO:
* - add microbenchmarks
--
2.17.1
On Fri, Jun 22, 2018 at 12:04 AM Tycho Andersen <[email protected]> wrote:
> As an alternative to SECCOMP_FILTER_FLAG_GET_LISTENER, perhaps a ptrace()
> version which can acquire filters is useful. There are at least two reasons
> this is preferable, even though it uses ptrace:
>
> 1. You can control tasks that aren't cooperating with you
> 2. You can control tasks whose filters block sendmsg() and socket(); if the
> task installs a filter which blocks these calls, there's no way with
> SECCOMP_FILTER_FLAG_GET_LISTENER to get the fd out to the privileged task.
[...]
> diff --git a/kernel/seccomp.c b/kernel/seccomp.c
> index bbc24938c51d..b68a5d4a15cd 100644
> --- a/kernel/seccomp.c
> +++ b/kernel/seccomp.c
> @@ -1743,6 +1743,34 @@ static struct file *init_listener(struct task_struct *task,
>
> return ret;
> }
> +
> +long seccomp_new_listener(struct task_struct *task,
> + unsigned long filter_off)
> +{
> + struct seccomp_filter *filter;
> + struct file *listener;
> + int fd;
> +
> + filter = get_nth_filter(task, filter_off);
> + if (IS_ERR(filter))
> + return PTR_ERR(filter);
> +
> + fd = get_unused_fd_flags(0);
> + if (fd < 0) {
> + __put_seccomp_filter(filter);
> + return fd;
> + }
> +
> + listener = init_listener(task, task->seccomp.filter);
> + __put_seccomp_filter(filter);
> + if (IS_ERR(listener)) {
> + put_unused_fd(fd);
> + return PTR_ERR(listener);
> + }
> +
> + fd_install(fd, listener);
> + return fd;
> +}
I think there's a security problem here. Imagine the following scenario:
1. task A (uid==0) sets up a seccomp filter that uses SECCOMP_RET_USER_NOTIF
2. task A forks off a child B
3. task B uses setuid(1) to drop its privileges
4. task B becomes dumpable again, either via prctl(PR_SET_DUMPABLE, 1)
or via execve()
5. task C (the attacker, uid==1) attaches to task B via ptrace
6. task C uses PTRACE_SECCOMP_NEW_LISTENER on task B
7. because the seccomp filter is shared by task A and task B, task C
is now able to influence syscall results for syscalls performed by
task A
Unless I'm missing something, you might have to add some extra
security check here: Either a check to ensure that no other task is
using the same seccomp filter, or (as a last resort) a check for
capable(CAP_SYS_ADMIN).
Hi Jann,
On Fri, Jun 22, 2018 at 12:48:09AM +0200, Jann Horn wrote:
> On Fri, Jun 22, 2018 at 12:04 AM Tycho Andersen <[email protected]> wrote:
> > As an alternative to SECCOMP_FILTER_FLAG_GET_LISTENER, perhaps a ptrace()
> > version which can acquire filters is useful. There are at least two reasons
> > this is preferable, even though it uses ptrace:
> >
> > 1. You can control tasks that aren't cooperating with you
> > 2. You can control tasks whose filters block sendmsg() and socket(); if the
> > task installs a filter which blocks these calls, there's no way with
> > SECCOMP_FILTER_FLAG_GET_LISTENER to get the fd out to the privileged task.
> [...]
> > diff --git a/kernel/seccomp.c b/kernel/seccomp.c
> > index bbc24938c51d..b68a5d4a15cd 100644
> > --- a/kernel/seccomp.c
> > +++ b/kernel/seccomp.c
> > @@ -1743,6 +1743,34 @@ static struct file *init_listener(struct task_struct *task,
> >
> > return ret;
> > }
> > +
> > +long seccomp_new_listener(struct task_struct *task,
> > + unsigned long filter_off)
> > +{
> > + struct seccomp_filter *filter;
> > + struct file *listener;
> > + int fd;
> > +
> > + filter = get_nth_filter(task, filter_off);
> > + if (IS_ERR(filter))
> > + return PTR_ERR(filter);
> > +
> > + fd = get_unused_fd_flags(0);
> > + if (fd < 0) {
> > + __put_seccomp_filter(filter);
> > + return fd;
> > + }
> > +
> > + listener = init_listener(task, task->seccomp.filter);
> > + __put_seccomp_filter(filter);
> > + if (IS_ERR(listener)) {
> > + put_unused_fd(fd);
> > + return PTR_ERR(listener);
> > + }
> > +
> > + fd_install(fd, listener);
> > + return fd;
> > +}
>
> I think there's a security problem here. Imagine the following scenario:
>
> 1. task A (uid==0) sets up a seccomp filter that uses SECCOMP_RET_USER_NOTIF
> 2. task A forks off a child B
> 3. task B uses setuid(1) to drop its privileges
> 4. task B becomes dumpable again, either via prctl(PR_SET_DUMPABLE, 1)
> or via execve()
> 5. task C (the attacker, uid==1) attaches to task B via ptrace
> 6. task C uses PTRACE_SECCOMP_NEW_LISTENER on task B
> 7. because the seccomp filter is shared by task A and task B, task C
> is now able to influence syscall results for syscalls performed by
> task A
>
> Unless I'm missing something, you might have to add some extra
> security check here: Either a check to ensure that no other task is
> using the same seccomp filter, or (as a last resort) a check for
> capable(CAP_SYS_ADMIN).
I guess my first thought is "don't do that". But I am also not opposed
to adding a check for capable(CAP_SYS_ADMIN) to prevent the footgun,
so I can do that for v5. I think checking whether other tasks are
using a filter would be hard without adding some additional counter
logic or something, and at least for the use cases I know of,
capable(CAP_SYS_ADMIN) is fine.
Tycho
On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
>
> This patch introduces a means for syscalls matched in seccomp to notify
> some other task that a particular filter has been triggered.
[...]
> +Userspace Notification
> +======================
> +
> +The ``SECCOMP_RET_USER_NOTIF`` return code lets seccomp filters pass a
> +particular syscall to userspace to be handled. This may be useful for
> +applications like container managers, which whish to intercept particular
typo: "wish"
[...]
> +passed around via ``SCM_RIGHTS`` or similar. Alternativley, a filter fd can be
typo: "Alternatively"
[...]
> +It is worth noting that ``struct seccomp_data`` contains the values of register
> +arguments to the syscall, but does not contain pointers to memory. The task's
> +memory is accessiable to suitably privileged traces via via ``ptrace()`` or
Typo: "accessible"
[...]
> +
> +static void seccomp_do_user_notification(int this_syscall,
> + struct seccomp_filter *match,
> + const struct seccomp_data *sd)
> +{
> + int err;
> + long ret = 0;
> + struct seccomp_knotif n = {};
> +
> + mutex_lock(&match->notify_lock);
> + err = -ENOSYS;
> + if (!match->has_listener)
> + goto out;
> +
> + n.pid = task_pid(current);
> + n.state = SECCOMP_NOTIFY_INIT;
> + n.data = sd;
> + n.id = seccomp_next_notify_id(match);
> + init_completion(&n.ready);
> +
> + list_add(&n.list, &match->notifications);
> + wake_up_poll(&match->wqh, EPOLLIN | EPOLLRDNORM);
> +
> + mutex_unlock(&match->notify_lock);
> + up(&match->request);
> +
> + err = wait_for_completion_interruptible(&n.ready);
> + mutex_lock(&match->notify_lock);
> +
> + /*
> + * Here it's possible we got a signal and then had to wait on the mutex
> + * while the reply was sent, so let's be sure there wasn't a response
> + * in the meantime.
> + */
> + if (err < 0 && n.state != SECCOMP_NOTIFY_REPLIED) {
> + /*
> + * We got a signal. Let's tell userspace about it (potentially
> + * again, if we had already notified them about the first one).
> + */
> + if (n.state == SECCOMP_NOTIFY_SENT) {
> + n.state = SECCOMP_NOTIFY_INIT;
> + up(&match->request);
> + }
> + mutex_unlock(&match->notify_lock);
> + err = wait_for_completion_killable(&n.ready);
Does this mean that when you get a signal that isn't SIGKILL,
wait_for_completion_interruptible() will bail out with -ERESTARTSYS,
but then you hang on this wait_for_completion_killable()? I don't
understand what's going on here. What's the point of using
wait_for_completion_interruptible() when you'll just hang on another
wait on the same "struct completion"?
> + mutex_lock(&match->notify_lock);
> + if (err < 0)
> + goto remove_list;
> + }
> +
> + ret = n.val;
> + err = n.error;
> +
> +remove_list:
> + list_del(&n.list);
> +out:
> + mutex_unlock(&match->notify_lock);
> + syscall_set_return_value(current, task_pt_regs(current),
> + err, ret);
> +}
On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
>
> The idea here is that the userspace handler should be able to pass an fd
> back to the trapped task, for example so it can be returned from socket().
[...]
> +Userspace can also return file descriptors. For example, one may decide to
> +intercept ``socket()`` syscalls, and return some file descriptor from those
> +based on some policy. To return a file descriptor, the ``return_fd`` member
> +should be non-zero, the ``fd`` argument should be the fd in the listener's
> +table to send to the tracee (similar to how ``SCM_RIGHTS`` works), and
> +``fd_flags`` should be the flags that the fd in the tracee's table is opened
> +with (e.g. ``O_EXCL`` or similar).
fd_flags only contains file descriptor flags (meaning only O_CLOEXEC).
O_EXCL is a file creation flag, so setting it here wouldn't make sense.
Setting file status flags like O_APPEND does make sense, but those are
stored in the `struct file` and don't need to be passed separately;
the caller can e.g. set them via fcntl(fd, F_SETFD, flags) or on
open().
(The fcntl.2 manpage explains these.)
On Fri, Jun 22, 2018 at 01:34:18AM +0200, Jann Horn wrote:
> On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
> >
> > The idea here is that the userspace handler should be able to pass an fd
> > back to the trapped task, for example so it can be returned from socket().
> [...]
> > +Userspace can also return file descriptors. For example, one may decide to
> > +intercept ``socket()`` syscalls, and return some file descriptor from those
> > +based on some policy. To return a file descriptor, the ``return_fd`` member
> > +should be non-zero, the ``fd`` argument should be the fd in the listener's
> > +table to send to the tracee (similar to how ``SCM_RIGHTS`` works), and
> > +``fd_flags`` should be the flags that the fd in the tracee's table is opened
> > +with (e.g. ``O_EXCL`` or similar).
>
> fd_flags only contains file descriptor flags (meaning only O_CLOEXEC).
> O_EXCL is a file creation flag, so setting it here wouldn't make sense.
> Setting file status flags like O_APPEND does make sense, but those are
> stored in the `struct file` and don't need to be passed separately;
> the caller can e.g. set them via fcntl(fd, F_SETFD, flags) or on
> open().
> (The fcntl.2 manpage explains these.)
Ugh, yes, O_CLOEXEC is what I meant. Thanks, I'll clarify.
Tycho
On Fri, Jun 22, 2018 at 01:21:47AM +0200, Jann Horn wrote:
> On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
> >
> > This patch introduces a means for syscalls matched in seccomp to notify
> > some other task that a particular filter has been triggered.
> [...]
> > +Userspace Notification
> > +======================
> > +
> > +The ``SECCOMP_RET_USER_NOTIF`` return code lets seccomp filters pass a
> > +particular syscall to userspace to be handled. This may be useful for
> > +applications like container managers, which whish to intercept particular
>
> typo: "wish"
>
> [...]
> > +passed around via ``SCM_RIGHTS`` or similar. Alternativley, a filter fd can be
>
> typo: "Alternatively"
>
> [...]
> > +It is worth noting that ``struct seccomp_data`` contains the values of register
> > +arguments to the syscall, but does not contain pointers to memory. The task's
> > +memory is accessiable to suitably privileged traces via via ``ptrace()`` or
>
> Typo: "accessible"
Thanks!
> [...]
> > +
> > +static void seccomp_do_user_notification(int this_syscall,
> > + struct seccomp_filter *match,
> > + const struct seccomp_data *sd)
> > +{
> > + int err;
> > + long ret = 0;
> > + struct seccomp_knotif n = {};
> > +
> > + mutex_lock(&match->notify_lock);
> > + err = -ENOSYS;
> > + if (!match->has_listener)
> > + goto out;
> > +
> > + n.pid = task_pid(current);
> > + n.state = SECCOMP_NOTIFY_INIT;
> > + n.data = sd;
> > + n.id = seccomp_next_notify_id(match);
> > + init_completion(&n.ready);
> > +
> > + list_add(&n.list, &match->notifications);
> > + wake_up_poll(&match->wqh, EPOLLIN | EPOLLRDNORM);
> > +
> > + mutex_unlock(&match->notify_lock);
> > + up(&match->request);
> > +
> > + err = wait_for_completion_interruptible(&n.ready);
> > + mutex_lock(&match->notify_lock);
> > +
> > + /*
> > + * Here it's possible we got a signal and then had to wait on the mutex
> > + * while the reply was sent, so let's be sure there wasn't a response
> > + * in the meantime.
> > + */
> > + if (err < 0 && n.state != SECCOMP_NOTIFY_REPLIED) {
> > + /*
> > + * We got a signal. Let's tell userspace about it (potentially
> > + * again, if we had already notified them about the first one).
> > + */
> > + if (n.state == SECCOMP_NOTIFY_SENT) {
> > + n.state = SECCOMP_NOTIFY_INIT;
> > + up(&match->request);
> > + }
> > + mutex_unlock(&match->notify_lock);
> > + err = wait_for_completion_killable(&n.ready);
>
> Does this mean that when you get a signal that isn't SIGKILL,
> wait_for_completion_interruptible() will bail out with -ERESTARTSYS,
> but then you hang on this wait_for_completion_killable()? I don't
> understand what's going on here. What's the point of using
> wait_for_completion_interruptible() when you'll just hang on another
> wait on the same "struct completion"?
This is the implementation of this suggestion by Andy:
https://lkml.org/lkml/2018/3/15/1122
The idea is to alert the listener that there was a signal exactly
once, in case it's in the middle of processing a request it could bail
out and do something else. So the killable wait is intended to ignore
other (non-fatal) signals after the first one and wait for whatever
the handler decides to do with the signal it received.
Tycho
On Fri, Jun 22, 2018 at 2:58 AM Tycho Andersen <[email protected]> wrote:
>
> On Fri, Jun 22, 2018 at 01:21:47AM +0200, Jann Horn wrote:
> > On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
> > >
> > > This patch introduces a means for syscalls matched in seccomp to notify
> > > some other task that a particular filter has been triggered.
> > [...]
> > > +Userspace Notification
> > > +======================
> > > +
> > > +The ``SECCOMP_RET_USER_NOTIF`` return code lets seccomp filters pass a
> > > +particular syscall to userspace to be handled. This may be useful for
> > > +applications like container managers, which whish to intercept particular
> >
> > typo: "wish"
> >
> > [...]
> > > +passed around via ``SCM_RIGHTS`` or similar. Alternativley, a filter fd can be
> >
> > typo: "Alternatively"
> >
> > [...]
> > > +It is worth noting that ``struct seccomp_data`` contains the values of register
> > > +arguments to the syscall, but does not contain pointers to memory. The task's
> > > +memory is accessiable to suitably privileged traces via via ``ptrace()`` or
> >
> > Typo: "accessible"
>
> Thanks!
>
> > [...]
> > > +
> > > +static void seccomp_do_user_notification(int this_syscall,
> > > + struct seccomp_filter *match,
> > > + const struct seccomp_data *sd)
> > > +{
> > > + int err;
> > > + long ret = 0;
> > > + struct seccomp_knotif n = {};
> > > +
> > > + mutex_lock(&match->notify_lock);
> > > + err = -ENOSYS;
> > > + if (!match->has_listener)
> > > + goto out;
> > > +
> > > + n.pid = task_pid(current);
> > > + n.state = SECCOMP_NOTIFY_INIT;
> > > + n.data = sd;
> > > + n.id = seccomp_next_notify_id(match);
> > > + init_completion(&n.ready);
> > > +
> > > + list_add(&n.list, &match->notifications);
> > > + wake_up_poll(&match->wqh, EPOLLIN | EPOLLRDNORM);
> > > +
> > > + mutex_unlock(&match->notify_lock);
> > > + up(&match->request);
> > > +
> > > + err = wait_for_completion_interruptible(&n.ready);
> > > + mutex_lock(&match->notify_lock);
> > > +
> > > + /*
> > > + * Here it's possible we got a signal and then had to wait on the mutex
> > > + * while the reply was sent, so let's be sure there wasn't a response
> > > + * in the meantime.
> > > + */
> > > + if (err < 0 && n.state != SECCOMP_NOTIFY_REPLIED) {
> > > + /*
> > > + * We got a signal. Let's tell userspace about it (potentially
> > > + * again, if we had already notified them about the first one).
> > > + */
> > > + if (n.state == SECCOMP_NOTIFY_SENT) {
> > > + n.state = SECCOMP_NOTIFY_INIT;
> > > + up(&match->request);
> > > + }
> > > + mutex_unlock(&match->notify_lock);
> > > + err = wait_for_completion_killable(&n.ready);
> >
> > Does this mean that when you get a signal that isn't SIGKILL,
> > wait_for_completion_interruptible() will bail out with -ERESTARTSYS,
> > but then you hang on this wait_for_completion_killable()? I don't
> > understand what's going on here. What's the point of using
> > wait_for_completion_interruptible() when you'll just hang on another
> > wait on the same "struct completion"?
>
> This is the implementation of this suggestion by Andy:
> https://lkml.org/lkml/2018/3/15/1122
>
> The idea is to alert the listener that there was a signal exactly
> once, in case it's in the middle of processing a request it could bail
> out and do something else. So the killable wait is intended to ignore
> other (non-fatal) signals after the first one and wait for whatever
> the handler decides to do with the signal it received.
How can the listener tell that a signal arrived? When the first
non-fatal signal comes in, you just set the state to
SECCOMP_NOTIFY_INIT if it was SECCOMP_NOTIFY_SENT, right? So the
listener will potentially see the request twice, but with no
additional indicator that a signal arrived? And in particular, if the
listener doesn't read the request before the signal arrives, it will
only see the request once, just as if it was a normal request with no
signals involved?
Would it perhaps make sense to add a field to struct seccomp_notif
that indicates whether the notification is for a normal syscall or a
canceled syscall?
On Fri, Jun 22, 2018 at 03:28:24AM +0200, Jann Horn wrote:
> On Fri, Jun 22, 2018 at 2:58 AM Tycho Andersen <[email protected]> wrote:
> >
> > On Fri, Jun 22, 2018 at 01:21:47AM +0200, Jann Horn wrote:
> > > On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
> > > [...]
> > > > +
> > > > +static void seccomp_do_user_notification(int this_syscall,
> > > > + struct seccomp_filter *match,
> > > > + const struct seccomp_data *sd)
> > > > +{
> > > > + int err;
> > > > + long ret = 0;
> > > > + struct seccomp_knotif n = {};
> > > > +
> > > > + mutex_lock(&match->notify_lock);
> > > > + err = -ENOSYS;
> > > > + if (!match->has_listener)
> > > > + goto out;
> > > > +
> > > > + n.pid = task_pid(current);
> > > > + n.state = SECCOMP_NOTIFY_INIT;
> > > > + n.data = sd;
> > > > + n.id = seccomp_next_notify_id(match);
> > > > + init_completion(&n.ready);
> > > > +
> > > > + list_add(&n.list, &match->notifications);
> > > > + wake_up_poll(&match->wqh, EPOLLIN | EPOLLRDNORM);
> > > > +
> > > > + mutex_unlock(&match->notify_lock);
> > > > + up(&match->request);
> > > > +
> > > > + err = wait_for_completion_interruptible(&n.ready);
> > > > + mutex_lock(&match->notify_lock);
> > > > +
> > > > + /*
> > > > + * Here it's possible we got a signal and then had to wait on the mutex
> > > > + * while the reply was sent, so let's be sure there wasn't a response
> > > > + * in the meantime.
> > > > + */
> > > > + if (err < 0 && n.state != SECCOMP_NOTIFY_REPLIED) {
> > > > + /*
> > > > + * We got a signal. Let's tell userspace about it (potentially
> > > > + * again, if we had already notified them about the first one).
> > > > + */
> > > > + if (n.state == SECCOMP_NOTIFY_SENT) {
> > > > + n.state = SECCOMP_NOTIFY_INIT;
> > > > + up(&match->request);
> > > > + }
> > > > + mutex_unlock(&match->notify_lock);
> > > > + err = wait_for_completion_killable(&n.ready);
> > >
> > > Does this mean that when you get a signal that isn't SIGKILL,
> > > wait_for_completion_interruptible() will bail out with -ERESTARTSYS,
> > > but then you hang on this wait_for_completion_killable()? I don't
> > > understand what's going on here. What's the point of using
> > > wait_for_completion_interruptible() when you'll just hang on another
> > > wait on the same "struct completion"?
> >
> > This is the implementation of this suggestion by Andy:
> > https://lkml.org/lkml/2018/3/15/1122
> >
> > The idea is to alert the listener that there was a signal exactly
> > once, in case it's in the middle of processing a request it could bail
> > out and do something else. So the killable wait is intended to ignore
> > other (non-fatal) signals after the first one and wait for whatever
> > the handler decides to do with the signal it received.
>
> How can the listener tell that a signal arrived? When the first
> non-fatal signal comes in, you just set the state to
> SECCOMP_NOTIFY_INIT if it was SECCOMP_NOTIFY_SENT, right? So the
> listener will potentially see the request twice, but with no
> additional indicator that a signal arrived? And in particular, if the
> listener doesn't read the request before the signal arrives, it will
> only see the request once, just as if it was a normal request with no
> signals involved?
I was thinking just parsing /proc/pid/status (given that people are
already going to be mapping things in /proc/pid/map_files to read
arguments and stuff, I didn't think too much of it),
> Would it perhaps make sense to add a field to struct seccomp_notif
> that indicates whether the notification is for a normal syscall or a
> canceled syscall?
Sure, I'll add a __u32 signal and set it to the value of the signal if
we got one.
Thanks!
Tycho
On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
> This patch introduces a means for syscalls matched in seccomp to notify
> some other task that a particular filter has been triggered.
>
> The motivation for this is primarily for use with containers. For example,
> if a container does an init_module(), we obviously don't want to load this
> untrusted code, which may be compiled for the wrong version of the kernel
> anyway. Instead, we could parse the module image, figure out which module
> the container is trying to load and load it on the host.
>
> As another example, containers cannot mknod(), since this checks
> capable(CAP_SYS_ADMIN). However, harmless devices like /dev/null or
> /dev/zero should be ok for containers to mknod, but we'd like to avoid hard
> coding some whitelist in the kernel. Another example is mount(), which has
> many security restrictions for good reason, but configuration or runtime
> knowledge could potentially be used to relax these restrictions.
>
> This patch adds functionality that is already possible via at least two
> other means that I know about, both of which involve ptrace(): first, one
> could ptrace attach, and then iterate through syscalls via PTRACE_SYSCALL.
> Unfortunately this is slow, so a faster version would be to install a
> filter that does SECCOMP_RET_TRACE, which triggers a PTRACE_EVENT_SECCOMP.
> Since ptrace allows only one tracer, if the container runtime is that
> tracer, users inside the container (or outside) trying to debug it will not
> be able to use ptrace, which is annoying. It also means that older
> distributions based on Upstart cannot boot inside containers using ptrace,
> since upstart itself uses ptrace to start services.
>
> The actual implementation of this is fairly small, although getting the
> synchronization right was/is slightly complex.
>
> Finally, it's worth noting that the classic seccomp TOCTOU of reading
> memory data from the task still applies here, but can be avoided with
> careful design of the userspace handler: if the userspace handler reads all
> of the task memory that is necessary before applying its security policy,
> the tracee's subsequent memory edits will not be read by the tracer.
I've been thinking about how one would actually write userspace code
that uses this API, and whether PID reuse is an issue here. As far as
I can tell, the following situation can happen:
- seccomped process tries to perform a syscall that gets trapped
- notification is sent to the supervisor
- supervisor reads the notification
- seccomped process gets SIGKILLed
- new process appears with the PID that the seccomped process had
- supervisor tries to access memory of the seccomped process via
process_vm_{read,write}v or /proc/$pid/mem
- supervisor unintentionally accesses memory of the new process instead
This could have particularly nasty consequences if the supervisor has
to write to memory of the seccomped process for some reason.
It might make sense to explicitly document how the API has to be used
to avoid such a scenario from occuring. AFAICS,
process_vm_{read,write}v are fundamentally unsafe for this;
/proc/$pid/mem might be safe if you do the following dance in the
supervisor to validate that you have a reference to the right struct
mm before starting to actually access memory:
- supervisor reads a syscall notification for the seccomped process with PID $A
- supervisor opens /proc/$A/mem [taking a reference on the mm of the
process that currently has PID $A]
- supervisor reads all pending events from the notification FD; if
one of them says that PID $A was signalled, send back -ERESTARTSYS (or
-ERESTARTNOINTR?) and bail out
- [at this point, the open FD to /proc/$A/mem is known to actually
refer to the mm struct of the seccomped process]
- read and write on the open FD to /proc/$A/mem as necessary
- send back the syscall result
It might be nice if the kernel was able to directly give the
supervisor an FD to /proc/$A/mem that is guaranteed to point to the
right struct mm, but trying to implement that would probably make this
patch set significantly larger?
Hi Jann,
On Fri, Jun 22, 2018 at 04:40:20PM +0200, Jann Horn wrote:
> On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
> > This patch introduces a means for syscalls matched in seccomp to notify
> > some other task that a particular filter has been triggered.
> >
> > The motivation for this is primarily for use with containers. For example,
> > if a container does an init_module(), we obviously don't want to load this
> > untrusted code, which may be compiled for the wrong version of the kernel
> > anyway. Instead, we could parse the module image, figure out which module
> > the container is trying to load and load it on the host.
> >
> > As another example, containers cannot mknod(), since this checks
> > capable(CAP_SYS_ADMIN). However, harmless devices like /dev/null or
> > /dev/zero should be ok for containers to mknod, but we'd like to avoid hard
> > coding some whitelist in the kernel. Another example is mount(), which has
> > many security restrictions for good reason, but configuration or runtime
> > knowledge could potentially be used to relax these restrictions.
> >
> > This patch adds functionality that is already possible via at least two
> > other means that I know about, both of which involve ptrace(): first, one
> > could ptrace attach, and then iterate through syscalls via PTRACE_SYSCALL.
> > Unfortunately this is slow, so a faster version would be to install a
> > filter that does SECCOMP_RET_TRACE, which triggers a PTRACE_EVENT_SECCOMP.
> > Since ptrace allows only one tracer, if the container runtime is that
> > tracer, users inside the container (or outside) trying to debug it will not
> > be able to use ptrace, which is annoying. It also means that older
> > distributions based on Upstart cannot boot inside containers using ptrace,
> > since upstart itself uses ptrace to start services.
> >
> > The actual implementation of this is fairly small, although getting the
> > synchronization right was/is slightly complex.
> >
> > Finally, it's worth noting that the classic seccomp TOCTOU of reading
> > memory data from the task still applies here, but can be avoided with
> > careful design of the userspace handler: if the userspace handler reads all
> > of the task memory that is necessary before applying its security policy,
> > the tracee's subsequent memory edits will not be read by the tracer.
>
> I've been thinking about how one would actually write userspace code
> that uses this API, and whether PID reuse is an issue here. As far as
> I can tell, the following situation can happen:
>
> - seccomped process tries to perform a syscall that gets trapped
> - notification is sent to the supervisor
> - supervisor reads the notification
> - seccomped process gets SIGKILLed
> - new process appears with the PID that the seccomped process had
> - supervisor tries to access memory of the seccomped process via
> process_vm_{read,write}v or /proc/$pid/mem
> - supervisor unintentionally accesses memory of the new process instead
>
> This could have particularly nasty consequences if the supervisor has
> to write to memory of the seccomped process for some reason.
> It might make sense to explicitly document how the API has to be used
> to avoid such a scenario from occuring. AFAICS,
> process_vm_{read,write}v are fundamentally unsafe for this;
> /proc/$pid/mem might be safe if you do the following dance in the
> supervisor to validate that you have a reference to the right struct
> mm before starting to actually access memory:
>
> - supervisor reads a syscall notification for the seccomped process with PID $A
> - supervisor opens /proc/$A/mem [taking a reference on the mm of the
> process that currently has PID $A]
> - supervisor reads all pending events from the notification FD; if
> one of them says that PID $A was signalled, send back -ERESTARTSYS (or
> -ERESTARTNOINTR?) and bail out
> - [at this point, the open FD to /proc/$A/mem is known to actually
> refer to the mm struct of the seccomped process]
> - read and write on the open FD to /proc/$A/mem as necessary
> - send back the syscall result
Yes, this is a nasty problem :(. We have the id in the
request/response structs to avoid this race, so perhaps we can re-use
that? So it would look like:
- supervisor gets syscall notification for $A
- supervisor opens /proc/$A/mem or /proc/$A/map_files/... or a dir fd
to the container's root or whatever
- supervisor calls seccomp(SECCOMP_NOTIFICATION_IS_VALID, req->id, listener_fd)
- supervisor knows that the fds it has open are safe
That way it doesn't have to flush the whole queue? Of course this
makes things a lot slower, but it does enable safety for more than
just memory accesses, and also isn't required for things which
wouldn't read memory.
> It might be nice if the kernel was able to directly give the
> supervisor an FD to /proc/$A/mem that is guaranteed to point to the
> right struct mm, but trying to implement that would probably make this
> patch set significantly larger?
I'll take a look and see how big it is, it doesn't *seem* like it
should be that hard. Famous last words :)
Tycho
On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
>
> The idea here is that the userspace handler should be able to pass an fd
> back to the trapped task, for example so it can be returned from socket().
>
> I've proposed one API here, but I'm open to other options. In particular,
> this only lets you return an fd from a syscall, which may not be enough in
> all cases. For example, if an fd is written to an output parameter instead
> of returned, the current API can't handle this. Another case is that
> netlink takes as input fds sometimes (IFLA_NET_NS_FD, e.g.). If netlink
> ever decides to install an fd and output it, we wouldn't be able to handle
> this either.
>
> Still, the vast majority of interesting cases are covered by this API, so
> perhaps it is Enough.
>
> I've left it as a separate commit for two reasons:
> * It illustrates the way in which we would grow struct seccomp_notif and
> struct seccomp_notif_resp without using netlink
> * It shows just how little code is needed to accomplish this :)
>
[...]
> @@ -1669,10 +1706,20 @@ static ssize_t seccomp_notify_write(struct file *file, const char __user *buf,
> goto out;
> }
>
> + if (resp.return_fd) {
> + knotif->flags = resp.fd_flags;
> + knotif->file = fget(resp.fd);
> + if (!knotif->file) {
> + ret = -EBADF;
> + goto out;
> + }
> + }
> +
I think this is a security bug. Imagine the following scenario:
- attacker creates processes A and B
- process A installs a seccomp filter and sends the notification fd
to process B
- process A starts a syscall for which the filter returns
SECCOMP_RET_USER_NOTIF
- process B reads the notification from the notification fd
- process B uses dup2() to copy the notification fd to file
descriptor 1 (stdout)
- process B executes a setuid root binary
- the setuid root binary opens some privileged file descriptor
(something like open("/etc/shadow", O_RDWR))
- the setuid root binary tries to write some attacker-controlled data to stdout
- seccomp_notify_write() interprets the start of the written data as
a struct seccomp_notif_resp
- seccomp_notify_write() grabs the privileged file descriptor and
installs a copy in process A
- process A now has access to the privileged file (e.g. /etc/shadow)
It isn't clear whether it would actually be exploitable - you'd need a
setuid binary that performs the right actions - but it's still bad.
Unless I'm missing something, can you please turn the ->read and
->write handlers into an ->unlocked_ioctl handler? Something like
this:
struct seccomp_user_notif_args {
u64 buf;
u64 size;
};
static long unlocked_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct seccomp_user_notif_args args;
struct seccomp_user_notif_args __user *uargs;
if (cmd != SECCOMP_USER_NOTIF_READ && cmd != SECCOMP_USER_NOTIF_WRITE)
return -EINVAL;
if (copy_from_user(&args, uargs, sizeof(args)))
return -EFAULT;
switch (cmd) {
case SECCOMP_USER_NOTIF_READ:
return seccomp_notify_read(file, (char __user
*)args.buf, (size_t)args.size);
case SECCOMP_USER_NOTIF_WRITE:
return seccomp_notify_write(file, (char __user
*)args.buf, (size_t)args.size);
default:
return -EINVAL;
}
}
On Fri, Jun 22, 2018 at 5:15 PM Tycho Andersen <[email protected]> wrote:
>
> Hi Jann,
>
> On Fri, Jun 22, 2018 at 04:40:20PM +0200, Jann Horn wrote:
> > On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
> > > This patch introduces a means for syscalls matched in seccomp to notify
> > > some other task that a particular filter has been triggered.
> > >
> > > The motivation for this is primarily for use with containers. For example,
> > > if a container does an init_module(), we obviously don't want to load this
> > > untrusted code, which may be compiled for the wrong version of the kernel
> > > anyway. Instead, we could parse the module image, figure out which module
> > > the container is trying to load and load it on the host.
> > >
> > > As another example, containers cannot mknod(), since this checks
> > > capable(CAP_SYS_ADMIN). However, harmless devices like /dev/null or
> > > /dev/zero should be ok for containers to mknod, but we'd like to avoid hard
> > > coding some whitelist in the kernel. Another example is mount(), which has
> > > many security restrictions for good reason, but configuration or runtime
> > > knowledge could potentially be used to relax these restrictions.
> > >
> > > This patch adds functionality that is already possible via at least two
> > > other means that I know about, both of which involve ptrace(): first, one
> > > could ptrace attach, and then iterate through syscalls via PTRACE_SYSCALL.
> > > Unfortunately this is slow, so a faster version would be to install a
> > > filter that does SECCOMP_RET_TRACE, which triggers a PTRACE_EVENT_SECCOMP.
> > > Since ptrace allows only one tracer, if the container runtime is that
> > > tracer, users inside the container (or outside) trying to debug it will not
> > > be able to use ptrace, which is annoying. It also means that older
> > > distributions based on Upstart cannot boot inside containers using ptrace,
> > > since upstart itself uses ptrace to start services.
> > >
> > > The actual implementation of this is fairly small, although getting the
> > > synchronization right was/is slightly complex.
> > >
> > > Finally, it's worth noting that the classic seccomp TOCTOU of reading
> > > memory data from the task still applies here, but can be avoided with
> > > careful design of the userspace handler: if the userspace handler reads all
> > > of the task memory that is necessary before applying its security policy,
> > > the tracee's subsequent memory edits will not be read by the tracer.
> >
> > I've been thinking about how one would actually write userspace code
> > that uses this API, and whether PID reuse is an issue here. As far as
> > I can tell, the following situation can happen:
> >
> > - seccomped process tries to perform a syscall that gets trapped
> > - notification is sent to the supervisor
> > - supervisor reads the notification
> > - seccomped process gets SIGKILLed
> > - new process appears with the PID that the seccomped process had
> > - supervisor tries to access memory of the seccomped process via
> > process_vm_{read,write}v or /proc/$pid/mem
> > - supervisor unintentionally accesses memory of the new process instead
> >
> > This could have particularly nasty consequences if the supervisor has
> > to write to memory of the seccomped process for some reason.
> > It might make sense to explicitly document how the API has to be used
> > to avoid such a scenario from occuring. AFAICS,
> > process_vm_{read,write}v are fundamentally unsafe for this;
> > /proc/$pid/mem might be safe if you do the following dance in the
> > supervisor to validate that you have a reference to the right struct
> > mm before starting to actually access memory:
> >
> > - supervisor reads a syscall notification for the seccomped process with PID $A
> > - supervisor opens /proc/$A/mem [taking a reference on the mm of the
> > process that currently has PID $A]
> > - supervisor reads all pending events from the notification FD; if
> > one of them says that PID $A was signalled, send back -ERESTARTSYS (or
> > -ERESTARTNOINTR?) and bail out
> > - [at this point, the open FD to /proc/$A/mem is known to actually
> > refer to the mm struct of the seccomped process]
> > - read and write on the open FD to /proc/$A/mem as necessary
> > - send back the syscall result
>
> Yes, this is a nasty problem :(. We have the id in the
> request/response structs to avoid this race, so perhaps we can re-use
> that? So it would look like:
>
> - supervisor gets syscall notification for $A
> - supervisor opens /proc/$A/mem or /proc/$A/map_files/... or a dir fd
> to the container's root or whatever
(or open a dir fd to /proc/$A; then later, you can use openat()
relative to that to open whatever you need)
> - supervisor calls seccomp(SECCOMP_NOTIFICATION_IS_VALID, req->id, listener_fd)
> - supervisor knows that the fds it has open are safe
>
> That way it doesn't have to flush the whole queue? Of course this
> makes things a lot slower, but it does enable safety for more than
> just memory accesses, and also isn't required for things which
> wouldn't read memory.
That sounds good to me. :)
> > It might be nice if the kernel was able to directly give the
> > supervisor an FD to /proc/$A/mem that is guaranteed to point to the
> > right struct mm, but trying to implement that would probably make this
> > patch set significantly larger?
>
> I'll take a look and see how big it is, it doesn't *seem* like it
> should be that hard. Famous last words :)
Good luck. :D
If you do manage to implement this, it might actually make sense to
hand out an O_PATH FD to /proc/$A (or perhaps more accurately,
/proc/$A/task/$A?) instead of an FD to /proc/*/mem. Then you could
safely open whatever files you need from the process' procfs directory
in a race-free manner.
I think you'd have to add some way to tell the kernel in which procfs
instance you want the lookup to happen; so I think you'd need to
supply an FD to the root of a procfs when opening a notification fd,
and then in the read handler, you'd have to perform a lookup in
procfs.
> On Jun 22, 2018, at 8:15 AM, Tycho Andersen <[email protected]> wrote:
>
> Hi Jann,
>
>> On Fri, Jun 22, 2018 at 04:40:20PM +0200, Jann Horn wrote:
>>> On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
>>> This patch introduces a means for syscalls matched in seccomp to notify
>>> some other task that a particular filter has been triggered.
>>>
>>> The motivation for this is primarily for use with containers. For example,
>>> if a container does an init_module(), we obviously don't want to load this
>>> untrusted code, which may be compiled for the wrong version of the kernel
>>> anyway. Instead, we could parse the module image, figure out which module
>>> the container is trying to load and load it on the host.
>>>
>>> As another example, containers cannot mknod(), since this checks
>>> capable(CAP_SYS_ADMIN). However, harmless devices like /dev/null or
>>> /dev/zero should be ok for containers to mknod, but we'd like to avoid hard
>>> coding some whitelist in the kernel. Another example is mount(), which has
>>> many security restrictions for good reason, but configuration or runtime
>>> knowledge could potentially be used to relax these restrictions.
>>>
>>> This patch adds functionality that is already possible via at least two
>>> other means that I know about, both of which involve ptrace(): first, one
>>> could ptrace attach, and then iterate through syscalls via PTRACE_SYSCALL.
>>> Unfortunately this is slow, so a faster version would be to install a
>>> filter that does SECCOMP_RET_TRACE, which triggers a PTRACE_EVENT_SECCOMP.
>>> Since ptrace allows only one tracer, if the container runtime is that
>>> tracer, users inside the container (or outside) trying to debug it will not
>>> be able to use ptrace, which is annoying. It also means that older
>>> distributions based on Upstart cannot boot inside containers using ptrace,
>>> since upstart itself uses ptrace to start services.
>>>
>>> The actual implementation of this is fairly small, although getting the
>>> synchronization right was/is slightly complex.
>>>
>>> Finally, it's worth noting that the classic seccomp TOCTOU of reading
>>> memory data from the task still applies here, but can be avoided with
>>> careful design of the userspace handler: if the userspace handler reads all
>>> of the task memory that is necessary before applying its security policy,
>>> the tracee's subsequent memory edits will not be read by the tracer.
>>
>> I've been thinking about how one would actually write userspace code
>> that uses this API, and whether PID reuse is an issue here. As far as
>> I can tell, the following situation can happen:
>>
>> - seccomped process tries to perform a syscall that gets trapped
>> - notification is sent to the supervisor
>> - supervisor reads the notification
>> - seccomped process gets SIGKILLed
>> - new process appears with the PID that the seccomped process had
>> - supervisor tries to access memory of the seccomped process via
>> process_vm_{read,write}v or /proc/$pid/mem
>> - supervisor unintentionally accesses memory of the new process instead
>>
>> This could have particularly nasty consequences if the supervisor has
>> to write to memory of the seccomped process for some reason.
>> It might make sense to explicitly document how the API has to be used
>> to avoid such a scenario from occuring. AFAICS,
>> process_vm_{read,write}v are fundamentally unsafe for this;
>> /proc/$pid/mem might be safe if you do the following dance in the
>> supervisor to validate that you have a reference to the right struct
>> mm before starting to actually access memory:
>>
>> - supervisor reads a syscall notification for the seccomped process with PID $A
>> - supervisor opens /proc/$A/mem [taking a reference on the mm of the
>> process that currently has PID $A]
>> - supervisor reads all pending events from the notification FD; if
>> one of them says that PID $A was signalled, send back -ERESTARTSYS (or
>> -ERESTARTNOINTR?) and bail out
>> - [at this point, the open FD to /proc/$A/mem is known to actually
>> refer to the mm struct of the seccomped process]
>> - read and write on the open FD to /proc/$A/mem as necessary
>> - send back the syscall result
>
> Yes, this is a nasty problem :(. We have the id in the
> request/response structs to avoid this race, so perhaps we can re-use
> that? So it would look like:
>
> - supervisor gets syscall notification for $A
> - supervisor opens /proc/$A/mem or /proc/$A/map_files/... or a dir fd
> to the container's root or whatever
> - supervisor calls seccomp(SECCOMP_NOTIFICATION_IS_VALID, req->id, listener_fd)
> - supervisor knows that the fds it has open are safe
>
> That way it doesn't have to flush the whole queue? Of course this
> makes things a lot slower, but it does enable safety for more than
> just memory accesses, and also isn't required for things which
> wouldn't read memory.
>
>> It might be nice if the kernel was able to directly give the
>> supervisor an FD to /proc/$A/mem that is guaranteed to point to the
>> right struct mm, but trying to implement that would probably make this
>> patch set significantly larger?
>
> I'll take a look and see how big it is, it doesn't *seem* like it
> should be that hard. Famous last words :)
One possible extra issue: IIRC /proc/.../mem uses FOLL_FORCE, which is not what we want here.
How about just adding an explicit “read/write the seccomp-trapped task’s memory” primitive? That should be easier than a “open mem fd” primitive.
> On Jun 22, 2018, at 9:23 AM, Jann Horn <[email protected]> wrote:
>
>> On Fri, Jun 22, 2018 at 12:05 AM Tycho Andersen <[email protected]> wrote:
>>
>> The idea here is that the userspace handler should be able to pass an fd
>> back to the trapped task, for example so it can be returned from socket().
>>
>> I've proposed one API here, but I'm open to other options. In particular,
>> this only lets you return an fd from a syscall, which may not be enough in
>> all cases. For example, if an fd is written to an output parameter instead
>> of returned, the current API can't handle this. Another case is that
>> netlink takes as input fds sometimes (IFLA_NET_NS_FD, e.g.). If netlink
>> ever decides to install an fd and output it, we wouldn't be able to handle
>> this either.
>>
>> Still, the vast majority of interesting cases are covered by this API, so
>> perhaps it is Enough.
>>
>> I've left it as a separate commit for two reasons:
>> * It illustrates the way in which we would grow struct seccomp_notif and
>> struct seccomp_notif_resp without using netlink
>> * It shows just how little code is needed to accomplish this :)
>>
> [...]
>> @@ -1669,10 +1706,20 @@ static ssize_t seccomp_notify_write(struct file *file, const char __user *buf,
>> goto out;
>> }
>>
>> + if (resp.return_fd) {
>> + knotif->flags = resp.fd_flags;
>> + knotif->file = fget(resp.fd);
>> + if (!knotif->file) {
>> + ret = -EBADF;
>> + goto out;
>> + }
>> + }
>> +
>
> I think this is a security bug. Imagine the following scenario:
>
> - attacker creates processes A and B
> - process A installs a seccomp filter and sends the notification fd
> to process B
> - process A starts a syscall for which the filter returns
> SECCOMP_RET_USER_NOTIF
> - process B reads the notification from the notification fd
> - process B uses dup2() to copy the notification fd to file
> descriptor 1 (stdout)
> - process B executes a setuid root binary
> - the setuid root binary opens some privileged file descriptor
> (something like open("/etc/shadow", O_RDWR))
> - the setuid root binary tries to write some attacker-controlled data to stdout
> - seccomp_notify_write() interprets the start of the written data as
> a struct seccomp_notif_resp
> - seccomp_notify_write() grabs the privileged file descriptor and
> installs a copy in process A
> - process A now has access to the privileged file (e.g. /etc/shadow)
>
> It isn't clear whether it would actually be exploitable - you'd need a
> setuid binary that performs the right actions - but it's still bad.
Jann is right. ->read and ->write must not reference any of the calling task’s state except the literal memory passed in.
>
> Unless I'm missing something, can you please turn the ->read and
> ->write handlers into an ->unlocked_ioctl handler? Something like
> this:
>
> struct seccomp_user_notif_args {
> u64 buf;
> u64 size;
> };
>
> static long unlocked_ioctl(struct file *file, unsigned int cmd,
> unsigned long arg)
> {
> struct seccomp_user_notif_args args;
> struct seccomp_user_notif_args __user *uargs;
>
> if (cmd != SECCOMP_USER_NOTIF_READ && cmd != SECCOMP_USER_NOTIF_WRITE)
> return -EINVAL;
>
> if (copy_from_user(&args, uargs, sizeof(args)))
> return -EFAULT;
>
> switch (cmd) {
> case SECCOMP_USER_NOTIF_READ:
> return seccomp_notify_read(file, (char __user
> *)args.buf, (size_t)args.size);
> case SECCOMP_USER_NOTIF_WRITE:
> return seccomp_notify_write(file, (char __user
> *)args.buf, (size_t)args.size);
> default:
> return -EINVAL;
> }
> }
On Fri, Jun 22, 2018 at 11:09 AM, Andy Lutomirski <[email protected]> wrote:
> One possible extra issue: IIRC /proc/.../mem uses FOLL_FORCE, which is not what we want here.
>
> How about just adding an explicit “read/write the seccomp-trapped task’s memory” primitive? That should be easier than a “open mem fd” primitive.
Uuugh. Can we avoid adding another "read/write remote process memory"
interface? The point of this series was to provide a lightweight
approach to what should normally be possible via the existing
seccomp+ptrace interface. I do like Jann's context idea, but I agree
with Andy: it can't be a handle to /proc/$pid/mem, since it's
FOLL_FORCE. Is there any other kind of process context id we can use
for this instead of pid? There was once an idea of pid-fd but it never
landed... This would let us get rid of the "id" in the structure too.
And if that existed, we could make process_vm_*v() safer too (taking a
pid-fd instead of a pid).
-Kees
--
Kees Cook
Pixel Security
On Fri, Jun 22, 2018 at 11:51 PM Kees Cook <[email protected]> wrote:
>
> On Fri, Jun 22, 2018 at 11:09 AM, Andy Lutomirski <[email protected]> wrote:
> > One possible extra issue: IIRC /proc/.../mem uses FOLL_FORCE, which is not what we want here.
Uuugh, I forgot about that.
> > How about just adding an explicit “read/write the seccomp-trapped task’s memory” primitive? That should be easier than a “open mem fd” primitive.
>
> Uuugh. Can we avoid adding another "read/write remote process memory"
> interface? The point of this series was to provide a lightweight
> approach to what should normally be possible via the existing
> seccomp+ptrace interface. I do like Jann's context idea, but I agree
> with Andy: it can't be a handle to /proc/$pid/mem, since it's
> FOLL_FORCE. Is there any other kind of process context id we can use
> for this instead of pid? There was once an idea of pid-fd but it never
> landed... This would let us get rid of the "id" in the structure too.
> And if that existed, we could make process_vm_*v() safer too (taking a
> pid-fd instead of a pid).
Or make a duplicate of /proc/$pid/mem that only differs in whether it
sets FOLL_FORCE? The code is basically already there... something like
this:
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 80aa42506b8b..e8a6a63046da 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -762,6 +762,8 @@ static int mem_open(struct inode *inode, struct file *file)
return ret;
}
+static const struct file_operations proc_mem_operations;
+
static ssize_t mem_rw(struct file *file, char __user *buf,
size_t count, loff_t *ppos, int write)
{
@@ -782,7 +784,10 @@ static ssize_t mem_rw(struct file *file, char __user *buf,
if (!mmget_not_zero(mm))
goto free;
- flags = FOLL_FORCE | (write ? FOLL_WRITE : 0);
+ flags = (write ? FOLL_WRITE : 0);
+ if (file->f_op == &proc_mem_operations) {
+ flags |= FOLL_FORCE;
+ }
while (count > 0) {
int this_len = min_t(int, count, PAGE_SIZE);
@@ -861,6 +866,14 @@ static const struct file_operations proc_mem_operations = {
.release = mem_release,
};
+static const struct file_operations proc_mem_noforce_operations = {
+ .llseek = mem_lseek,
+ .read = mem_read,
+ .write = mem_write,
+ .open = mem_open,
+ .release = mem_release,
+};
+
static int environ_open(struct inode *inode, struct file *file)
{
return __mem_open(inode, file, PTRACE_MODE_READ);
@@ -2916,6 +2929,7 @@ static const struct pid_entry tgid_base_stuff[] = {
REG("numa_maps", S_IRUGO, proc_pid_numa_maps_operations),
#endif
REG("mem", S_IRUSR|S_IWUSR, proc_mem_operations),
+ REG("mem_noforce", S_IRUSR|S_IWUSR, proc_mem_noforce_operations),
LNK("cwd", proc_cwd_link),
LNK("root", proc_root_link),
LNK("exe", proc_exe_link),
@@ -3302,6 +3316,7 @@ static const struct pid_entry tid_base_stuff[] = {
REG("numa_maps", S_IRUGO, proc_tid_numa_maps_operations),
#endif
REG("mem", S_IRUSR|S_IWUSR, proc_mem_operations),
+ REG("mem_noforce",S_IRUSR|S_IWUSR, proc_mem_noforce_operations),
LNK("cwd", proc_cwd_link),
LNK("root", proc_root_link),
LNK("exe", proc_exe_link),
On Sat, Jun 23, 2018 at 12:27:43AM +0200, Jann Horn wrote:
> On Fri, Jun 22, 2018 at 11:51 PM Kees Cook <[email protected]> wrote:
> >
> > On Fri, Jun 22, 2018 at 11:09 AM, Andy Lutomirski <[email protected]> wrote:
> > > One possible extra issue: IIRC /proc/.../mem uses FOLL_FORCE, which is not what we want here.
>
> Uuugh, I forgot about that.
>
> > > How about just adding an explicit “read/write the seccomp-trapped task’s memory” primitive? That should be easier than a “open mem fd” primitive.
> >
> > Uuugh. Can we avoid adding another "read/write remote process memory"
> > interface? The point of this series was to provide a lightweight
> > approach to what should normally be possible via the existing
> > seccomp+ptrace interface. I do like Jann's context idea, but I agree
> > with Andy: it can't be a handle to /proc/$pid/mem, since it's
> > FOLL_FORCE. Is there any other kind of process context id we can use
> > for this instead of pid? There was once an idea of pid-fd but it never
> > landed... This would let us get rid of the "id" in the structure too.
> > And if that existed, we could make process_vm_*v() safer too (taking a
> > pid-fd instead of a pid).
>
> Or make a duplicate of /proc/$pid/mem that only differs in whether it
> sets FOLL_FORCE? The code is basically already there... something like
> this:
But we want more than just memory access, I think. rootfs access, ns
fds, etc. all seem like they might be useful, and racy to open.
I guess I see two options: use the existing id and add something to
seccomp() to ask if it's still valid or independent of this patchset
add some kind of pid id :\
Tycho
> On Jun 25, 2018, at 6:32 PM, Tycho Andersen <[email protected]> wrote:
>
>> On Sat, Jun 23, 2018 at 12:27:43AM +0200, Jann Horn wrote:
>>> On Fri, Jun 22, 2018 at 11:51 PM Kees Cook <[email protected]> wrote:
>>>
>>>> On Fri, Jun 22, 2018 at 11:09 AM, Andy Lutomirski <[email protected]> wrote:
>>>> One possible extra issue: IIRC /proc/.../mem uses FOLL_FORCE, which is not what we want here.
>>
>> Uuugh, I forgot about that.
>>
>>>> How about just adding an explicit “read/write the seccomp-trapped task’s memory” primitive? That should be easier than a “open mem fd” primitive.
>>>
>>> Uuugh. Can we avoid adding another "read/write remote process memory"
>>> interface? The point of this series was to provide a lightweight
>>> approach to what should normally be possible via the existing
>>> seccomp+ptrace interface. I do like Jann's context idea, but I agree
>>> with Andy: it can't be a handle to /proc/$pid/mem, since it's
>>> FOLL_FORCE. Is there any other kind of process context id we can use
>>> for this instead of pid? There was once an idea of pid-fd but it never
>>> landed... This would let us get rid of the "id" in the structure too.
>>> And if that existed, we could make process_vm_*v() safer too (taking a
>>> pid-fd instead of a pid).
>>
>> Or make a duplicate of /proc/$pid/mem that only differs in whether it
>> sets FOLL_FORCE? The code is basically already there... something like
>> this:
>
> But we want more than just memory access, I think. rootfs access, ns
> fds, etc. all seem like they might be useful, and racy to open.
>
> I guess I see two options: use the existing id and add something to
> seccomp() to ask if it's still valid or independent of this patchset
> add some kind of pid id :\
>
I think we use the existing id / cookie / whatever and ask seccomp, or new syscalls, to do the requested operation. This is because we know the target task is in a very special stopping point. As a result, a seccomp-specific mechanism can do RCU-less fd modifications against a single-threaded target, can muck with things like struct cred, etc, while a more general interface can’t.
It might be nice to add a syscall with flags such that it could be used on ptrace-stopped targets later on. Something like:
access_remote_task(int fd, u64 id, u32 type, ...)
Where type is 16 bits of “id and fd is from seccomp” and 16 bits of “write memory” or such.
> On Aug 6, 2018, at 7:44 PM, Tycho Andersen <[email protected]> wrote:
>
> Hi all,
>
> Dinesh Subhraveti has claimed that some part of this series might be
> patented. While he has not furnished me with anything to confirm this
> claim, I'll put this series on hold.
That... is utterly ridiculous. Does LF have a mechanism to figure out wtf and deal with it by, for example, filing for ex parte review.
Every microkernel ever should strongly resemble prior art.
>
> Tycho
>
>> On Thu, Jun 21, 2018 at 04:04:12PM -0600, Tycho Andersen wrote:
>> Hi all,
>>
>> Here's v4 of the seccomp trap to userspace series. v3 is here:
>> https://lkml.org/lkml/2018/5/31/527
>>
>> I believe we've addressed the two burning questions I had about v3: 1.
>> it seems ok not to use netlink, since there's not a great way to re-use
>> the API without a lot of unnecessary code and 2. only having return
>> capability for fds seems fine with people. Or at least I haven't heard
>> any strong objections.
>>
>> I've re-worked a bunch of things in this version based on feedback from
>> the last series. See patch notes for details. At this point I'm not
>> aware of anything that needs to be addressed, but of course that is
>> subject to change :)
>>
>> Tycho
>>
>> Tycho Andersen (4):
>> seccomp: add a return code to trap to userspace
>> seccomp: make get_nth_filter available outside of CHECKPOINT_RESTORE
>> seccomp: add a way to get a listener fd from ptrace
>> seccomp: add support for passing fds via USER_NOTIF
>>
>> .../userspace-api/seccomp_filter.rst | 79 +++
>> arch/Kconfig | 7 +
>> include/linux/seccomp.h | 18 +-
>> include/uapi/linux/ptrace.h | 2 +
>> include/uapi/linux/seccomp.h | 23 +-
>> kernel/ptrace.c | 4 +
>> kernel/seccomp.c | 491 ++++++++++++++-
>> tools/testing/selftests/seccomp/seccomp_bpf.c | 560 +++++++++++++++++-
>> 8 files changed, 1172 insertions(+), 12 deletions(-)
>>
>> --
>> 2.17.1
>>
Hi all,
Dinesh Subhraveti has claimed that some part of this series might be
patented. While he has not furnished me with anything to confirm this
claim, I'll put this series on hold.
Tycho
On Thu, Jun 21, 2018 at 04:04:12PM -0600, Tycho Andersen wrote:
> Hi all,
>
> Here's v4 of the seccomp trap to userspace series. v3 is here:
> https://lkml.org/lkml/2018/5/31/527
>
> I believe we've addressed the two burning questions I had about v3: 1.
> it seems ok not to use netlink, since there's not a great way to re-use
> the API without a lot of unnecessary code and 2. only having return
> capability for fds seems fine with people. Or at least I haven't heard
> any strong objections.
>
> I've re-worked a bunch of things in this version based on feedback from
> the last series. See patch notes for details. At this point I'm not
> aware of anything that needs to be addressed, but of course that is
> subject to change :)
>
> Tycho
>
> Tycho Andersen (4):
> seccomp: add a return code to trap to userspace
> seccomp: make get_nth_filter available outside of CHECKPOINT_RESTORE
> seccomp: add a way to get a listener fd from ptrace
> seccomp: add support for passing fds via USER_NOTIF
>
> .../userspace-api/seccomp_filter.rst | 79 +++
> arch/Kconfig | 7 +
> include/linux/seccomp.h | 18 +-
> include/uapi/linux/ptrace.h | 2 +
> include/uapi/linux/seccomp.h | 23 +-
> kernel/ptrace.c | 4 +
> kernel/seccomp.c | 491 ++++++++++++++-
> tools/testing/selftests/seccomp/seccomp_bpf.c | 560 +++++++++++++++++-
> 8 files changed, 1172 insertions(+), 12 deletions(-)
>
> --
> 2.17.1
>
On Mon, Aug 06, 2018 at 08:44:42PM -0600, Tycho Andersen wrote:
> Hi all,
>
> Dinesh Subhraveti has claimed that some part of this series might be
> patented. While he has not furnished me with anything to confirm this
> claim, I'll put this series on hold.
Hey man,
Sorry to hear that your faced with such nonsense, Tycho. This is utter
bullsh*t of course. If you have more details at some point and feel
comfortable doing so it would probably be good to share them here.
Christian
>
> Tycho
>
> On Thu, Jun 21, 2018 at 04:04:12PM -0600, Tycho Andersen wrote:
> > Hi all,
> >
> > Here's v4 of the seccomp trap to userspace series. v3 is here:
> > https://lkml.org/lkml/2018/5/31/527
> >
> > I believe we've addressed the two burning questions I had about v3: 1.
> > it seems ok not to use netlink, since there's not a great way to re-use
> > the API without a lot of unnecessary code and 2. only having return
> > capability for fds seems fine with people. Or at least I haven't heard
> > any strong objections.
> >
> > I've re-worked a bunch of things in this version based on feedback from
> > the last series. See patch notes for details. At this point I'm not
> > aware of anything that needs to be addressed, but of course that is
> > subject to change :)
> >
> > Tycho
> >
> > Tycho Andersen (4):
> > seccomp: add a return code to trap to userspace
> > seccomp: make get_nth_filter available outside of CHECKPOINT_RESTORE
> > seccomp: add a way to get a listener fd from ptrace
> > seccomp: add support for passing fds via USER_NOTIF
> >
> > .../userspace-api/seccomp_filter.rst | 79 +++
> > arch/Kconfig | 7 +
> > include/linux/seccomp.h | 18 +-
> > include/uapi/linux/ptrace.h | 2 +
> > include/uapi/linux/seccomp.h | 23 +-
> > kernel/ptrace.c | 4 +
> > kernel/seccomp.c | 491 ++++++++++++++-
> > tools/testing/selftests/seccomp/seccomp_bpf.c | 560 +++++++++++++++++-
> > 8 files changed, 1172 insertions(+), 12 deletions(-)
> >
> > --
> > 2.17.1
> >
> _______________________________________________
> Containers mailing list
> [email protected]
> https://lists.linuxfoundation.org/mailman/listinfo/containers
On Mon, Aug 6, 2018 at 8:30 PM, Christian Brauner <[email protected]> wrote:
> On Mon, Aug 06, 2018 at 08:44:42PM -0600, Tycho Andersen wrote:
>> Hi all,
>>
>> Dinesh Subhraveti has claimed that some part of this series might be
>> patented. While he has not furnished me with anything to confirm this
>> claim, I'll put this series on hold.
>
> Hey man,
>
> Sorry to hear that your faced with such nonsense, Tycho. This is utter
> bullsh*t of course. If you have more details at some point and feel
> comfortable doing so it would probably be good to share them here.
IANAL, but I think it would probably *not* be good to share them here.
Patent law is seriously fucked up like that.
--Andy
On Mon, Aug 06, 2018 at 09:19:04PM -0700, Andy Lutomirski wrote:
> On Mon, Aug 6, 2018 at 8:30 PM, Christian Brauner <[email protected]> wrote:
> > On Mon, Aug 06, 2018 at 08:44:42PM -0600, Tycho Andersen wrote:
> >> Hi all,
> >>
> >> Dinesh Subhraveti has claimed that some part of this series might be
> >> patented. While he has not furnished me with anything to confirm this
> >> claim, I'll put this series on hold.
> >
> > Hey man,
> >
> > Sorry to hear that your faced with such nonsense, Tycho. This is utter
> > bullsh*t of course. If you have more details at some point and feel
> > comfortable doing so it would probably be good to share them here.
>
> IANAL, but I think it would probably *not* be good to share them here.
> Patent law is seriously fucked up like that.
**ugh**. Yeah, you're probably right. My thought was that in the face of
publicity this nonesense might go away quickly.
Christian
On Mon, 2018-08-06 at 20:44 -0600, Tycho Andersen wrote:
> Hi all,
>
> Dinesh Subhraveti has claimed that some part of this series might be
> patented. While he has not furnished me with anything to confirm this
> claim, I'll put this series on hold.
Just on a general policy for handling things like this.
Firstly, never discuss specifics on a mailing list. Patents are a
really tricky area where advice in confidence is needed and lack of
confidentiality usually only benefits the patent holder.
Secondly, vague threats (like this) can be safely ignored. Thanks to
the terms of the GPL and the specific actions of OIN, we legitimately
have licences to a vast pool of patents, so we should assume for every
vague threat of the existence of a patent that we do legitimately
possess a licence until the threat is made specific.
Finally, if the vague threat becomes specific (which means citation of
patent by number and claim) you need to engage the resources we have at
our disposal to investigate. Likely it will be OIN resources that do
this investigation, but the way to begin is to start with your
corporate counsel for contributions on behalf of a company. If they're
on behalf of you as an individual, you can begin with Counsel which the
Linux Foundation will provide.
James
On Mon, Aug 6, 2018 at 7:44 PM Tycho Andersen <[email protected]> wrote:
>
> Hi all,
>
> Dinesh Subhraveti has claimed that some part of this series might be
> patented. While he has not furnished me with anything to confirm this
> claim, I'll put this series on hold.
For the sake of everyone's clarity, there is no patent or intellectual
property issue here and we'd like to see this feature in the kernel.
I did indicate that there is a patent related to the mechanism
underlying this patch set but that is completely incidental to the
issue here. We have filed that patent only for the purpose of defense
and have no interest or resources to go after infringements. As such,
I spoke about the approach, it's value for networking and a few
possible ways of implementing it in the kernel at Linux Plumbers
Conference 2017.
As a contractor and a member of our team at AppSwitch (FKA Fermat),
Tycho Andersen helped implement a fully user space version of system
call trap mechanism based on seccomp / fd-passing and participated in
our team discussions about upstreaming a kernel version of the
feature. Given that context, we were taken aback that he posted the
v1 patch set without letting us know and without any mention of
AppSwitch in the post even though he was still under contract at that
time.
In any case, please note that we will communicate with Tycho directly
regarding this matter going forward.
--
Dinesh Subhraveti
On Tue, Aug 07, 2018 at 09:11:50PM -0700, Dinesh Subhraveti wrote:
> On Mon, Aug 6, 2018 at 7:44 PM, Tycho Andersen <[email protected]> wrote:
> > Hi all,
> >
> > Dinesh Subhraveti has claimed that some part of this series might be
> > patented. While he has not furnished me with anything to confirm this
> > claim, I'll put this series on hold.
>
> For the sake of everyone's clarity, there is no patent or intellectual
> property issue here and we'd like to see this feature in the kernel. I did
> indicate that there is a patent related to the mechanism underlying this
> patch set but that is completely incidental to the issue here. We have
> filed that patent only for the purpose of defense and have no interest or
> resources to go after infringements. As such, I spoke about the approach,
> it's value for networking and a few possible ways of implementing it in the
> kernel at Linux Plumbers Conference 2017.
>
> As a contractor and a member of our team at AppSwitch (FKA Fermat), Tycho
> Andersen helped implement a fully user space version of system call trap
> mechanism based on seccomp / fd-passing and participated in our team
> discussions about upstreaming a kernel version of the feature. Given that
> context, we were taken aback that he posted the v1 patch set without
> letting us know and without any mention of AppSwitch in the post even
> though he was still under contract at that time.
It seems we disagree on the series of events, but agree that this
patchset should move forward. I'll work on preparing a v5.
Thanks!
Tycho