diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2018-06-29 10:47:42 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2018-06-29 10:47:42 +0200 |
| commit | 7b45fa115b57d0a6424c369483b320acfe6a1de7 (patch) | |
| tree | fbc8b30b977926c3345509358bfdc76eaa2ca495 /executor | |
| parent | 1a3c2436df1f7c9c0271aad092558d943a0ee19e (diff) | |
pkg/csource: support fuchsia
Lots of assorted heavylifting to support csource on fuchsia.
Diffstat (limited to 'executor')
| -rw-r--r-- | executor/common.h | 16 | ||||
| -rw-r--r-- | executor/common_fuchsia.h | 284 | ||||
| -rw-r--r-- | executor/common_linux.h | 29 | ||||
| -rw-r--r-- | executor/executor_fuchsia.cc | 19 | ||||
| -rw-r--r-- | executor/executor_fuchsia.h | 62 | ||||
| -rw-r--r-- | executor/executor_linux.cc | 15 |
6 files changed, 382 insertions, 43 deletions
diff --git a/executor/common.h b/executor/common.h index fe1290f73..880c479bf 100644 --- a/executor/common.h +++ b/executor/common.h @@ -8,7 +8,8 @@ #if defined(SYZ_EXECUTOR) || (defined(SYZ_REPEAT) && defined(SYZ_WAIT_REPEAT)) || \ defined(SYZ_USE_TMP_DIR) || defined(SYZ_TUN_ENABLE) || defined(SYZ_SANDBOX_NAMESPACE) || \ defined(SYZ_SANDBOX_NONE) || defined(SYZ_SANDBOX_SETUID) || defined(SYZ_FAULT_INJECTION) || \ - defined(__NR_syz_kvm_setup_cpu) || defined(__NR_syz_init_net_socket) + defined(__NR_syz_kvm_setup_cpu) || defined(__NR_syz_init_net_socket) || \ + defined(__NR_syz_mmap) #include <errno.h> #include <stdarg.h> #include <stdio.h> @@ -74,7 +75,7 @@ struct call_t { #if defined(SYZ_EXECUTOR) || (defined(SYZ_REPEAT) && defined(SYZ_WAIT_REPEAT)) || \ defined(SYZ_USE_TMP_DIR) || defined(SYZ_TUN_ENABLE) || defined(SYZ_SANDBOX_NAMESPACE) || \ defined(SYZ_SANDBOX_NONE) || defined(SYZ_SANDBOX_SETUID) || defined(SYZ_FAULT_INJECTION) || \ - defined(__NR_syz_kvm_setup_cpu) + defined(__NR_syz_kvm_setup_cpu) || defined(__NR_syz_mmap) const int kFailStatus = 67; const int kRetryStatus = 69; #endif @@ -83,11 +84,12 @@ const int kRetryStatus = 69; const int kErrorStatus = 68; #endif -#if defined(SYZ_EXECUTOR) || (defined(SYZ_REPEAT) && defined(SYZ_WAIT_REPEAT)) || \ - defined(SYZ_USE_TMP_DIR) || defined(SYZ_TUN_ENABLE) || defined(SYZ_SANDBOX_NAMESPACE) || \ - defined(SYZ_SANDBOX_NONE) || defined(SYZ_SANDBOX_SETUID) || defined(__NR_syz_kvm_setup_cpu) || \ - defined(__NR_syz_init_net_socket) && \ - (defined(SYZ_SANDBOX_NONE) || defined(SYZ_SANDBOX_SETUID) || defined(SYZ_SANDBOX_NAMESPACE)) +#if defined(SYZ_EXECUTOR) || (defined(SYZ_REPEAT) && defined(SYZ_WAIT_REPEAT)) || \ + defined(SYZ_USE_TMP_DIR) || defined(SYZ_TUN_ENABLE) || defined(SYZ_SANDBOX_NAMESPACE) || \ + defined(SYZ_SANDBOX_NONE) || defined(SYZ_SANDBOX_SETUID) || defined(__NR_syz_kvm_setup_cpu) || \ + defined(__NR_syz_init_net_socket) && \ + (defined(SYZ_SANDBOX_NONE) || defined(SYZ_SANDBOX_SETUID) || defined(SYZ_SANDBOX_NAMESPACE)) || \ + defined(__NR_syz_mmap) // logical error (e.g. invalid input program), use as an assert() alernative NORETURN PRINTF static void fail(const char* msg, ...) { diff --git a/executor/common_fuchsia.h b/executor/common_fuchsia.h index 8b5b9f6a0..7db03e05e 100644 --- a/executor/common_fuchsia.h +++ b/executor/common_fuchsia.h @@ -3,28 +3,36 @@ // This file is shared between executor and csource package. +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #include <fcntl.h> #include <poll.h> +#include <signal.h> #include <sys/file.h> #include <sys/stat.h> +#include <sys/time.h> #include <sys/types.h> #include <sys/uio.h> +#include <time.h> #include <unistd.h> #include <utime.h> #include <zircon/process.h> #include <zircon/syscalls.h> -#if defined(SYZ_EXECUTOR) || defined(SYZ_THREADED) || defined(SYZ_COLLIDE) +#if defined(SYZ_EXECUTOR) || defined(SYZ_THREADED) || defined(SYZ_COLLIDE) || defined(SYZ_HANDLE_SEGV) #include <pthread.h> #include <stdlib.h> #endif +#if defined(SYZ_EXECUTOR) || (defined(SYZ_REPEAT) && defined(SYZ_WAIT_REPEAT) && defined(SYZ_USE_TMP_DIR)) +#include <dirent.h> +#endif #if defined(SYZ_EXECUTOR) || (defined(SYZ_REPEAT) && defined(SYZ_WAIT_REPEAT)) #include <errno.h> -#include <signal.h> #include <stdarg.h> #include <stdio.h> #include <sys/time.h> #include <sys/wait.h> -#include <time.h> #endif #if defined(SYZ_EXECUTOR) || defined(SYZ_HANDLE_SEGV) #include <zircon/syscalls/debug.h> @@ -36,7 +44,8 @@ #if defined(SYZ_EXECUTOR) || (defined(SYZ_REPEAT) && defined(SYZ_WAIT_REPEAT)) || \ defined(SYZ_USE_TMP_DIR) || defined(SYZ_HANDLE_SEGV) || defined(SYZ_TUN_ENABLE) || \ defined(SYZ_SANDBOX_NAMESPACE) || defined(SYZ_SANDBOX_SETUID) || \ - defined(SYZ_SANDBOX_NONE) || defined(SYZ_FAULT_INJECTION) || defined(__NR_syz_kvm_setup_cpu) + defined(SYZ_SANDBOX_NONE) || defined(SYZ_FAULT_INJECTION) || \ + defined(__NR_syz_mmap) __attribute__((noreturn)) static void doexit(int status) { _exit(status); @@ -72,7 +81,7 @@ static void* ex_handler(void* arg) continue; } debug("got exception packet: type=%d status=%d tid=%llu\n", - packet.type, packet.status, static_cast<unsigned long long>(packet.exception.tid)); + packet.type, packet.status, (unsigned long long)(packet.exception.tid)); zx_handle_t thread; status = zx_object_get_child(zx_process_self(), packet.exception.tid, ZX_RIGHT_SAME_RIGHTS, &thread); @@ -95,12 +104,14 @@ static void* ex_handler(void* arg) #error "unsupported arch" #endif status = zx_thread_write_state(thread, ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)); - if (status != ZX_OK) + if (status != ZX_OK) { debug("zx_thread_write_state failed: %d\n", status); + } } status = zx_task_resume(thread, ZX_RESUME_EXCEPTION); - if (status != ZX_OK) + if (status != ZX_OK) { debug("zx_task_resume failed: %d\n", status); + } zx_handle_close(thread); } doexit(1); @@ -167,7 +178,7 @@ long syz_mmap(size_t addr, size_t size) zx_info_vmar_t info; zx_status_t status = zx_object_get_info(root, ZX_INFO_VMAR, &info, sizeof(info), 0, 0); if (status != ZX_OK) - error("zx_object_get_info(ZX_INFO_VMAR) failed: %d", status); + fail("zx_object_get_info(ZX_INFO_VMAR) failed: %d", status); zx_handle_t vmo; status = zx_vmo_create(size, 0, &vmo); if (status != ZX_OK) @@ -225,3 +236,260 @@ long syz_future_time(long when) return now + delta_ms * 1000 * 1000; } #endif + +#if defined(SYZ_SANDBOX_NONE) +static void loop(); +static int do_sandbox_none(void) +{ + loop(); + return 0; +} +#endif + +#if defined(SYZ_USE_TMP_DIR) +static void use_temporary_dir() +{ + char tmpdir_template[] = "./syzkaller.XXXXXX"; + char* tmpdir = mkdtemp(tmpdir_template); + if (!tmpdir) + fail("failed to mkdtemp"); + if (chmod(tmpdir, 0777)) + fail("failed to chmod"); + if (chdir(tmpdir)) + fail("failed to chdir"); +} +#endif + +#if (defined(SYZ_REPEAT) && defined(SYZ_WAIT_REPEAT) && defined(SYZ_USE_TMP_DIR)) +static void remove_dir(const char* dir) +{ + struct dirent* ep; + DIR* dp = opendir(dir); + if (dp == NULL) + exitf("opendir(%s) failed", dir); + while ((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; + } + if (unlink(filename)) + exitf("unlink(%s) failed", filename); + } + closedir(dp); + if (rmdir(dir)) + exitf("rmdir(%s) failed", dir); +} +#endif + +#if defined(SYZ_EXECUTOR) || defined(SYZ_REPEAT) +static void execute_one(); +extern unsigned long long procid; + +#if defined(SYZ_EXECUTOR) +void reply_handshake(); +void receive_execute(); +void reply_execute(int status); +extern uint32* output_data; +extern uint32* output_pos; +#endif + +#if defined(SYZ_WAIT_REPEAT) +static void loop() +{ +#if defined(SYZ_EXECUTOR) + // Tell parent that we are ready to serve. + reply_handshake(); +#endif + int iter; + for (iter = 0;; iter++) { +#if defined(SYZ_EXECUTOR) || defined(SYZ_USE_TMP_DIR) + // Create a new private work dir for this test (removed at the end of the loop). + char cwdbuf[32]; + sprintf(cwdbuf, "./%d", iter); + if (mkdir(cwdbuf, 0777)) + fail("failed to mkdir"); +#endif +#if defined(SYZ_EXECUTOR) + // TODO: consider moving the read into the child. + // Potentially it can speed up things a bit -- when the read finishes + // we already have a forked worker process. + receive_execute(); +#endif + int pid = fork(); + if (pid < 0) + fail("clone failed"); + if (pid == 0) { +#if defined(SYZ_EXECUTOR) || defined(SYZ_USE_TMP_DIR) + if (chdir(cwdbuf)) + fail("failed to chdir"); +#endif +#if defined(SYZ_EXECUTOR) + close(kInPipeFd); + close(kOutPipeFd); +#endif +#if defined(SYZ_EXECUTOR) + output_pos = output_data; +#endif + execute_one(); + debug("worker exiting\n"); + doexit(0); + } + debug("spawned worker pid %d\n", pid); + + // We used to use sigtimedwait(SIGCHLD) to wait for the subprocess. + // But SIGCHLD is also delivered when a process stops/continues, + // so it would require a loop with status analysis and timeout recalculation. + // SIGCHLD should also unblock the usleep below, so the spin loop + // should be as efficient as sigtimedwait. + int status = 0; + uint64 start = current_time_ms(); +#if defined(SYZ_EXECUTOR) + uint64 last_executed = start; + uint32 executed_calls = __atomic_load_n(output_data, __ATOMIC_RELAXED); +#endif + for (;;) { + int res = waitpid(-1, &status, WNOHANG); + if (res == pid) { + debug("waitpid(%d)=%d\n", pid, res); + break; + } + usleep(1000); +#if defined(SYZ_EXECUTOR) + // Even though the test process executes exit at the end + // and execution time of each syscall is bounded by 20ms, + // this backup watchdog is necessary and its performance is important. + // The problem is that exit in the test processes can fail (sic). + // One observed scenario is that the test processes prohibits + // exit_group syscall using seccomp. Another observed scenario + // is that the test processes setups a userfaultfd for itself, + // then the main thread hangs when it wants to page in a page. + // Below we check if the test process still executes syscalls + // and kill it after 500ms of inactivity. + uint64 now = current_time_ms(); + uint32 now_executed = __atomic_load_n(output_data, __ATOMIC_RELAXED); + if (executed_calls != now_executed) { + executed_calls = now_executed; + last_executed = now; + } + if ((now - start < 3 * 1000) && (now - start < 1000 || now - last_executed < 500)) + continue; +#else + if (current_time_ms() - start < 3 * 1000) + continue; +#endif + debug("waitpid(%d)=%d\n", pid, res); + debug("killing\n"); + kill(-pid, SIGKILL); + kill(pid, SIGKILL); + while (waitpid(-1, &status, 0) != pid) { + } + break; + } +#if defined(SYZ_EXECUTOR) + status = WEXITSTATUS(status); + if (status == kFailStatus) + fail("child failed"); + if (status == kErrorStatus) + error("child errored"); + reply_execute(0); +#endif +#if defined(SYZ_EXECUTOR) || defined(SYZ_USE_TMP_DIR) + remove_dir(cwdbuf); +#endif + } +} +#else +void loop() +{ + while (1) { + execute_one(); + } +} +#endif +#endif + +#if defined(SYZ_THREADED) +struct thread_t { + int created, running, call; + pthread_t th; + pthread_mutex_t mu; + pthread_cond_t cv; +}; + +static struct thread_t threads[16]; +static void execute_call(int call); +static int running; +#if defined(SYZ_COLLIDE) +static int collide; +#endif + +static void* thr(void* arg) +{ + struct thread_t* th = (struct thread_t*)arg; + for (;;) { + pthread_mutex_lock(&th->mu); + while (!th->running) + pthread_cond_wait(&th->cv, &th->mu); + pthread_mutex_unlock(&th->mu); + execute_call(th->call); + __atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED); + pthread_mutex_lock(&th->mu); + __atomic_store_n(&th->running, 0, __ATOMIC_RELEASE); + pthread_mutex_unlock(&th->mu); + pthread_cond_signal(&th->cv); + } + return 0; +} + +static void execute(int num_calls) +{ + int call, thread; + running = 0; + for (call = 0; call < num_calls; call++) { + for (thread = 0; thread < sizeof(threads) / sizeof(threads[0]); thread++) { + struct thread_t* th = &threads[thread]; + if (!th->created) { + th->created = 1; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 128 << 10); + pthread_create(&th->th, &attr, thr, th); + pthread_mutex_init(&th->mu, 0); + pthread_cond_init(&th->cv, 0); + } + if (!__atomic_load_n(&th->running, __ATOMIC_ACQUIRE)) { + th->call = call; + __atomic_fetch_add(&running, 1, __ATOMIC_RELAXED); + + pthread_mutex_lock(&th->mu); + th->running = 1; + pthread_mutex_unlock(&th->mu); + pthread_cond_signal(&th->cv); +#if defined(SYZ_COLLIDE) + if (collide && call % 2) + break; +#endif + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 20 * 1000 * 1000; + pthread_mutex_lock(&th->mu); + while (th->running && ts.tv_nsec >= 5 * 1000 * 1000) { + pthread_cond_timedwait(&th->cv, &th->mu, &ts); + ts.tv_nsec /= 2; + } + pthread_mutex_unlock(&th->mu); + if (__atomic_load_n(&running, __ATOMIC_RELAXED)) + usleep((call == num_calls - 1) ? 10000 : 1000); + break; + } + } + } +} +#endif diff --git a/executor/common_linux.h b/executor/common_linux.h index 661f58c94..18acaaa7e 100644 --- a/executor/common_linux.h +++ b/executor/common_linux.h @@ -1192,6 +1192,17 @@ static void sandbox_common() debug("unshare(CLONE_SYSVSEM): %d\n", errno); } } + +int wait_for_loop(int pid) +{ + if (pid < 0) + fail("sandbox fork failed"); + debug("spawned loop pid %d\n", pid); + int status = 0; + while (waitpid(-1, &status, __WALL) != pid) { + } + return WEXITSTATUS(status); +} #endif #if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_NONE) @@ -1207,10 +1218,8 @@ static int do_sandbox_none(void) debug("unshare(CLONE_NEWPID): %d\n", errno); } int pid = fork(); - if (pid < 0) - fail("sandbox fork failed"); - if (pid) - return pid; + if (pid <= 0) + return wait_for_loop(pid); #if defined(SYZ_EXECUTOR) || defined(SYZ_ENABLE_CGROUPS) setup_cgroups(); @@ -1237,10 +1246,8 @@ static int do_sandbox_setuid(void) if (unshare(CLONE_NEWPID)) fail("unshare(CLONE_NEWPID)"); int pid = fork(); - if (pid < 0) - fail("sandbox fork failed"); - if (pid) - return pid; + if (pid <= 0) + return wait_for_loop(pid); #if defined(SYZ_EXECUTOR) || defined(SYZ_ENABLE_CGROUPS) setup_cgroups(); @@ -1404,9 +1411,7 @@ static int do_sandbox_namespace(void) mprotect(sandbox_stack, 4096, PROT_NONE); // to catch stack underflows pid = clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 64], CLONE_NEWUSER | CLONE_NEWPID, 0); - if (pid < 0) - fail("sandbox clone failed"); - return pid; + return wait_for_loop(pid); } #endif @@ -2205,7 +2210,7 @@ static void execute(int num_calls) ts.tv_sec = 0; ts.tv_nsec = 20 * 1000 * 1000; syscall(SYS_futex, &th->running, FUTEX_WAIT, 1, &ts); - if (running) + if (__atomic_load_n(&running, __ATOMIC_RELAXED)) usleep((call == num_calls - 1) ? 10000 : 1000); break; } diff --git a/executor/executor_fuchsia.cc b/executor/executor_fuchsia.cc index c21198449..ab6d627a3 100644 --- a/executor/executor_fuchsia.cc +++ b/executor/executor_fuchsia.cc @@ -6,7 +6,7 @@ #define SYZ_EXECUTOR #include "common_fuchsia.h" -#include "executor_posix.h" +#include "executor_fuchsia.h" #include "syscalls_fuchsia.h" @@ -27,6 +27,7 @@ int main(int argc, char** argv) install_segv_handler(); main_init(); execute_one(); + (void)error; // prevent unused function warning return 0; } @@ -34,10 +35,18 @@ long execute_syscall(const call_t* c, long a0, long a1, long a2, long a3, long a { long res = ZX_ERR_INVALID_ARGS; NONFAILING(res = c->call(a0, a1, a2, a3, a4, a5, a6, a7, a8)); - if (res == ZX_OK) - return 0; - errno = res; - return -1; + if (strncmp(c->name, "zx_", 3) == 0) { + // Convert zircon error convention to the libc convention that executor expects. + if (res == ZX_OK) + return 0; + errno = res; + return -1; + } + // We cast libc functions to signature returning long, + // as the result int -1 is returned as 0x00000000ffffffff rather than full -1. + if (res == 0xffffffff) + res = (long)-1; + return res; } void cover_open() diff --git a/executor/executor_fuchsia.h b/executor/executor_fuchsia.h new file mode 100644 index 000000000..e49f75b67 --- /dev/null +++ b/executor/executor_fuchsia.h @@ -0,0 +1,62 @@ +// Copyright 2018 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +// Fuchsia's pthread_cond_timedwait just returns immidiately, so we use simple spin wait. + +#include <pthread.h> + +typedef pthread_t osthread_t; + +void thread_start(osthread_t* t, void* (*fn)(void*), void* arg) +{ + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 128 << 10); + if (pthread_create(t, &attr, fn, arg)) + exitf("pthread_create failed"); + pthread_attr_destroy(&attr); +} + +struct event_t { + int state; +}; + +void event_init(event_t* ev) +{ + ev->state = 0; +} + +void event_reset(event_t* ev) +{ + ev->state = 0; +} + +void event_set(event_t* ev) +{ + if (ev->state) + fail("event already set"); + __atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE); +} + +void event_wait(event_t* ev) +{ + while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) + usleep(200); +} + +bool event_isset(event_t* ev) +{ + return __atomic_load_n(&ev->state, __ATOMIC_ACQUIRE); +} + +bool event_timedwait(event_t* ev, uint64 timeout_ms) +{ + uint64 start = current_time_ms(); + for (;;) { + if (__atomic_load_n(&ev->state, __ATOMIC_RELAXED)) + return true; + if (current_time_ms() - start > timeout_ms) + return false; + usleep(200); + } +} diff --git a/executor/executor_linux.cc b/executor/executor_linux.cc index 33474b75f..f2a21343a 100644 --- a/executor/executor_linux.cc +++ b/executor/executor_linux.cc @@ -76,27 +76,20 @@ int main(int argc, char** argv) install_segv_handler(); use_temporary_dir(); - int pid = -1; + int status = 0; switch (flag_sandbox) { case sandbox_none: - pid = do_sandbox_none(); + status = do_sandbox_none(); break; case sandbox_setuid: - pid = do_sandbox_setuid(); + status = do_sandbox_setuid(); break; case sandbox_namespace: - pid = do_sandbox_namespace(); + status = do_sandbox_namespace(); break; default: fail("unknown sandbox type"); } - if (pid < 0) - fail("clone failed"); - debug("spawned loop pid %d\n", pid); - int status = 0; - while (waitpid(-1, &status, __WALL) != pid) { - } - status = WEXITSTATUS(status); // Other statuses happen when fuzzer processes manages to kill loop. if (status != kFailStatus && status != kErrorStatus) status = kRetryStatus; |
