Merge branch 'next-seccomp' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris...
[sfrench/cifs-2.6.git] / tools / testing / selftests / seccomp / seccomp_bpf.c
index c9a2abf8be1b38d0ea3d0ee14019b6078ca95af2..067cb4607d6cd519793b4ea2d26a5ef96f446e89 100644 (file)
@@ -5,6 +5,7 @@
  * Test code for seccomp bpf.
  */
 
+#define _GNU_SOURCE
 #include <sys/types.h>
 
 /*
 #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;
@@ -2938,6 +2984,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