You've already forked linux-rockchip
mirror of
https://github.com/armbian/linux-rockchip.git
synced 2026-01-06 11:08:10 -08:00
seccomp: add a return code to trap to userspace
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 mount() in general since various filesystems assume a trusted image. However, if an orchestrator knows that e.g. a particular block device has not been exposed to a container for writing, it want to allow the container to mount that block device (that is, handle the mount for it). 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 monitor services while starting. 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. Signed-off-by: Tycho Andersen <tycho@tycho.ws> CC: Kees Cook <keescook@chromium.org> CC: Andy Lutomirski <luto@amacapital.net> CC: Oleg Nesterov <oleg@redhat.com> CC: Eric W. Biederman <ebiederm@xmission.com> CC: "Serge E. Hallyn" <serge@hallyn.com> Acked-by: Serge Hallyn <serge@hallyn.com> CC: Christian Brauner <christian@brauner.io> CC: Tyler Hicks <tyhicks@canonical.com> CC: Akihiro Suda <suda.akihiro@lab.ntt.co.jp> Signed-off-by: Kees Cook <keescook@chromium.org>
This commit is contained in:
committed by
Kees Cook
parent
a5662e4d81
commit
6a21cc50f0
@@ -79,6 +79,7 @@ Code Seq#(hex) Include File Comments
|
||||
0x1b all InfiniBand Subsystem <http://infiniband.sourceforge.net/>
|
||||
0x20 all drivers/cdrom/cm206.h
|
||||
0x22 all scsi/sg.h
|
||||
'!' 00-1F uapi/linux/seccomp.h
|
||||
'#' 00-3F IEEE 1394 Subsystem Block for the entire subsystem
|
||||
'$' 00-0F linux/perf_counter.h, linux/perf_event.h
|
||||
'%' 00-0F include/uapi/linux/stm.h
|
||||
|
||||
@@ -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,85 @@ 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 wish to intercept particular
|
||||
syscalls (``mount()``, ``finit_module()``, etc.) and change their behavior.
|
||||
|
||||
To acquire a notification FD, use the ``SECCOMP_FILTER_FLAG_NEW_LISTENER``
|
||||
argument to the ``seccomp()`` syscall:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
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 then be
|
||||
passed around via ``SCM_RIGHTS`` or similar. 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:: c
|
||||
|
||||
struct seccomp_notif_sizes {
|
||||
__u16 seccomp_notif;
|
||||
__u16 seccomp_notif_resp;
|
||||
__u16 seccomp_data;
|
||||
};
|
||||
|
||||
struct seccomp_notif {
|
||||
__u64 id;
|
||||
__u32 pid;
|
||||
__u32 flags;
|
||||
struct seccomp_data data;
|
||||
};
|
||||
|
||||
struct seccomp_notif_resp {
|
||||
__u64 id;
|
||||
__s64 val;
|
||||
__s32 error;
|
||||
__u32 flags;
|
||||
};
|
||||
|
||||
The ``struct seccomp_notif_sizes`` structure can be used to determine the size
|
||||
of the various structures used in seccomp notifications. The size of ``struct
|
||||
seccomp_data`` may change in the future, so code should use:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct seccomp_notif_sizes sizes;
|
||||
seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes);
|
||||
|
||||
to determine the size of the various structures to allocate. See
|
||||
samples/seccomp/user-trap.c for an example.
|
||||
|
||||
Users can read via ``ioctl(SECCOMP_IOCTL_NOTIF_RECV)`` (or ``poll()``) on a
|
||||
seccomp notification fd to receive a ``struct seccomp_notif``, which contains
|
||||
five members: the input length of the structure, a unique-per-filter ``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), a ``flags``
|
||||
member which for now only has ``SECCOMP_NOTIF_FLAG_SIGNALED``, representing
|
||||
whether or not the notification is a result of a non-fatal signal, and the
|
||||
``data`` passed to seccomp. Userspace can then make a decision based on this
|
||||
information about what to do, and ``ioctl(SECCOMP_IOCTL_NOTIF_SEND)`` 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 accessible to suitably privileged traces via ``ptrace()`` or
|
||||
``/proc/pid/mem``. 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
|
||||
=======
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
#define SECCOMP_SET_MODE_STRICT 0
|
||||
#define SECCOMP_SET_MODE_FILTER 1
|
||||
#define SECCOMP_GET_ACTION_AVAIL 2
|
||||
#define SECCOMP_GET_NOTIF_SIZES 3
|
||||
|
||||
/* 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 +37,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 +63,35 @@ struct seccomp_data {
|
||||
__u64 args[6];
|
||||
};
|
||||
|
||||
struct seccomp_notif_sizes {
|
||||
__u16 seccomp_notif;
|
||||
__u16 seccomp_notif_resp;
|
||||
__u16 seccomp_data;
|
||||
};
|
||||
|
||||
struct seccomp_notif {
|
||||
__u64 id;
|
||||
__u32 pid;
|
||||
__u32 flags;
|
||||
struct seccomp_data data;
|
||||
};
|
||||
|
||||
struct seccomp_notif_resp {
|
||||
__u64 id;
|
||||
__s64 val;
|
||||
__s32 error;
|
||||
__u32 flags;
|
||||
};
|
||||
|
||||
#define SECCOMP_IOC_MAGIC '!'
|
||||
#define SECCOMP_IO(nr) _IO(SECCOMP_IOC_MAGIC, nr)
|
||||
#define SECCOMP_IOR(nr, type) _IOR(SECCOMP_IOC_MAGIC, nr, type)
|
||||
#define SECCOMP_IOW(nr, type) _IOW(SECCOMP_IOC_MAGIC, nr, type)
|
||||
#define SECCOMP_IOWR(nr, type) _IOWR(SECCOMP_IOC_MAGIC, nr, type)
|
||||
|
||||
/* Flags for seccomp notification fd ioctl. */
|
||||
#define SECCOMP_IOCTL_NOTIF_RECV SECCOMP_IOWR(0, struct seccomp_notif)
|
||||
#define SECCOMP_IOCTL_NOTIF_SEND SECCOMP_IOWR(1, \
|
||||
struct seccomp_notif_resp)
|
||||
#define SECCOMP_IOCTL_NOTIF_ID_VALID SECCOMP_IOR(2, __u64)
|
||||
#endif /* _UAPI_LINUX_SECCOMP_H */
|
||||
|
||||
448
kernel/seccomp.c
448
kernel/seccomp.c
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@
|
||||
* Test code for seccomp bpf.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/types.h>
|
||||
|
||||
/*
|
||||
@@ -40,10 +41,12 @@
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/times.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include "../kselftest_harness.h"
|
||||
|
||||
@@ -133,6 +136,10 @@ struct seccomp_data {
|
||||
#define SECCOMP_GET_ACTION_AVAIL 2
|
||||
#endif
|
||||
|
||||
#ifndef SECCOMP_GET_NOTIF_SIZES
|
||||
#define SECCOMP_GET_NOTIF_SIZES 3
|
||||
#endif
|
||||
|
||||
#ifndef SECCOMP_FILTER_FLAG_TSYNC
|
||||
#define SECCOMP_FILTER_FLAG_TSYNC (1UL << 0)
|
||||
#endif
|
||||
@@ -154,6 +161,44 @@ struct seccomp_metadata {
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef SECCOMP_FILTER_FLAG_NEW_LISTENER
|
||||
#define SECCOMP_FILTER_FLAG_NEW_LISTENER (1UL << 3)
|
||||
|
||||
#define SECCOMP_RET_USER_NOTIF 0x7fc00000U
|
||||
|
||||
#define SECCOMP_IOC_MAGIC '!'
|
||||
#define SECCOMP_IO(nr) _IO(SECCOMP_IOC_MAGIC, nr)
|
||||
#define SECCOMP_IOR(nr, type) _IOR(SECCOMP_IOC_MAGIC, nr, type)
|
||||
#define SECCOMP_IOW(nr, type) _IOW(SECCOMP_IOC_MAGIC, nr, type)
|
||||
#define SECCOMP_IOWR(nr, type) _IOWR(SECCOMP_IOC_MAGIC, nr, type)
|
||||
|
||||
/* Flags for seccomp notification fd ioctl. */
|
||||
#define SECCOMP_IOCTL_NOTIF_RECV SECCOMP_IOWR(0, struct seccomp_notif)
|
||||
#define SECCOMP_IOCTL_NOTIF_SEND SECCOMP_IOWR(1, \
|
||||
struct seccomp_notif_resp)
|
||||
#define SECCOMP_IOCTL_NOTIF_ID_VALID SECCOMP_IOR(2, __u64)
|
||||
|
||||
struct seccomp_notif {
|
||||
__u64 id;
|
||||
__u32 pid;
|
||||
__u32 flags;
|
||||
struct seccomp_data data;
|
||||
};
|
||||
|
||||
struct seccomp_notif_resp {
|
||||
__u64 id;
|
||||
__s64 val;
|
||||
__s32 error;
|
||||
__u32 flags;
|
||||
};
|
||||
|
||||
struct seccomp_notif_sizes {
|
||||
__u16 seccomp_notif;
|
||||
__u16 seccomp_notif_resp;
|
||||
__u16 seccomp_data;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef seccomp
|
||||
int seccomp(unsigned int op, unsigned int flags, void *args)
|
||||
{
|
||||
@@ -2077,7 +2122,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 +2979,403 @@ skip:
|
||||
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);
|
||||
}
|
||||
|
||||
#define USER_NOTIF_MAGIC 116983961184613L
|
||||
TEST(user_notification_basic)
|
||||
{
|
||||
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 for grins. */
|
||||
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(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
|
||||
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;
|
||||
|
||||
/* check that we make sure flags == 0 */
|
||||
resp.flags = 1;
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), -1);
|
||||
EXPECT_EQ(errno, EINVAL);
|
||||
|
||||
resp.flags = 0;
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
|
||||
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
TEST(user_notification_kill_in_middle)
|
||||
{
|
||||
pid_t pid;
|
||||
long ret;
|
||||
int listener;
|
||||
struct seccomp_notif req = {};
|
||||
struct seccomp_notif_resp resp = {};
|
||||
|
||||
listener = user_trap_syscall(__NR_getpid,
|
||||
SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
EXPECT_GE(listener, 0);
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ID_VALID, &req.id), 0);
|
||||
|
||||
EXPECT_EQ(kill(pid, SIGKILL), 0);
|
||||
EXPECT_EQ(waitpid(pid, NULL, 0), pid);
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ID_VALID, &req.id), -1);
|
||||
|
||||
resp.id = req.id;
|
||||
ret = ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp);
|
||||
EXPECT_EQ(ret, -1);
|
||||
EXPECT_EQ(errno, ENOENT);
|
||||
}
|
||||
|
||||
static int handled = -1;
|
||||
|
||||
static void signal_handler(int signal)
|
||||
{
|
||||
if (write(handled, "c", 1) != 1)
|
||||
perror("write from signal");
|
||||
}
|
||||
|
||||
TEST(user_notification_signal)
|
||||
{
|
||||
pid_t pid;
|
||||
long ret;
|
||||
int status, listener, sk_pair[2];
|
||||
struct seccomp_notif req = {};
|
||||
struct seccomp_notif_resp resp = {};
|
||||
char c;
|
||||
|
||||
ASSERT_EQ(socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair), 0);
|
||||
|
||||
listener = user_trap_syscall(__NR_gettid,
|
||||
SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
EXPECT_GE(listener, 0);
|
||||
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0) {
|
||||
close(sk_pair[0]);
|
||||
handled = sk_pair[1];
|
||||
if (signal(SIGUSR1, signal_handler) == SIG_ERR) {
|
||||
perror("signal");
|
||||
exit(1);
|
||||
}
|
||||
/*
|
||||
* ERESTARTSYS behavior is a bit hard to test, because we need
|
||||
* to rely on a signal that has not yet been handled. Let's at
|
||||
* least check that the error code gets propagated through, and
|
||||
* hope that it doesn't break when there is actually a signal :)
|
||||
*/
|
||||
ret = syscall(__NR_gettid);
|
||||
exit(!(ret == -1 && errno == 512));
|
||||
}
|
||||
|
||||
close(sk_pair[1]);
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
|
||||
EXPECT_EQ(kill(pid, SIGUSR1), 0);
|
||||
|
||||
/*
|
||||
* Make sure the signal really is delivered, which means we're not
|
||||
* stuck in the user notification code any more and the notification
|
||||
* should be dead.
|
||||
*/
|
||||
EXPECT_EQ(read(sk_pair[0], &c, 1), 1);
|
||||
|
||||
resp.id = req.id;
|
||||
resp.error = -EPERM;
|
||||
resp.val = 0;
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), -1);
|
||||
EXPECT_EQ(errno, ENOENT);
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
|
||||
resp.id = req.id;
|
||||
resp.error = -512; /* -ERESTARTSYS */
|
||||
resp.val = 0;
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
|
||||
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
TEST(user_notification_closed_listener)
|
||||
{
|
||||
pid_t pid;
|
||||
long ret;
|
||||
int status, listener;
|
||||
|
||||
listener = user_trap_syscall(__NR_getpid,
|
||||
SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
EXPECT_GE(listener, 0);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
struct seccomp_notif req = {};
|
||||
struct seccomp_notif_resp resp = {};
|
||||
|
||||
ASSERT_EQ(unshare(CLONE_NEWPID), 0);
|
||||
|
||||
listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
ASSERT_GE(listener, 0);
|
||||
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0)
|
||||
exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC);
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
EXPECT_EQ(req.pid, pid);
|
||||
|
||||
resp.id = req.id;
|
||||
resp.error = 0;
|
||||
resp.val = USER_NOTIF_MAGIC;
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
|
||||
|
||||
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;
|
||||
struct seccomp_notif req = {};
|
||||
struct seccomp_notif_resp resp = {};
|
||||
|
||||
listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
ASSERT_GE(listener, 0);
|
||||
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0) {
|
||||
ASSERT_EQ(unshare(CLONE_NEWPID), 0);
|
||||
|
||||
pid2 = fork();
|
||||
ASSERT_GE(pid2, 0);
|
||||
|
||||
if (pid2 == 0)
|
||||
exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC);
|
||||
|
||||
EXPECT_EQ(waitpid(pid2, &status, 0), pid2);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
exit(WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
/* 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(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
/*
|
||||
* 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(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
close(listener);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
TEST(user_notification_fault_recv)
|
||||
{
|
||||
pid_t pid;
|
||||
int status, listener;
|
||||
struct seccomp_notif req = {};
|
||||
struct seccomp_notif_resp resp = {};
|
||||
|
||||
listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
ASSERT_GE(listener, 0);
|
||||
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0)
|
||||
exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC);
|
||||
|
||||
/* Do a bad recv() */
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, NULL), -1);
|
||||
EXPECT_EQ(errno, EFAULT);
|
||||
|
||||
/* We should still be able to receive this notification, though. */
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
EXPECT_EQ(req.pid, pid);
|
||||
|
||||
resp.id = req.id;
|
||||
resp.error = 0;
|
||||
resp.val = USER_NOTIF_MAGIC;
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
|
||||
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
TEST(seccomp_get_notif_sizes)
|
||||
{
|
||||
struct seccomp_notif_sizes sizes;
|
||||
|
||||
EXPECT_EQ(seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes), 0);
|
||||
EXPECT_EQ(sizes.seccomp_notif, sizeof(struct seccomp_notif));
|
||||
EXPECT_EQ(sizes.seccomp_notif_resp, sizeof(struct seccomp_notif_resp));
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* - add microbenchmarks
|
||||
|
||||
Reference in New Issue
Block a user