| Index: sandbox/linux/seccomp-bpf/demo.cc
|
| diff --git a/sandbox/linux/seccomp-bpf/demo.cc b/sandbox/linux/seccomp-bpf/demo.cc
|
| deleted file mode 100644
|
| index d9fd3423eeef5410d38f88137c1ab10ed12a5df8..0000000000000000000000000000000000000000
|
| --- a/sandbox/linux/seccomp-bpf/demo.cc
|
| +++ /dev/null
|
| @@ -1,542 +0,0 @@
|
| -// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -
|
| -#include <errno.h>
|
| -#include <fcntl.h>
|
| -#include <linux/unistd.h>
|
| -#include <netinet/in.h>
|
| -#include <netinet/tcp.h>
|
| -#include <netinet/udp.h>
|
| -#include <pthread.h>
|
| -#include <signal.h>
|
| -#include <stdarg.h>
|
| -#include <stdio.h>
|
| -#include <stdlib.h>
|
| -#include <string.h>
|
| -#include <sys/ioctl.h>
|
| -#include <sys/ipc.h>
|
| -#include <sys/mman.h>
|
| -#include <sys/prctl.h>
|
| -#include <sys/resource.h>
|
| -#include <sys/shm.h>
|
| -#include <sys/socket.h>
|
| -#include <sys/time.h>
|
| -#include <sys/types.h>
|
| -#include <time.h>
|
| -#include <unistd.h>
|
| -
|
| -#include "base/macros.h"
|
| -#include "base/posix/eintr_wrapper.h"
|
| -#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
|
| -#include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h"
|
| -#include "sandbox/linux/services/linux_syscalls.h"
|
| -
|
| -using sandbox::ErrorCode;
|
| -using sandbox::SandboxBPF;
|
| -using sandbox::SandboxBPFPolicy;
|
| -using sandbox::arch_seccomp_data;
|
| -
|
| -#define ERR EPERM
|
| -
|
| -// We don't expect our sandbox to do anything useful yet. So, we will fail
|
| -// almost immediately. For now, force the code to continue running. The
|
| -// following line should be removed as soon as the sandbox is starting to
|
| -// actually enforce restrictions in a meaningful way:
|
| -#define _exit(x) do { } while (0)
|
| -
|
| -namespace {
|
| -
|
| -bool SendFds(int transport, const void *buf, size_t len, ...) {
|
| - int count = 0;
|
| - va_list ap;
|
| - va_start(ap, len);
|
| - while (va_arg(ap, int) >= 0) {
|
| - ++count;
|
| - }
|
| - va_end(ap);
|
| - if (!count) {
|
| - return false;
|
| - }
|
| - char cmsg_buf[CMSG_SPACE(count*sizeof(int))];
|
| - memset(cmsg_buf, 0, sizeof(cmsg_buf));
|
| - struct iovec iov[2] = { { 0 } };
|
| - struct msghdr msg = { 0 };
|
| - int dummy = 0;
|
| - iov[0].iov_base = &dummy;
|
| - iov[0].iov_len = sizeof(dummy);
|
| - if (buf && len > 0) {
|
| - iov[1].iov_base = const_cast<void *>(buf);
|
| - iov[1].iov_len = len;
|
| - }
|
| - msg.msg_iov = iov;
|
| - msg.msg_iovlen = (buf && len > 0) ? 2 : 1;
|
| - msg.msg_control = cmsg_buf;
|
| - msg.msg_controllen = CMSG_LEN(count*sizeof(int));
|
| - struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
| - cmsg->cmsg_level = SOL_SOCKET;
|
| - cmsg->cmsg_type = SCM_RIGHTS;
|
| - cmsg->cmsg_len = CMSG_LEN(count*sizeof(int));
|
| - va_start(ap, len);
|
| - for (int i = 0, fd; (fd = va_arg(ap, int)) >= 0; ++i) {
|
| - (reinterpret_cast<int *>(CMSG_DATA(cmsg)))[i] = fd;
|
| - }
|
| - return sendmsg(transport, &msg, 0) ==
|
| - static_cast<ssize_t>(sizeof(dummy) + ((buf && len > 0) ? len : 0));
|
| -}
|
| -
|
| -bool GetFds(int transport, void *buf, size_t *len, ...) {
|
| - int count = 0;
|
| - va_list ap;
|
| - va_start(ap, len);
|
| - for (int *fd; (fd = va_arg(ap, int *)) != NULL; ++count) {
|
| - *fd = -1;
|
| - }
|
| - va_end(ap);
|
| - if (!count) {
|
| - return false;
|
| - }
|
| - char cmsg_buf[CMSG_SPACE(count*sizeof(int))];
|
| - memset(cmsg_buf, 0, sizeof(cmsg_buf));
|
| - struct iovec iov[2] = { { 0 } };
|
| - struct msghdr msg = { 0 };
|
| - int err;
|
| - iov[0].iov_base = &err;
|
| - iov[0].iov_len = sizeof(int);
|
| - if (buf && len && *len > 0) {
|
| - iov[1].iov_base = buf;
|
| - iov[1].iov_len = *len;
|
| - }
|
| - msg.msg_iov = iov;
|
| - msg.msg_iovlen = (buf && len && *len > 0) ? 2 : 1;
|
| - msg.msg_control = cmsg_buf;
|
| - msg.msg_controllen = CMSG_LEN(count*sizeof(int));
|
| - ssize_t bytes = recvmsg(transport, &msg, 0);
|
| - if (len) {
|
| - *len = bytes > static_cast<int>(sizeof(int)) ? bytes - sizeof(int) : 0;
|
| - }
|
| - if (bytes != static_cast<ssize_t>(sizeof(int) + iov[1].iov_len)) {
|
| - if (bytes >= 0) {
|
| - errno = 0;
|
| - }
|
| - return false;
|
| - }
|
| - if (err) {
|
| - // "err" is the first four bytes of the payload. If these are non-zero,
|
| - // the sender on the other side of the socketpair sent us an errno value.
|
| - // We don't expect to get any file handles in this case.
|
| - errno = err;
|
| - return false;
|
| - }
|
| - struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
| - if ((msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) ||
|
| - !cmsg ||
|
| - cmsg->cmsg_level != SOL_SOCKET ||
|
| - cmsg->cmsg_type != SCM_RIGHTS ||
|
| - cmsg->cmsg_len != CMSG_LEN(count*sizeof(int))) {
|
| - errno = EBADF;
|
| - return false;
|
| - }
|
| - va_start(ap, len);
|
| - for (int *fd, i = 0; (fd = va_arg(ap, int *)) != NULL; ++i) {
|
| - *fd = (reinterpret_cast<int *>(CMSG_DATA(cmsg)))[i];
|
| - }
|
| - va_end(ap);
|
| - return true;
|
| -}
|
| -
|
| -
|
| -// POSIX doesn't define any async-signal safe function for converting
|
| -// an integer to ASCII. We'll have to define our own version.
|
| -// itoa_r() converts a (signed) integer to ASCII. It returns "buf", if the
|
| -// conversion was successful or NULL otherwise. It never writes more than "sz"
|
| -// bytes. Output will be truncated as needed, and a NUL character is always
|
| -// appended.
|
| -char *itoa_r(int i, char *buf, size_t sz) {
|
| - // Make sure we can write at least one NUL byte.
|
| - size_t n = 1;
|
| - if (n > sz) {
|
| - return NULL;
|
| - }
|
| -
|
| - // Handle negative numbers.
|
| - char *start = buf;
|
| - int minint = 0;
|
| - if (i < 0) {
|
| - // Make sure we can write the '-' character.
|
| - if (++n > sz) {
|
| - *start = '\000';
|
| - return NULL;
|
| - }
|
| - *start++ = '-';
|
| -
|
| - // Turn our number positive.
|
| - if (i == -i) {
|
| - // The lowest-most negative integer needs special treatment.
|
| - minint = 1;
|
| - i = -(i + 1);
|
| - } else {
|
| - // "Normal" negative numbers are easy.
|
| - i = -i;
|
| - }
|
| - }
|
| -
|
| - // Loop until we have converted the entire number. Output at least one
|
| - // character (i.e. '0').
|
| - char *ptr = start;
|
| - do {
|
| - // Make sure there is still enough space left in our output buffer.
|
| - if (++n > sz) {
|
| - buf = NULL;
|
| - goto truncate;
|
| - }
|
| -
|
| - // Output the next digit and (if necessary) compensate for the lowest-most
|
| - // negative integer needing special treatment. This works because, no
|
| - // matter the bit width of the integer, the lowest-most integer always ends
|
| - // in 2, 4, 6, or 8.
|
| - *ptr++ = i%10 + '0' + minint;
|
| - minint = 0;
|
| - i /= 10;
|
| - } while (i);
|
| - truncate: // Terminate the output with a NUL character.
|
| - *ptr = '\000';
|
| -
|
| - // Conversion to ASCII actually resulted in the digits being in reverse
|
| - // order. We can't easily generate them in forward order, as we can't tell
|
| - // the number of characters needed until we are done converting.
|
| - // So, now, we reverse the string (except for the possible "-" sign).
|
| - while (--ptr > start) {
|
| - char ch = *ptr;
|
| - *ptr = *start;
|
| - *start++ = ch;
|
| - }
|
| - return buf;
|
| -}
|
| -
|
| -// This handler gets called, whenever we encounter a system call that we
|
| -// don't recognize explicitly. For the purposes of this program, we just
|
| -// log the system call and then deny it. More elaborate sandbox policies
|
| -// might try to evaluate the system call in user-space, instead.
|
| -// The only notable complication is that this function must be async-signal
|
| -// safe. This restricts the libary functions that we can call.
|
| -intptr_t DefaultHandler(const struct arch_seccomp_data& data, void *) {
|
| - static const char msg0[] = "Disallowed system call #";
|
| - static const char msg1[] = "\n";
|
| - char buf[sizeof(msg0) - 1 + 25 + sizeof(msg1)];
|
| -
|
| - *buf = '\000';
|
| - strncat(buf, msg0, sizeof(buf) - 1);
|
| -
|
| - char *ptr = strrchr(buf, '\000');
|
| - itoa_r(data.nr, ptr, sizeof(buf) - (ptr - buf));
|
| -
|
| - ptr = strrchr(ptr, '\000');
|
| - strncat(ptr, msg1, sizeof(buf) - (ptr - buf));
|
| -
|
| - ptr = strrchr(ptr, '\000');
|
| - if (HANDLE_EINTR(write(2, buf, ptr - buf))) { }
|
| -
|
| - return -ERR;
|
| -}
|
| -
|
| -class DemoPolicy : public SandboxBPFPolicy {
|
| - public:
|
| - DemoPolicy() {}
|
| - virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
|
| - int sysno) const OVERRIDE;
|
| -
|
| - private:
|
| - DISALLOW_COPY_AND_ASSIGN(DemoPolicy);
|
| -};
|
| -
|
| -ErrorCode DemoPolicy::EvaluateSyscall(SandboxBPF* sandbox, int sysno) const {
|
| - switch (sysno) {
|
| -#if defined(__NR_accept)
|
| - case __NR_accept: case __NR_accept4:
|
| -#endif
|
| - case __NR_alarm:
|
| - case __NR_brk:
|
| - case __NR_clock_gettime:
|
| - case __NR_close:
|
| - case __NR_dup: case __NR_dup2:
|
| - case __NR_epoll_create: case __NR_epoll_ctl: case __NR_epoll_wait:
|
| - case __NR_exit: case __NR_exit_group:
|
| - case __NR_fcntl:
|
| -#if defined(__NR_fcntl64)
|
| - case __NR_fcntl64:
|
| -#endif
|
| - case __NR_fdatasync:
|
| - case __NR_fstat:
|
| -#if defined(__NR_fstat64)
|
| - case __NR_fstat64:
|
| -#endif
|
| - case __NR_ftruncate:
|
| - case __NR_futex:
|
| - case __NR_getdents: case __NR_getdents64:
|
| - case __NR_getegid:
|
| -#if defined(__NR_getegid32)
|
| - case __NR_getegid32:
|
| -#endif
|
| - case __NR_geteuid:
|
| -#if defined(__NR_geteuid32)
|
| - case __NR_geteuid32:
|
| -#endif
|
| - case __NR_getgid:
|
| -#if defined(__NR_getgid32)
|
| - case __NR_getgid32:
|
| -#endif
|
| - case __NR_getitimer: case __NR_setitimer:
|
| -#if defined(__NR_getpeername)
|
| - case __NR_getpeername:
|
| -#endif
|
| - case __NR_getpid: case __NR_gettid:
|
| -#if defined(__NR_getsockname)
|
| - case __NR_getsockname:
|
| -#endif
|
| - case __NR_gettimeofday:
|
| - case __NR_getuid:
|
| -#if defined(__NR_getuid32)
|
| - case __NR_getuid32:
|
| -#endif
|
| -#if defined(__NR__llseek)
|
| - case __NR__llseek:
|
| -#endif
|
| - case __NR_lseek:
|
| - case __NR_nanosleep:
|
| - case __NR_pipe: case __NR_pipe2:
|
| - case __NR_poll:
|
| - case __NR_pread64: case __NR_preadv:
|
| - case __NR_pwrite64: case __NR_pwritev:
|
| - case __NR_read: case __NR_readv:
|
| - case __NR_restart_syscall:
|
| - case __NR_set_robust_list:
|
| - case __NR_rt_sigaction:
|
| -#if defined(__NR_sigaction)
|
| - case __NR_sigaction:
|
| -#endif
|
| -#if defined(__NR_signal)
|
| - case __NR_signal:
|
| -#endif
|
| - case __NR_rt_sigprocmask:
|
| -#if defined(__NR_sigprocmask)
|
| - case __NR_sigprocmask:
|
| -#endif
|
| -#if defined(__NR_shutdown)
|
| - case __NR_shutdown:
|
| -#endif
|
| - case __NR_rt_sigreturn:
|
| -#if defined(__NR_sigreturn)
|
| - case __NR_sigreturn:
|
| -#endif
|
| -#if defined(__NR_socketpair)
|
| - case __NR_socketpair:
|
| -#endif
|
| - case __NR_time:
|
| - case __NR_uname:
|
| - case __NR_write: case __NR_writev:
|
| - return ErrorCode(ErrorCode::ERR_ALLOWED);
|
| -
|
| - case __NR_prctl:
|
| - // Allow PR_SET_DUMPABLE and PR_GET_DUMPABLE. Do not allow anything else.
|
| - return sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL,
|
| - PR_SET_DUMPABLE,
|
| - ErrorCode(ErrorCode::ERR_ALLOWED),
|
| - sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL,
|
| - PR_GET_DUMPABLE,
|
| - ErrorCode(ErrorCode::ERR_ALLOWED),
|
| - sandbox->Trap(DefaultHandler, NULL)));
|
| -
|
| - // The following system calls are temporarily permitted. This must be
|
| - // tightened later. But we currently don't implement enough of the sandboxing
|
| - // API to do so.
|
| - // As is, this sandbox isn't exactly safe :-/
|
| -#if defined(__NR_sendmsg)
|
| - case __NR_sendmsg: case __NR_sendto:
|
| - case __NR_recvmsg: case __NR_recvfrom:
|
| - case __NR_getsockopt: case __NR_setsockopt:
|
| -#elif defined(__NR_socketcall)
|
| - case __NR_socketcall:
|
| -#endif
|
| -#if defined(__NR_shmat)
|
| - case __NR_shmat: case __NR_shmctl: case __NR_shmdt: case __NR_shmget:
|
| -#elif defined(__NR_ipc)
|
| - case __NR_ipc:
|
| -#endif
|
| -#if defined(__NR_mmap2)
|
| - case __NR_mmap2:
|
| -#else
|
| - case __NR_mmap:
|
| -#endif
|
| -#if defined(__NR_ugetrlimit)
|
| - case __NR_ugetrlimit:
|
| -#endif
|
| - case __NR_getrlimit:
|
| - case __NR_ioctl:
|
| - case __NR_clone:
|
| - case __NR_munmap: case __NR_mprotect: case __NR_madvise:
|
| - case __NR_remap_file_pages:
|
| - return ErrorCode(ErrorCode::ERR_ALLOWED);
|
| -
|
| - // Everything that isn't explicitly allowed is denied.
|
| - default:
|
| - return sandbox->Trap(DefaultHandler, NULL);
|
| - }
|
| -}
|
| -
|
| -void *ThreadFnc(void *arg) {
|
| - return arg;
|
| -}
|
| -
|
| -void *SendmsgStressThreadFnc(void *arg) {
|
| - if (arg) { }
|
| - static const int repetitions = 100;
|
| - static const int kNumFds = 3;
|
| - for (int rep = 0; rep < repetitions; ++rep) {
|
| - int fds[2 + kNumFds];
|
| - if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) {
|
| - perror("socketpair()");
|
| - _exit(1);
|
| - }
|
| - size_t len = 4;
|
| - char buf[4];
|
| - if (!SendFds(fds[0], "test", 4, fds[1], fds[1], fds[1], -1) ||
|
| - !GetFds(fds[1], buf, &len, fds+2, fds+3, fds+4, NULL) ||
|
| - len != 4 ||
|
| - memcmp(buf, "test", len) ||
|
| - write(fds[2], "demo", 4) != 4 ||
|
| - read(fds[0], buf, 4) != 4 ||
|
| - memcmp(buf, "demo", 4)) {
|
| - perror("sending/receiving of fds");
|
| - _exit(1);
|
| - }
|
| - for (int i = 0; i < 2+kNumFds; ++i) {
|
| - if (close(fds[i])) {
|
| - perror("close");
|
| - _exit(1);
|
| - }
|
| - }
|
| - }
|
| - return NULL;
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -int main(int argc, char *argv[]) {
|
| - if (argc) { }
|
| - if (argv) { }
|
| - int proc_fd = open("/proc", O_RDONLY|O_DIRECTORY);
|
| - if (SandboxBPF::SupportsSeccompSandbox(proc_fd) !=
|
| - SandboxBPF::STATUS_AVAILABLE) {
|
| - perror("sandbox");
|
| - _exit(1);
|
| - }
|
| - SandboxBPF sandbox;
|
| - sandbox.set_proc_fd(proc_fd);
|
| - sandbox.SetSandboxPolicy(new DemoPolicy());
|
| - if (!sandbox.StartSandbox(SandboxBPF::PROCESS_SINGLE_THREADED)) {
|
| - fprintf(stderr, "StartSandbox() failed");
|
| - _exit(1);
|
| - }
|
| -
|
| - // Check that we can create threads
|
| - pthread_t thr;
|
| - if (!pthread_create(&thr, NULL, ThreadFnc,
|
| - reinterpret_cast<void *>(0x1234))) {
|
| - void *ret;
|
| - pthread_join(thr, &ret);
|
| - if (ret != reinterpret_cast<void *>(0x1234)) {
|
| - perror("clone() failed");
|
| - _exit(1);
|
| - }
|
| - } else {
|
| - perror("clone() failed");
|
| - _exit(1);
|
| - }
|
| -
|
| - // Check that we handle restart_syscall() without dieing. This is a little
|
| - // tricky to trigger. And I can't think of a good way to verify whether it
|
| - // actually executed.
|
| - signal(SIGALRM, SIG_IGN);
|
| - const struct itimerval tv = { { 0, 0 }, { 0, 5*1000 } };
|
| - const struct timespec tmo = { 0, 100*1000*1000 };
|
| - setitimer(ITIMER_REAL, &tv, NULL);
|
| - nanosleep(&tmo, NULL);
|
| -
|
| - // Check that we can query the size of the stack, but that all other
|
| - // calls to getrlimit() fail.
|
| - if (((errno = 0), !getrlimit(RLIMIT_STACK, NULL)) || errno != EFAULT ||
|
| - ((errno = 0), !getrlimit(RLIMIT_CORE, NULL)) || errno != ERR) {
|
| - perror("getrlimit()");
|
| - _exit(1);
|
| - }
|
| -
|
| - // Check that we can query TCGETS and TIOCGWINSZ, but no other ioctls().
|
| - if (((errno = 0), !ioctl(2, TCGETS, NULL)) || errno != EFAULT ||
|
| - ((errno = 0), !ioctl(2, TIOCGWINSZ, NULL)) || errno != EFAULT ||
|
| - ((errno = 0), !ioctl(2, TCSETS, NULL)) || errno != ERR) {
|
| - perror("ioctl()");
|
| - _exit(1);
|
| - }
|
| -
|
| - // Check that prctl() can manipulate the dumpable flag, but nothing else.
|
| - if (((errno = 0), !prctl(PR_GET_DUMPABLE)) || errno ||
|
| - ((errno = 0), prctl(PR_SET_DUMPABLE, 1)) || errno ||
|
| - ((errno = 0), !prctl(PR_SET_SECCOMP, 0)) || errno != ERR) {
|
| - perror("prctl()");
|
| - _exit(1);
|
| - }
|
| -
|
| - // Check that we can send and receive file handles.
|
| - int fds[3];
|
| - if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) {
|
| - perror("socketpair()");
|
| - _exit(1);
|
| - }
|
| - size_t len = 4;
|
| - char buf[4];
|
| - if (!SendFds(fds[0], "test", 4, fds[1], -1) ||
|
| - !GetFds(fds[1], buf, &len, fds+2, NULL) ||
|
| - len != 4 ||
|
| - memcmp(buf, "test", len) ||
|
| - write(fds[2], "demo", 4) != 4 ||
|
| - read(fds[0], buf, 4) != 4 ||
|
| - memcmp(buf, "demo", 4) ||
|
| - close(fds[0]) ||
|
| - close(fds[1]) ||
|
| - close(fds[2])) {
|
| - perror("sending/receiving of fds");
|
| - _exit(1);
|
| - }
|
| -
|
| - // Check whether SysV IPC works.
|
| - int shmid;
|
| - void *addr;
|
| - if ((shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT|0600)) < 0 ||
|
| - (addr = shmat(shmid, NULL, 0)) == reinterpret_cast<void *>(-1) ||
|
| - shmdt(addr) ||
|
| - shmctl(shmid, IPC_RMID, NULL)) {
|
| - perror("sysv IPC");
|
| - _exit(1);
|
| - }
|
| -
|
| - // Print a message so that the user can see the sandbox is activated.
|
| - time_t tm = time(NULL);
|
| - printf("Sandbox has been started at %s", ctime(&tm));
|
| -
|
| - // Stress-test the sendmsg() code
|
| - static const int kSendmsgStressNumThreads = 10;
|
| - pthread_t sendmsgStressThreads[kSendmsgStressNumThreads];
|
| - for (int i = 0; i < kSendmsgStressNumThreads; ++i) {
|
| - if (pthread_create(sendmsgStressThreads + i, NULL,
|
| - SendmsgStressThreadFnc, NULL)) {
|
| - perror("pthread_create");
|
| - _exit(1);
|
| - }
|
| - }
|
| - for (int i = 0; i < kSendmsgStressNumThreads; ++i) {
|
| - pthread_join(sendmsgStressThreads[i], NULL);
|
| - }
|
| -
|
| - return 0;
|
| -}
|
|
|