diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2016-10-18 21:07:40 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2016-11-19 10:00:36 +0100 |
| commit | 59f7c210d0584164a821bde6686debe169660f30 (patch) | |
| tree | ffb942e9f3af91fb6e6fb26ca1ae4e48f9a54962 /executor/executor.cc | |
| parent | dbc7ff38051cba31976238c743b1d8c53ce64470 (diff) | |
repro: factor out of syz-repro tool
Factor out repro logic from syz-repro tool,
so that it can be used in syz-manager.
Also, support sandboxes in code generated by
csoure. This is required to reproduce crashes
that require e.g. namespace sandbox.
Diffstat (limited to 'executor/executor.cc')
| -rw-r--r-- | executor/executor.cc | 311 |
1 files changed, 3 insertions, 308 deletions
diff --git a/executor/executor.cc b/executor/executor.cc index 972a80d24..b155c578d 100644 --- a/executor/executor.cc +++ b/executor/executor.cc @@ -2,18 +2,14 @@ // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. #include <algorithm> -#include <dirent.h> #include <errno.h> #include <fcntl.h> -#include <grp.h> #include <limits.h> -#include <linux/capability.h> #include <linux/futex.h> #include <linux/reboot.h> #include <pthread.h> #include <setjmp.h> #include <signal.h> -#include <stdarg.h> #include <stddef.h> #include <stdint.h> #include <stdio.h> @@ -21,10 +17,8 @@ #include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> -#include <sys/mount.h> #include <sys/prctl.h> #include <sys/reboot.h> -#include <sys/resource.h> #include <sys/stat.h> #include <sys/syscall.h> #include <sys/time.h> @@ -35,6 +29,7 @@ #include "syscalls.h" +#define SYZ_EXECUTOR #include "common.h" #define KCOV_INIT_TRACE _IOR('c', 1, unsigned long long) @@ -61,10 +56,6 @@ const uint64_t arg_const = 0; const uint64_t arg_result = 1; const uint64_t arg_data = 2; -const int kFailStatus = 67; -const int kErrorStatus = 68; -const int kRetryStatus = 69; - // We use the default value instead of results of failed syscalls. // -1 is an invalid fd and an invalid address and deterministic, // so good enough for our purposes. @@ -76,7 +67,6 @@ enum sandbox_type { sandbox_namespace, }; -bool flag_debug; bool flag_cover; bool flag_threaded; bool flag_collide; @@ -90,8 +80,6 @@ uint32_t* output_pos; int completed; int running; bool collide; -int real_uid; -int real_gid; struct res_t { bool executed; @@ -121,18 +109,7 @@ struct thread_t { }; thread_t threads[kMaxThreads]; -char sandbox_stack[1 << 20]; - -__attribute__((noreturn)) void fail(const char* msg, ...); -__attribute__((noreturn)) void error(const char* msg, ...); -__attribute__((noreturn)) void exitf(const char* msg, ...); -void debug(const char* msg, ...); -int sandbox_proc(void* arg); -int do_sandbox_none(); -int do_sandbox_setuid(); -int do_sandbox_namespace(); -void sandbox_common(); -void loop(); + void execute_one(); uint64_t read_input(uint64_t** input_posp, bool peek = false); uint64_t read_arg(uint64_t** input_posp); @@ -146,14 +123,11 @@ void handle_completion(thread_t* th); void thread_create(thread_t* th, int id); void* worker_thread(void* arg); bool write_file(const char* file, const char* what, ...); -void remove_dir(const char* dir); -uint64_t current_time_ms(); void cover_open(); void cover_enable(thread_t* th); void cover_reset(thread_t* th); uint64_t cover_read(thread_t* th); uint64_t cover_dedup(thread_t* th, uint64_t n); -void handle_segv(int sig, siginfo_t* info, void* uctx); int main(int argc, char** argv) { @@ -189,16 +163,7 @@ int main(int argc, char** argv) flag_collide = false; cover_open(); - - // Don't need that SIGCANCEL/SIGSETXID glibc stuff. - // SIGCANCEL sent to main thread causes it to exit - // without bringing down the whole group. - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = SIG_IGN; - syscall(SYS_rt_sigaction, 0x20, &sa, NULL, 8); - syscall(SYS_rt_sigaction, 0x21, &sa, NULL, 8); - install_segv_handler(); + setup_main_process(); int pid = -1; switch (flag_sandbox) { @@ -303,125 +268,6 @@ void loop() } } -int do_sandbox_none() -{ - int pid = fork(); - if (pid) - return pid; - loop(); - exit(1); -} - -int do_sandbox_setuid() -{ - int pid = fork(); - if (pid) - return pid; - - sandbox_common(); - - const int nobody = 65534; - if (setgroups(0, NULL)) - fail("failed to setgroups"); - // glibc versions do not we want -- they force all threads to setuid. - // We want to preserve the thread above as root. - if (syscall(SYS_setresgid, nobody, nobody, nobody)) - fail("failed to setresgid"); - if (syscall(SYS_setresuid, nobody, nobody, nobody)) - fail("failed to setresuid"); - - loop(); - exit(1); -} - -int do_sandbox_namespace() -{ - real_uid = getuid(); - real_gid = getgid(); - return clone(sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 8], - CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET, NULL); -} - -void sandbox_common() -{ - prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); - setpgrp(); - setsid(); - - struct rlimit rlim; - rlim.rlim_cur = rlim.rlim_max = 128 << 20; - setrlimit(RLIMIT_AS, &rlim); - rlim.rlim_cur = rlim.rlim_max = 1 << 20; - setrlimit(RLIMIT_FSIZE, &rlim); - rlim.rlim_cur = rlim.rlim_max = 1 << 20; - setrlimit(RLIMIT_STACK, &rlim); - rlim.rlim_cur = rlim.rlim_max = 0; - setrlimit(RLIMIT_CORE, &rlim); - - // CLONE_NEWIPC/CLONE_IO cause EINVAL on some systems, so we do them separately of clone. - unshare(CLONE_NEWNS); - unshare(CLONE_NEWIPC); - unshare(CLONE_IO); -} - -int sandbox_proc(void* arg) -{ - sandbox_common(); - - // /proc/self/setgroups is not present on some systems, ignore error. - write_file("/proc/self/setgroups", "deny"); - if (!write_file("/proc/self/uid_map", "0 %d 1\n", real_uid)) - fail("write of /proc/self/uid_map failed"); - if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid)) - fail("write of /proc/self/gid_map failed"); - - if (mkdir("./syz-tmp", 0777)) - fail("mkdir(syz-tmp) failed"); - if (mount("", "./syz-tmp", "tmpfs", 0, NULL)) - fail("mount(tmpfs) failed"); - if (mkdir("./syz-tmp/newroot", 0777)) - fail("mkdir failed"); - if (mkdir("./syz-tmp/newroot/dev", 0700)) - fail("mkdir failed"); - if (mount("/dev", "./syz-tmp/newroot/dev", NULL, MS_BIND | MS_REC | MS_PRIVATE, NULL)) - fail("mount(dev) failed"); - if (mkdir("./syz-tmp/pivot", 0777)) - fail("mkdir failed"); - if (syscall(SYS_pivot_root, "./syz-tmp", "./syz-tmp/pivot")) { - debug("pivot_root failed\n"); - if (chdir("./syz-tmp")) - fail("chdir failed"); - } else { - if (chdir("/")) - fail("chdir failed"); - if (umount2("./pivot", MNT_DETACH)) - fail("umount failed"); - } - if (chroot("./newroot")) - fail("chroot failed"); - if (chdir("/")) - fail("chdir failed"); - - // Drop CAP_SYS_PTRACE so that test processes can't attach to parent processes. - // Previously it lead to hangs because the loop process stopped due to SIGSTOP. - // Note that a process can always ptrace its direct children, which is enough - // for testing purposes. - __user_cap_header_struct cap_hdr = {}; - __user_cap_data_struct cap_data[2] = {}; - cap_hdr.version = _LINUX_CAPABILITY_VERSION_3; - cap_hdr.pid = getpid(); - if (syscall(SYS_capget, &cap_hdr, &cap_data)) - fail("capget failed"); - cap_data[0].effective &= ~(1 << CAP_SYS_PTRACE); - cap_data[0].permitted &= ~(1 << CAP_SYS_PTRACE); - cap_data[0].inheritable &= ~(1 << CAP_SYS_PTRACE); - if (syscall(SYS_capset, &cap_hdr, &cap_data)) - fail("capset failed"); - - loop(); - exit(1); -} - void execute_one() { retry: @@ -831,154 +677,3 @@ void write_output(uint32_t v) fail("output overflow"); *output_pos++ = v; } - -bool write_file(const char* file, const char* what, ...) -{ - char buf[1024]; - va_list args; - va_start(args, what); - vsnprintf(buf, sizeof(buf), what, args); - va_end(args); - buf[sizeof(buf) - 1] = 0; - int len = strlen(buf); - - int fd = open(file, O_WRONLY | O_CLOEXEC); - if (fd == -1) - return false; - if (write(fd, buf, len) != len) { - close(fd); - return false; - } - close(fd); - return true; -} - -// One does not simply remove a directory. -// There can be mounts, so we need to try to umount. -// Moreover, a mount can be mounted several times, so we need to try to umount in a loop. -// Moreover, after umount a dir can become non-empty again, so we need another loop. -// Moreover, a mount can be re-mounted as read-only and then we will fail to make a dir empty. -void remove_dir(const char* dir) -{ - int iter = 0; -retry: - DIR* dp = opendir(dir); - if (dp == NULL) { - if (errno == EMFILE) { - // This happens when the test process casts prlimit(NOFILE) on us. - // Ideally we somehow prevent test processes from messing with parent processes. - // But full sandboxing is expensive, so let's ignore this error for now. - exitf("opendir(%s) failed due to NOFILE, exiting"); - } - exitf("opendir(%s) failed", dir); - } - while (dirent* ep = readdir(dp)) { - if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0) - continue; - char filename[FILENAME_MAX]; - snprintf(filename, sizeof(filename), "%s/%s", dir, ep->d_name); - struct stat st; - if (lstat(filename, &st)) - exitf("lstat(%s) failed", filename); - if (S_ISDIR(st.st_mode)) { - remove_dir(filename); - continue; - } - for (int i = 0;; i++) { - debug("unlink(%s)\n", filename); - if (unlink(filename) == 0) - break; - if (errno == EROFS) { - debug("ignoring EROFS\n"); - break; - } - if (errno != EBUSY || i > 100) - exitf("unlink(%s) failed", filename); - debug("umount(%s)\n", filename); - if (umount2(filename, MNT_DETACH)) - exitf("umount(%s) failed", filename); - } - } - closedir(dp); - for (int i = 0;; i++) { - debug("rmdir(%s)\n", dir); - if (rmdir(dir) == 0) - break; - if (i < 100) { - if (errno == EROFS) { - debug("ignoring EROFS\n"); - break; - } - if (errno == EBUSY) { - debug("umount(%s)\n", dir); - if (umount2(dir, MNT_DETACH)) - exitf("umount(%s) failed", dir); - continue; - } - if (errno == ENOTEMPTY) { - if (iter < 100) { - iter++; - goto retry; - } - } - } - exitf("rmdir(%s) failed", dir); - } -} - -uint64_t current_time_ms() -{ - timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts)) - fail("clock_gettime failed"); - return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; -} - -// logical error (e.g. invalid input program) -void fail(const char* msg, ...) -{ - int e = errno; - fflush(stdout); - va_list args; - va_start(args, msg); - vfprintf(stderr, msg, args); - va_end(args); - fprintf(stderr, " (errno %d)\n", e); - exit(kFailStatus); -} - -// kernel error (e.g. wrong syscall return value) -void error(const char* msg, ...) -{ - fflush(stdout); - va_list args; - va_start(args, msg); - vfprintf(stderr, msg, args); - va_end(args); - fprintf(stderr, "\n"); - exit(kErrorStatus); -} - -// just exit (e.g. due to temporal ENOMEM error) -void exitf(const char* msg, ...) -{ - int e = errno; - fflush(stdout); - va_list args; - va_start(args, msg); - vfprintf(stderr, msg, args); - va_end(args); - fprintf(stderr, " (errno %d)\n", e); - exit(kRetryStatus); -} - -void debug(const char* msg, ...) -{ - if (!flag_debug) - return; - va_list args; - va_start(args, msg); - vfprintf(stdout, msg, args); - va_end(args); - fflush(stdout); -} |
