diff options
| author | Tycho Andersen <tycho@tycho.ws> | 2018-12-09 13:24:13 -0500 |
|---|---|---|
| committer | Kees Cook <keescook@chromium.org> | 2018-12-11 19:28:41 -0500 |
| commit | 6a21cc50f0c7f87dae5259f6cfefe024412313f6 (patch) | |
| tree | 0312987667dc2b05e9f9cc33586fac101b542a9a /tools/testing | |
| parent | a5662e4d81c4d4b08140c625d0f3c50b15786252 (diff) | |
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>
Diffstat (limited to 'tools/testing')
| -rw-r--r-- | tools/testing/selftests/seccomp/seccomp_bpf.c | 447 |
1 files changed, 445 insertions, 2 deletions
diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c index e1473234968d..5c9768a1b8cd 100644 --- a/tools/testing/selftests/seccomp/seccomp_bpf.c +++ b/tools/testing/selftests/seccomp/seccomp_bpf.c | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | * Test code for seccomp bpf. | 5 | * Test code for seccomp bpf. |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #define _GNU_SOURCE | ||
| 8 | #include <sys/types.h> | 9 | #include <sys/types.h> |
| 9 | 10 | ||
| 10 | /* | 11 | /* |
| @@ -40,10 +41,12 @@ | |||
| 40 | #include <sys/fcntl.h> | 41 | #include <sys/fcntl.h> |
| 41 | #include <sys/mman.h> | 42 | #include <sys/mman.h> |
| 42 | #include <sys/times.h> | 43 | #include <sys/times.h> |
| 44 | #include <sys/socket.h> | ||
| 45 | #include <sys/ioctl.h> | ||
| 43 | 46 | ||
| 44 | #define _GNU_SOURCE | ||
| 45 | #include <unistd.h> | 47 | #include <unistd.h> |
| 46 | #include <sys/syscall.h> | 48 | #include <sys/syscall.h> |
| 49 | #include <poll.h> | ||
| 47 | 50 | ||
| 48 | #include "../kselftest_harness.h" | 51 | #include "../kselftest_harness.h" |
| 49 | 52 | ||
| @@ -133,6 +136,10 @@ struct seccomp_data { | |||
| 133 | #define SECCOMP_GET_ACTION_AVAIL 2 | 136 | #define SECCOMP_GET_ACTION_AVAIL 2 |
| 134 | #endif | 137 | #endif |
| 135 | 138 | ||
| 139 | #ifndef SECCOMP_GET_NOTIF_SIZES | ||
| 140 | #define SECCOMP_GET_NOTIF_SIZES 3 | ||
| 141 | #endif | ||
| 142 | |||
| 136 | #ifndef SECCOMP_FILTER_FLAG_TSYNC | 143 | #ifndef SECCOMP_FILTER_FLAG_TSYNC |
| 137 | #define SECCOMP_FILTER_FLAG_TSYNC (1UL << 0) | 144 | #define SECCOMP_FILTER_FLAG_TSYNC (1UL << 0) |
| 138 | #endif | 145 | #endif |
| @@ -154,6 +161,44 @@ struct seccomp_metadata { | |||
| 154 | }; | 161 | }; |
| 155 | #endif | 162 | #endif |
| 156 | 163 | ||
| 164 | #ifndef SECCOMP_FILTER_FLAG_NEW_LISTENER | ||
| 165 | #define SECCOMP_FILTER_FLAG_NEW_LISTENER (1UL << 3) | ||
| 166 | |||
| 167 | #define SECCOMP_RET_USER_NOTIF 0x7fc00000U | ||
| 168 | |||
| 169 | #define SECCOMP_IOC_MAGIC '!' | ||
| 170 | #define SECCOMP_IO(nr) _IO(SECCOMP_IOC_MAGIC, nr) | ||
| 171 | #define SECCOMP_IOR(nr, type) _IOR(SECCOMP_IOC_MAGIC, nr, type) | ||
| 172 | #define SECCOMP_IOW(nr, type) _IOW(SECCOMP_IOC_MAGIC, nr, type) | ||
| 173 | #define SECCOMP_IOWR(nr, type) _IOWR(SECCOMP_IOC_MAGIC, nr, type) | ||
| 174 | |||
| 175 | /* Flags for seccomp notification fd ioctl. */ | ||
| 176 | #define SECCOMP_IOCTL_NOTIF_RECV SECCOMP_IOWR(0, struct seccomp_notif) | ||
| 177 | #define SECCOMP_IOCTL_NOTIF_SEND SECCOMP_IOWR(1, \ | ||
| 178 | struct seccomp_notif_resp) | ||
| 179 | #define SECCOMP_IOCTL_NOTIF_ID_VALID SECCOMP_IOR(2, __u64) | ||
| 180 | |||
| 181 | struct seccomp_notif { | ||
| 182 | __u64 id; | ||
| 183 | __u32 pid; | ||
| 184 | __u32 flags; | ||
| 185 | struct seccomp_data data; | ||
| 186 | }; | ||
| 187 | |||
| 188 | struct seccomp_notif_resp { | ||
| 189 | __u64 id; | ||
| 190 | __s64 val; | ||
| 191 | __s32 error; | ||
| 192 | __u32 flags; | ||
| 193 | }; | ||
| 194 | |||
| 195 | struct seccomp_notif_sizes { | ||
| 196 | __u16 seccomp_notif; | ||
| 197 | __u16 seccomp_notif_resp; | ||
| 198 | __u16 seccomp_data; | ||
| 199 | }; | ||
| 200 | #endif | ||
| 201 | |||
| 157 | #ifndef seccomp | 202 | #ifndef seccomp |
| 158 | int seccomp(unsigned int op, unsigned int flags, void *args) | 203 | int seccomp(unsigned int op, unsigned int flags, void *args) |
| 159 | { | 204 | { |
| @@ -2077,7 +2122,8 @@ TEST(detect_seccomp_filter_flags) | |||
| 2077 | { | 2122 | { |
| 2078 | unsigned int flags[] = { SECCOMP_FILTER_FLAG_TSYNC, | 2123 | unsigned int flags[] = { SECCOMP_FILTER_FLAG_TSYNC, |
| 2079 | SECCOMP_FILTER_FLAG_LOG, | 2124 | SECCOMP_FILTER_FLAG_LOG, |
| 2080 | SECCOMP_FILTER_FLAG_SPEC_ALLOW }; | 2125 | SECCOMP_FILTER_FLAG_SPEC_ALLOW, |
| 2126 | SECCOMP_FILTER_FLAG_NEW_LISTENER }; | ||
| 2081 | unsigned int flag, all_flags; | 2127 | unsigned int flag, all_flags; |
| 2082 | int i; | 2128 | int i; |
| 2083 | long ret; | 2129 | long ret; |
| @@ -2933,6 +2979,403 @@ skip: | |||
| 2933 | ASSERT_EQ(0, kill(pid, SIGKILL)); | 2979 | ASSERT_EQ(0, kill(pid, SIGKILL)); |
| 2934 | } | 2980 | } |
| 2935 | 2981 | ||
| 2982 | static int user_trap_syscall(int nr, unsigned int flags) | ||
| 2983 | { | ||
| 2984 | struct sock_filter filter[] = { | ||
| 2985 | BPF_STMT(BPF_LD+BPF_W+BPF_ABS, | ||
| 2986 | offsetof(struct seccomp_data, nr)), | ||
| 2987 | BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, nr, 0, 1), | ||
| 2988 | BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_USER_NOTIF), | ||
| 2989 | BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW), | ||
| 2990 | }; | ||
| 2991 | |||
| 2992 | struct sock_fprog prog = { | ||
| 2993 | .len = (unsigned short)ARRAY_SIZE(filter), | ||
| 2994 | .filter = filter, | ||
| 2995 | }; | ||
| 2996 | |||
| 2997 | return seccomp(SECCOMP_SET_MODE_FILTER, flags, &prog); | ||
| 2998 | } | ||
| 2999 | |||
| 3000 | #define USER_NOTIF_MAGIC 116983961184613L | ||
| 3001 | TEST(user_notification_basic) | ||
| 3002 | { | ||
| 3003 | pid_t pid; | ||
| 3004 | long ret; | ||
| 3005 | int status, listener; | ||
| 3006 | struct seccomp_notif req = {}; | ||
| 3007 | struct seccomp_notif_resp resp = {}; | ||
| 3008 | struct pollfd pollfd; | ||
| 3009 | |||
| 3010 | struct sock_filter filter[] = { | ||
| 3011 | BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), | ||
| 3012 | }; | ||
| 3013 | struct sock_fprog prog = { | ||
| 3014 | .len = (unsigned short)ARRAY_SIZE(filter), | ||
| 3015 | .filter = filter, | ||
| 3016 | }; | ||
| 3017 | |||
| 3018 | pid = fork(); | ||
| 3019 | ASSERT_GE(pid, 0); | ||
| 3020 | |||
| 3021 | /* Check that we get -ENOSYS with no listener attached */ | ||
| 3022 | if (pid == 0) { | ||
| 3023 | if (user_trap_syscall(__NR_getpid, 0) < 0) | ||
| 3024 | exit(1); | ||
| 3025 | ret = syscall(__NR_getpid); | ||
| 3026 | exit(ret >= 0 || errno != ENOSYS); | ||
| 3027 | } | ||
| 3028 | |||
| 3029 | EXPECT_EQ(waitpid(pid, &status, 0), pid); | ||
| 3030 | EXPECT_EQ(true, WIFEXITED(status)); | ||
| 3031 | EXPECT_EQ(0, WEXITSTATUS(status)); | ||
| 3032 | |||
| 3033 | /* Add some no-op filters so for grins. */ | ||
| 3034 | EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); | ||
| 3035 | EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); | ||
| 3036 | EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); | ||
| 3037 | EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); | ||
| 3038 | |||
| 3039 | /* Check that the basic notification machinery works */ | ||
| 3040 | listener = user_trap_syscall(__NR_getpid, | ||
| 3041 | SECCOMP_FILTER_FLAG_NEW_LISTENER); | ||
| 3042 | EXPECT_GE(listener, 0); | ||
| 3043 | |||
| 3044 | /* Installing a second listener in the chain should EBUSY */ | ||
| 3045 | EXPECT_EQ(user_trap_syscall(__NR_getpid, | ||
| 3046 | SECCOMP_FILTER_FLAG_NEW_LISTENER), | ||
| 3047 | -1); | ||
| 3048 | EXPECT_EQ(errno, EBUSY); | ||
| 3049 | |||
| 3050 | pid = fork(); | ||
| 3051 | ASSERT_GE(pid, 0); | ||
| 3052 | |||
| 3053 | if (pid == 0) { | ||
| 3054 | ret = syscall(__NR_getpid); | ||
| 3055 | exit(ret != USER_NOTIF_MAGIC); | ||
| 3056 | } | ||
| 3057 | |||
| 3058 | pollfd.fd = listener; | ||
| 3059 | pollfd.events = POLLIN | POLLOUT; | ||
| 3060 | |||
| 3061 | EXPECT_GT(poll(&pollfd, 1, -1), 0); | ||
| 3062 | EXPECT_EQ(pollfd.revents, POLLIN); | ||
