From f89294761cb8f89e11aecb58ee27629fcfeafbc3 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 18 Oct 2017 12:00:16 +0200 Subject: executor: use forkserver for freebsd Use forkserver and shmem for freebsd. This greatly improves speed. Also introduce fallback coverage signal based on unique (syscall+errno) pairs. --- executor/common_freebsd.h | 55 +++++++++++++++++++++++++++++ executor/executor.h | 1 + executor/executor_freebsd.cc | 84 +++++++++++++++++++++++++++++++++++++++++--- pkg/ipc/ipc.go | 14 ++++++++ sys/targets/targets.go | 4 +-- syz-fuzzer/fuzzer_freebsd.go | 2 +- 6 files changed, 153 insertions(+), 7 deletions(-) diff --git a/executor/common_freebsd.h b/executor/common_freebsd.h index c072b12aa..0712942bd 100644 --- a/executor/common_freebsd.h +++ b/executor/common_freebsd.h @@ -17,6 +17,9 @@ #include #include #endif +#if defined(SYZ_EXECUTOR) || (defined(SYZ_REPEAT) && defined(SYZ_WAIT_REPEAT) && defined(SYZ_USE_TMP_DIR)) +#include +#endif #define doexit exit @@ -86,6 +89,58 @@ static void sleep_ms(uint64_t ms) } #endif +#if defined(SYZ_EXECUTOR) || (defined(SYZ_REPEAT) && defined(SYZ_WAIT_REPEAT) && defined(SYZ_USE_TMP_DIR)) +static void remove_dir(const char* dir) +{ + DIR* dp; + struct dirent* ep; + int iter = 0; +retry: + dp = opendir(dir); + if (dp == NULL) + return; + 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)) + return; + if (S_ISDIR(st.st_mode)) { + remove_dir(filename); + continue; + } + int i; + for (i = 0;; i++) { + if (unlink(filename) == 0) + break; + if (errno == EROFS) + break; + if (errno != EBUSY || i > 100) + return; + } + } + closedir(dp); + int i; + for (i = 0;; i++) { + if (rmdir(dir) == 0) + break; + if (i < 100) { + if (errno == EROFS) + break; + if (errno == ENOTEMPTY) { + if (iter < 100) { + iter++; + goto retry; + } + } + } + return; + } +} +#endif + #if defined(SYZ_EXECUTOR) || defined(SYZ_FAULT_INJECTION) static int inject_fault(int nth) { diff --git a/executor/executor.h b/executor/executor.h index 1933c6cef..f3f0eb53a 100644 --- a/executor/executor.h +++ b/executor/executor.h @@ -616,6 +616,7 @@ void execute_call(thread_t* th) } cover_reset(th); + errno = 0; th->res = execute_syscall(call, th->args[0], th->args[1], th->args[2], th->args[3], th->args[4], th->args[5], th->args[6], th->args[7], th->args[8]); diff --git a/executor/executor_freebsd.cc b/executor/executor_freebsd.cc index 78c48b693..a009da4cb 100644 --- a/executor/executor_freebsd.cc +++ b/executor/executor_freebsd.cc @@ -12,11 +12,17 @@ #include "syscalls_freebsd.h" +#include +#include #include #include #include -uint32_t output; +const int kInFd = 3; +const int kOutFd = 4; + +uint32_t* output_data; +uint32_t* output_pos; int main(int argc, char** argv) { @@ -25,6 +31,23 @@ int main(int argc, char** argv) return 0; } + if (mmap(&input_data[0], kMaxInput, PROT_READ, MAP_PRIVATE | MAP_FIXED, kInFd, 0) != &input_data[0]) + fail("mmap of input file failed"); + // The output region is the only thing in executor process for which consistency matters. + // If it is corrupted ipc package will fail to parse its contents and panic. + // But fuzzer constantly invents new ways of how to currupt the region, + // so we map the region at a (hopefully) hard to guess address surrounded by unmapped pages. + void* const kOutputDataAddr = (void*)0x1ddbc20000; + output_data = (uint32_t*)mmap(kOutputDataAddr, kMaxOutput, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, kOutFd, 0); + if (output_data != kOutputDataAddr) + fail("mmap of output file failed"); + // Prevent random programs to mess with these fds. + // Due to races in collider mode, a program can e.g. ftruncate one of these fds, + // which will cause fuzzer to crash. + // That's also the reason why we close kInPipeFd/kOutPipeFd below. + close(kInFd); + close(kOutFd); + // Some minimal sandboxing. struct rlimit rlim; rlim.rlim_cur = rlim.rlim_max = 128 << 20; @@ -40,8 +63,55 @@ int main(int argc, char** argv) install_segv_handler(); setup_control_pipes(); - receive_execute(true); - execute_one(); + receive_handshake(); + reply_handshake(); + + for (;;) { + receive_execute(false); + char cwdbuf[128] = "/syz-tmpXXXXXX"; + mkdtemp(cwdbuf); + int pid = fork(); + if (pid < 0) + fail("fork failed"); + if (pid == 0) { + close(kInPipeFd); + close(kOutPipeFd); + if (chdir(cwdbuf)) + fail("chdir failed"); + output_pos = output_data; + execute_one(); + doexit(0); + } + int status = 0; + uint64_t start = current_time_ms(); + uint64_t last_executed = start; + uint32_t executed_calls = __atomic_load_n(output_data, __ATOMIC_RELAXED); + for (;;) { + int res = waitpid(pid, &status, WNOHANG); + if (res == pid) + break; + sleep_ms(1); + uint64_t now = current_time_ms(); + uint32_t 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 - last_executed < 500)) + continue; + kill(pid, SIGKILL); + while (waitpid(pid, &status, 0) != pid) { + } + break; + } + status = WEXITSTATUS(status); + if (status == kFailStatus) + fail("child failed"); + if (status == kErrorStatus) + error("child errored"); + remove_dir(cwdbuf); + reply_execute(0); + } return 0; } @@ -71,9 +141,15 @@ uint64_t read_cover_size(thread_t* th) uint32_t* write_output(uint32_t v) { - return &output; + if (collide) + return 0; + if (output_pos < output_data || (char*)output_pos >= (char*)output_data + kMaxOutput) + fail("output overflow"); + *output_pos = v; + return output_pos++; } void write_completed(uint32_t completed) { + __atomic_store_n(output_data, completed, __ATOMIC_RELEASE); } diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go index 8abf54054..a28eedcfa 100644 --- a/pkg/ipc/ipc.go +++ b/pkg/ipc/ipc.go @@ -477,6 +477,20 @@ func (env *Env) readOutCoverage(p *prog.Prog) (info []CallInfo, err0 error) { } info[callIndex].Comps = compMap } + if env.config.Flags&FlagSignal != 0 { + // This is fallback coverage used when no real coverage available. + // We use syscall number or-ed with returned errno value as signal. + // At least this gives us all combinations of syscall+errno. + for i := range info { + ci := &info[i] + if len(ci.Signal) != 0 { + continue + } + num := p.Calls[i].Meta.ID + sig := uint32(num<<16) | uint32(ci.Errno)&0x3ff + ci.Signal = []uint32{sig} + } + } return } diff --git a/sys/targets/targets.go b/sys/targets/targets.go index 4ec176b46..6b5523f7d 100644 --- a/sys/targets/targets.go +++ b/sys/targets/targets.go @@ -124,8 +124,8 @@ var oses = map[string]os{ "freebsd": { SyscallNumbers: true, SyscallPrefix: "SYS_", - ExecutorUsesShmem: false, - ExecutorUsesForkServer: false, + ExecutorUsesShmem: true, + ExecutorUsesForkServer: true, }, "fuchsia": { SyscallNumbers: false, diff --git a/syz-fuzzer/fuzzer_freebsd.go b/syz-fuzzer/fuzzer_freebsd.go index 656e87f15..3eba50f52 100644 --- a/syz-fuzzer/fuzzer_freebsd.go +++ b/syz-fuzzer/fuzzer_freebsd.go @@ -17,5 +17,5 @@ func kmemleakScan(report bool) { } func checkCompsSupported() (kcov, comps bool) { - return false, false + return true, false } -- cgit mrf-deployment