diff options
Diffstat (limited to 'executor/executor.cc')
| -rw-r--r-- | executor/executor.cc | 149 |
1 files changed, 105 insertions, 44 deletions
diff --git a/executor/executor.cc b/executor/executor.cc index 4d9ee9f26..52275b185 100644 --- a/executor/executor.cc +++ b/executor/executor.cc @@ -34,8 +34,8 @@ #include "syscalls.h" -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_INIT_TABLE _IOR('c', 2, unsigned long) +#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long long) +#define KCOV_INIT_TABLE _IOR('c', 2, unsigned long long) #define KCOV_ENABLE _IO('c', 100) #define KCOV_DISABLE _IO('c', 101) @@ -67,12 +67,19 @@ const int kRetryStatus = 69; // so good enough for our purposes. const uint64_t default_value = -1; +enum sandbox_type { + sandbox_none, + sandbox_setuid, + sandbox_namespace, +}; + bool flag_debug; bool flag_cover; bool flag_threaded; bool flag_collide; bool flag_deduplicate; -bool flag_drop_privs; +bool flag_sandbox_privs; +sandbox_type flag_sandbox; __attribute__((aligned(64 << 10))) char input_data[kMaxInput]; __attribute__((aligned(64 << 10))) char output_data[kMaxOutput]; @@ -94,7 +101,7 @@ struct thread_t { bool created; int id; pthread_t th; - uintptr_t* cover_data; + uint64_t* cover_data; uint64_t* copyout_pos; int ready; int done; @@ -103,10 +110,10 @@ struct thread_t { int call_index; int call_num; int num_args; - uint64_t args[kMaxArgs]; + uintptr_t args[kMaxArgs]; uint64_t res; uint64_t reserrno; - uintptr_t cover_size; + uint64_t cover_size; int cover_fd; }; @@ -117,7 +124,11 @@ __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(void* arg); +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); @@ -137,8 +148,8 @@ uint64_t current_time_ms(); void cover_open(); void cover_enable(thread_t* th); void cover_reset(thread_t* th); -uintptr_t cover_read(thread_t* th); -uintptr_t cover_dedup(thread_t* th, uintptr_t n); +uint64_t cover_read(thread_t* th); +uint64_t cover_dedup(thread_t* th, uint64_t n); int main(int argc, char** argv) { @@ -165,7 +176,11 @@ int main(int argc, char** argv) flag_threaded = flags & (1 << 2); flag_collide = flags & (1 << 3); flag_deduplicate = flags & (1 << 4); - flag_drop_privs = flags & (1 << 5); + flag_sandbox = sandbox_none; + if (flags & (1 << 5)) + flag_sandbox = sandbox_setuid; + else if (flags & (1 << 6)) + flag_sandbox = sandbox_namespace; if (!flag_threaded) flag_collide = false; @@ -181,17 +196,18 @@ int main(int argc, char** argv) syscall(SYS_rt_sigaction, 0x21, &sa, NULL, 8); int pid = -1; - if (flag_drop_privs) { - real_uid = getuid(); - real_gid = getgid(); - pid = clone(sandbox, &sandbox_stack[sizeof(sandbox_stack) - 8], - CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET, NULL); - } else { - pid = fork(); - if (pid == 0) { - loop(); - exit(1); - } + switch (flag_sandbox) { + case sandbox_none: + pid = do_sandbox_none(); + break; + case sandbox_setuid: + pid = do_sandbox_setuid(); + break; + case sandbox_namespace: + pid = do_sandbox_namespace(); + break; + default: + fail("unknown sandbox type"); } if (pid < 0) fail("clone failed"); @@ -282,7 +298,46 @@ void loop() } } -int sandbox(void* arg) +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(); @@ -299,8 +354,14 @@ int sandbox(void* arg) 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"); @@ -540,7 +601,7 @@ void handle_completion(thread_t* th) write_output(th->cover_size); // Truncate PCs to uint32_t assuming that they fit into 32-bits. // True for x86_64 and arm64 without KASLR. - for (uint32_t i = 0; i < th->cover_size; i++) + for (uint64_t i = 0; i < th->cover_size; i++) write_output((uint32_t)th->cover_data[i + 1]); completed++; __atomic_store_n((uint32_t*)&output_data[0], completed, __ATOMIC_RELEASE); @@ -651,9 +712,9 @@ void execute_call(thread_t* th) int fd = open("/dev/fuse", O_RDWR); if (fd != -1) { char buf[256]; - sprintf(buf, "fd=%d,user_id=%lu,group_id=%lu,rootmode=0%o", fd, uid, gid, (unsigned)mode & ~3u); + sprintf(buf, "fd=%d,user_id=%ld,group_id=%ld,rootmode=0%o", fd, (long)uid, (long)gid, (unsigned)mode & ~3u); if (maxread != 0) - sprintf(buf + strlen(buf), ",max_read=%lu", maxread); + sprintf(buf + strlen(buf), ",max_read=%ld", (long)maxread); if (mode & 1) strcat(buf, ",default_permissions"); if (mode & 2) @@ -679,11 +740,11 @@ void execute_call(thread_t* th) if (fd != -1) { if (syscall(SYS_mknodat, AT_FDCWD, blkdev, S_IFBLK, makedev(7, 199)) == 0) { char buf[256]; - sprintf(buf, "fd=%d,user_id=%lu,group_id=%lu,rootmode=0%o", fd, uid, gid, (unsigned)mode & ~3u); + sprintf(buf, "fd=%d,user_id=%ld,group_id=%ld,rootmode=0%o", fd, (long)uid, (long)gid, (unsigned)mode & ~3u); if (maxread != 0) - sprintf(buf + strlen(buf), ",max_read=%lu", maxread); + sprintf(buf + strlen(buf), ",max_read=%ld", (long)maxread); if (blksize != 0) - sprintf(buf + strlen(buf), ",blksize=%lu", blksize); + sprintf(buf + strlen(buf), ",blksize=%ld", (long)blksize); if (mode & 1) strcat(buf, ",default_permissions"); if (mode & 2) @@ -717,8 +778,8 @@ void cover_open() if (th->cover_fd == -1) fail("open of /sys/kernel/debug/kcov failed"); if (ioctl(th->cover_fd, KCOV_INIT_TRACE, kCoverSize)) - fail("cover enable write failed"); - th->cover_data = (uintptr_t*)mmap(NULL, kCoverSize * sizeof(th->cover_data[0]), PROT_READ | PROT_WRITE, MAP_SHARED, th->cover_fd, 0); + fail("cover init write failed"); + th->cover_data = (uint64_t*)mmap(NULL, kCoverSize * sizeof(th->cover_data[0]), PROT_READ | PROT_WRITE, MAP_SHARED, th->cover_fd, 0); if ((void*)th->cover_data == MAP_FAILED) fail("cover mmap failed"); } @@ -741,11 +802,11 @@ void cover_reset(thread_t* th) __atomic_store_n(&th->cover_data[0], 0, __ATOMIC_RELAXED); } -uintptr_t cover_read(thread_t* th) +uint64_t cover_read(thread_t* th) { if (!flag_cover) return 0; - uintptr_t n = __atomic_load_n(&th->cover_data[0], __ATOMIC_RELAXED); + uint64_t n = __atomic_load_n(&th->cover_data[0], __ATOMIC_RELAXED); debug("#%d: read cover = %d\n", th->id, n); if (n >= kCoverSize) fail("#%d: too much cover %d", th->id, n); @@ -756,14 +817,14 @@ uintptr_t cover_read(thread_t* th) return n; } -uintptr_t cover_dedup(thread_t* th, uintptr_t n) +uint64_t cover_dedup(thread_t* th, uint64_t n) { - uintptr_t* cover_data = th->cover_data + 1; + uint64_t* cover_data = th->cover_data + 1; std::sort(cover_data, cover_data + n); - uintptr_t w = 0; - uintptr_t last = 0; - for (uintptr_t i = 0; i < n; i++) { - uintptr_t pc = cover_data[i]; + uint64_t w = 0; + uint64_t last = 0; + for (uint64_t i = 0; i < n; i++) { + uint64_t pc = cover_data[i]; if (pc == last) continue; cover_data[w++] = last = pc; @@ -902,7 +963,7 @@ retry: // But full sandboxing is expensive, so let's ignore this error for now. exitf("opendir(%s) failed due to NOFILE, exiting"); } - fail("opendir(%s) failed", dir); + exitf("opendir(%s) failed", dir); } while (dirent* ep = readdir(dp)) { if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0) @@ -911,7 +972,7 @@ retry: snprintf(filename, sizeof(filename), "%s/%s", dir, ep->d_name); struct stat st; if (lstat(filename, &st)) - fail("lstat(%s) failed", filename); + exitf("lstat(%s) failed", filename); if (S_ISDIR(st.st_mode)) { remove_dir(filename); continue; @@ -925,10 +986,10 @@ retry: break; } if (errno != EBUSY || i > 100) - fail("unlink(%s) failed", filename); + exitf("unlink(%s) failed", filename); debug("umount(%s)\n", filename); if (umount2(filename, MNT_DETACH)) - fail("umount(%s) failed", filename); + exitf("umount(%s) failed", filename); } } closedir(dp); @@ -944,7 +1005,7 @@ retry: if (errno == EBUSY) { debug("umount(%s)\n", dir); if (umount2(dir, MNT_DETACH)) - fail("umount(%s) failed", dir); + exitf("umount(%s) failed", dir); continue; } if (errno == ENOTEMPTY) { @@ -954,7 +1015,7 @@ retry: } } } - fail("rmdir(%s) failed", dir); + exitf("rmdir(%s) failed", dir); } } |
