aboutsummaryrefslogtreecommitdiffstats
path: root/executor/executor.cc
diff options
context:
space:
mode:
Diffstat (limited to 'executor/executor.cc')
-rw-r--r--executor/executor.cc1269
1 files changed, 1269 insertions, 0 deletions
diff --git a/executor/executor.cc b/executor/executor.cc
new file mode 100644
index 000000000..1efba1060
--- /dev/null
+++ b/executor/executor.cc
@@ -0,0 +1,1269 @@
+// Copyright 2017 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.
+
+// +build
+
+#include <algorithm>
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "defs.h"
+
+#if defined(__GNUC__)
+#define SYSCALLAPI
+#define NORETURN __attribute__((noreturn))
+#define ALIGNED(N) __attribute__((aligned(N)))
+#define PRINTF __attribute__((format(printf, 1, 2)))
+#else
+// Assuming windows/cl.
+#define SYSCALLAPI WINAPI
+#define NORETURN __declspec(noreturn)
+#define ALIGNED(N) __declspec(align(N))
+#define PRINTF
+#endif
+
+#ifndef GIT_REVISION
+#define GIT_REVISION "unknown"
+#endif
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+// uint64 is impossible to printf without using the clumsy and verbose "%" PRId64.
+// So we define and use uint64. Note: pkg/csource does s/uint64/uint64/.
+// Also define uint32/16/8 for consistency.
+typedef unsigned long long uint64;
+typedef unsigned int uint32;
+typedef unsigned short uint16;
+typedef unsigned char uint8;
+
+// exit/_exit do not necessary work (e.g. if fuzzer sets seccomp filter that prohibits exit_group).
+// Use doexit instead. We must redefine exit to something that exists in stdlib,
+// because some standard libraries contain "using ::exit;", but has different signature.
+#define exit vsnprintf
+
+// Note: zircon max fd is 256.
+// Some common_OS.h files know about this constant for RLIMIT_NOFILE.
+const int kMaxFd = 250;
+const int kInPipeFd = kMaxFd - 1; // remapped from stdin
+const int kOutPipeFd = kMaxFd - 2; // remapped from stdout
+const int kMaxArgs = 9;
+const int kCoverSize = 256 << 10;
+const int kFailStatus = 67;
+const int kRetryStatus = 69;
+const int kErrorStatus = 68;
+
+// Logical error (e.g. invalid input program), use as an assert() alternative.
+NORETURN PRINTF void fail(const char* msg, ...);
+// Kernel error (e.g. wrong syscall return value).
+NORETURN PRINTF void error(const char* msg, ...);
+// Just exit (e.g. due to temporal ENOMEM error).
+NORETURN PRINTF void exitf(const char* msg, ...);
+// Print debug output, does not add \n at the end of msg as opposed to the previous functions.
+PRINTF void debug(const char* msg, ...);
+void debug_dump_data(const char* data, int length);
+NORETURN void doexit(int status);
+
+static void receive_execute();
+static void reply_execute(int status);
+
+#if GOOS_akaros
+static void resend_execute(int fd);
+#endif
+
+#if SYZ_EXECUTOR_USES_FORK_SERVER
+static void receive_handshake();
+static void reply_handshake();
+#endif
+
+#if SYZ_EXECUTOR_USES_SHMEM
+const int kMaxOutput = 16 << 20;
+const int kInFd = 3;
+const int kOutFd = 4;
+uint32* output_data;
+uint32* output_pos;
+static uint32* write_output(uint32 v);
+static void write_completed(uint32 completed);
+static uint32 hash(uint32 a);
+static bool dedup(uint32 sig);
+#endif
+
+enum sandbox_type {
+ sandbox_none,
+ sandbox_setuid,
+ sandbox_namespace,
+};
+
+bool flag_debug;
+bool flag_cover;
+bool flag_sandbox_privs;
+sandbox_type flag_sandbox;
+bool flag_enable_tun;
+bool flag_enable_net_dev;
+bool flag_enable_fault_injection;
+
+bool flag_collect_cover;
+bool flag_dedup_cover;
+bool flag_threaded;
+bool flag_collide;
+
+// If true, then executor should write the comparisons data to fuzzer.
+bool flag_collect_comps;
+
+// Inject fault into flag_fault_nth-th operation in flag_fault_call-th syscall.
+bool flag_inject_fault;
+int flag_fault_call;
+int flag_fault_nth;
+
+#define SYZ_EXECUTOR 1
+#include "common.h"
+
+const int kMaxCommands = 1000;
+const int kMaxInput = 2 << 20;
+const int kMaxThreads = 16;
+
+const uint64 instr_eof = -1;
+const uint64 instr_copyin = -2;
+const uint64 instr_copyout = -3;
+
+const uint64 arg_const = 0;
+const uint64 arg_result = 1;
+const uint64 arg_data = 2;
+const uint64 arg_csum = 3;
+
+const uint64 binary_format_native = 0;
+const uint64 binary_format_bigendian = 1;
+const uint64 binary_format_strdec = 2;
+const uint64 binary_format_strhex = 3;
+const uint64 binary_format_stroct = 4;
+
+const uint64 no_copyout = -1;
+
+unsigned long long procid;
+int running;
+uint32 completed;
+bool collide;
+bool is_kernel_64_bit = true;
+
+ALIGNED(64 << 10)
+char input_data[kMaxInput];
+
+// Checksum kinds.
+const uint64 arg_csum_inet = 0;
+
+// Checksum chunk kinds.
+const uint64 arg_csum_chunk_data = 0;
+const uint64 arg_csum_chunk_const = 1;
+
+typedef long(SYSCALLAPI* syscall_t)(long, long, long, long, long, long, long, long, long);
+
+struct call_t {
+ const char* name;
+ int sys_nr;
+ syscall_t call;
+};
+
+struct cover_t {
+ int fd;
+ uint32 size;
+ char* data;
+ char* data_end;
+};
+
+struct thread_t {
+ int id;
+ bool created;
+ event_t ready;
+ event_t done;
+ uint64* copyout_pos;
+ uint64 copyout_index;
+ bool colliding;
+ bool handled;
+ int call_index;
+ int call_num;
+ int num_args;
+ long args[kMaxArgs];
+ long res;
+ uint32 reserrno;
+ bool fault_injected;
+ cover_t cov;
+};
+
+thread_t threads[kMaxThreads];
+
+struct res_t {
+ bool executed;
+ uint64 val;
+};
+
+res_t results[kMaxCommands];
+
+const uint64 kInMagic = 0xbadc0ffeebadface;
+const uint32 kOutMagic = 0xbadf00d;
+
+struct handshake_req {
+ uint64 magic;
+ uint64 flags; // env flags
+ uint64 pid;
+};
+
+struct handshake_reply {
+ uint32 magic;
+};
+
+struct execute_req {
+ uint64 magic;
+ uint64 env_flags;
+ uint64 exec_flags;
+ uint64 pid;
+ uint64 fault_call;
+ uint64 fault_nth;
+ uint64 prog_size;
+};
+
+struct execute_reply {
+ uint32 magic;
+ uint32 done;
+ uint32 status;
+};
+
+struct call_reply {
+ execute_reply header;
+ uint32 call_index;
+ uint32 call_num;
+ uint32 reserrno;
+ uint32 fault_injected;
+ uint32 signal_size;
+ uint32 cover_size;
+ uint32 comps_size;
+ // signal/cover/comps follow
+};
+
+enum {
+ KCOV_CMP_CONST = 1,
+ KCOV_CMP_SIZE1 = 0,
+ KCOV_CMP_SIZE2 = 2,
+ KCOV_CMP_SIZE4 = 4,
+ KCOV_CMP_SIZE8 = 6,
+ KCOV_CMP_SIZE_MASK = 6,
+};
+
+struct kcov_comparison_t {
+ // Note: comparisons are always 64-bits regardless of kernel bitness.
+ uint64 type;
+ uint64 arg1;
+ uint64 arg2;
+ uint64 pc;
+
+ bool ignore() const;
+ void write();
+ bool operator==(const struct kcov_comparison_t& other) const;
+ bool operator<(const struct kcov_comparison_t& other) const;
+};
+
+static thread_t* schedule_call(int call_index, int call_num, bool colliding, uint64 copyout_index, uint64 num_args, uint64* args, uint64* pos);
+static void handle_completion(thread_t* th);
+static void execute_call(thread_t* th);
+static void thread_create(thread_t* th, int id);
+static void* worker_thread(void* arg);
+static uint64 read_input(uint64** input_posp, bool peek = false);
+static uint64 read_arg(uint64** input_posp);
+static uint64 read_const_arg(uint64** input_posp, uint64* size_p, uint64* bf, uint64* bf_off_p, uint64* bf_len_p);
+static uint64 read_result(uint64** input_posp);
+static void copyin(char* addr, uint64 val, uint64 size, uint64 bf, uint64 bf_off, uint64 bf_len);
+static bool copyout(char* addr, uint64 size, uint64* res);
+static void setup_control_pipes();
+
+#include "syscalls.h"
+
+#if GOOS_linux
+#include "executor_linux.h"
+#elif GOOS_fuchsia
+#include "executor_fuchsia.h"
+#elif GOOS_akaros
+#include "executor_akaros.h"
+#elif GOOS_freebsd || GOOS_netbsd
+#include "executor_bsd.h"
+#elif GOOS_windows
+#include "executor_windows.h"
+#elif GOOS_test
+#include "executor_test.h"
+#else
+#error "unknown OS"
+#endif
+
+#include "test.h"
+
+int main(int argc, char** argv)
+{
+ if (argc == 2 && strcmp(argv[1], "version") == 0) {
+ puts(GOOS " " GOARCH " " SYZ_REVISION " " GIT_REVISION);
+ return 0;
+ }
+ if (argc == 2 && strcmp(argv[1], "test") == 0)
+ return run_tests();
+
+ os_init(argc, argv, (void*)SYZ_DATA_OFFSET, SYZ_NUM_PAGES * SYZ_PAGE_SIZE);
+
+#if SYZ_EXECUTOR_USES_SHMEM
+ 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 with random offset,
+ // surrounded by unmapped pages.
+ // The address chosen must also work on 32-bit kernels with 1GB user address space.
+ void* preferred = (void*)(0x1b2bc20000ull + (1 << 20) * (getpid() % 128));
+ output_data = (uint32*)mmap(preferred, kMaxOutput,
+ PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, kOutFd, 0);
+ if (output_data != preferred)
+ fail("mmap of output file failed");
+
+ // Prevent test 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.
+ close(kInFd);
+ close(kOutFd);
+#endif
+
+ use_temporary_dir();
+ install_segv_handler();
+ setup_control_pipes();
+#if SYZ_EXECUTOR_USES_FORK_SERVER
+ receive_handshake();
+#else
+ receive_execute();
+#endif
+ if (flag_cover) {
+ for (int i = 0; i < kMaxThreads; i++)
+ cover_open(&threads[i].cov);
+ }
+
+ int status = 0;
+ switch (flag_sandbox) {
+ case sandbox_none:
+ status = do_sandbox_none();
+ break;
+ case sandbox_setuid:
+ status = do_sandbox_setuid();
+ break;
+ case sandbox_namespace:
+ status = do_sandbox_namespace();
+ break;
+ default:
+ fail("unknown sandbox type");
+ }
+#if SYZ_EXECUTOR_USES_FORK_SERVER
+ // Other statuses happen when fuzzer processes manages to kill loop.
+ if (status != kFailStatus && status != kErrorStatus)
+ status = kRetryStatus;
+ // If an external sandbox process wraps executor, the out pipe will be closed
+ // before the sandbox process exits this will make ipc package kill the sandbox.
+ // As the result sandbox process will exit with exit status 9 instead of the executor
+ // exit status (notably kRetryStatus). Consequently, ipc will treat it as hard
+ // failure rather than a temporal failure. So we duplicate the exit status on the pipe.
+ reply_execute(status);
+ errno = 0;
+ if (status == kFailStatus)
+ fail("loop failed");
+ if (status == kErrorStatus)
+ error("loop errored");
+ // Loop can be killed by a test process with e.g.:
+ // ptrace(PTRACE_SEIZE, 1, 0, 0x100040)
+ // This is unfortunate, but I don't have a better solution than ignoring it for now.
+ exitf("loop exited with status %d", status);
+ // Unreachable.
+ return 1;
+#else
+ reply_execute(status);
+ return status;
+#endif
+}
+
+void setup_control_pipes()
+{
+ if (dup2(0, kInPipeFd) < 0)
+ fail("dup2(0, kInPipeFd) failed");
+ if (dup2(1, kOutPipeFd) < 0)
+ fail("dup2(1, kOutPipeFd) failed");
+ if (dup2(2, 1) < 0)
+ fail("dup2(2, 1) failed");
+ // We used to close(0), but now we dup stderr to stdin to keep fd numbers
+ // stable across executor and C programs generated by pkg/csource.
+ if (dup2(2, 0) < 0)
+ fail("dup2(2, 0) failed");
+}
+
+void parse_env_flags(uint64 flags)
+{
+ flag_debug = flags & (1 << 0);
+ flag_cover = flags & (1 << 1);
+ flag_sandbox = sandbox_none;
+ if (flags & (1 << 2))
+ flag_sandbox = sandbox_setuid;
+ else if (flags & (1 << 3))
+ flag_sandbox = sandbox_namespace;
+ flag_enable_tun = flags & (1 << 4);
+ flag_enable_net_dev = flags & (1 << 5);
+ flag_enable_fault_injection = flags & (1 << 6);
+}
+
+#if SYZ_EXECUTOR_USES_FORK_SERVER
+void receive_handshake()
+{
+ handshake_req req = {};
+ int n = read(kInPipeFd, &req, sizeof(req));
+ if (n != sizeof(req))
+ fail("handshake read failed: %d", n);
+ if (req.magic != kInMagic)
+ fail("bad handshake magic 0x%llx", req.magic);
+ parse_env_flags(req.flags);
+ procid = req.pid;
+}
+
+void reply_handshake()
+{
+ handshake_reply reply = {};
+ reply.magic = kOutMagic;
+ if (write(kOutPipeFd, &reply, sizeof(reply)) != sizeof(reply))
+ fail("control pipe write failed");
+}
+#endif
+
+static execute_req last_execute_req;
+
+void receive_execute()
+{
+ execute_req& req = last_execute_req;
+ if (read(kInPipeFd, &req, sizeof(req)) != (ssize_t)sizeof(req))
+ fail("control pipe read failed");
+ if (req.magic != kInMagic)
+ fail("bad execute request magic 0x%llx", req.magic);
+ if (req.prog_size > kMaxInput)
+ fail("bad execute prog size 0x%llx", req.prog_size);
+ parse_env_flags(req.env_flags);
+ procid = req.pid;
+ flag_collect_cover = req.exec_flags & (1 << 0);
+ flag_dedup_cover = req.exec_flags & (1 << 1);
+ flag_inject_fault = req.exec_flags & (1 << 2);
+ flag_collect_comps = req.exec_flags & (1 << 3);
+ flag_threaded = req.exec_flags & (1 << 4);
+ flag_collide = req.exec_flags & (1 << 5);
+ flag_fault_call = req.fault_call;
+ flag_fault_nth = req.fault_nth;
+ if (!flag_threaded)
+ flag_collide = false;
+ debug("exec opts: pid=%llu threaded=%d collide=%d cover=%d comps=%d dedup=%d fault=%d/%d/%d prog=%llu\n",
+ procid, flag_threaded, flag_collide, flag_collect_cover, flag_collect_comps,
+ flag_dedup_cover, flag_inject_fault, flag_fault_call, flag_fault_nth,
+ req.prog_size);
+ if (SYZ_EXECUTOR_USES_SHMEM) {
+ if (req.prog_size)
+ fail("need_prog: no program");
+ return;
+ }
+ if (req.prog_size == 0)
+ fail("need_prog: no program");
+ uint64 pos = 0;
+ for (;;) {
+ ssize_t rv = read(kInPipeFd, input_data + pos, sizeof(input_data) - pos);
+ if (rv < 0)
+ fail("read failed");
+ pos += rv;
+ if (rv == 0 || pos >= req.prog_size)
+ break;
+ }
+ if (pos != req.prog_size)
+ fail("bad input size %lld, want %lld", pos, req.prog_size);
+}
+
+#if GOOS_akaros
+void resend_execute(int fd)
+{
+ execute_req& req = last_execute_req;
+ if (write(fd, &req, sizeof(req)) != sizeof(req))
+ fail("child pipe header write failed");
+ if (write(fd, input_data, req.prog_size) != (ssize_t)req.prog_size)
+ fail("child pipe program write failed");
+}
+#endif
+
+void reply_execute(int status)
+{
+ execute_reply reply = {};
+ reply.magic = kOutMagic;
+ reply.done = true;
+ reply.status = status;
+ if (write(kOutPipeFd, &reply, sizeof(reply)) != sizeof(reply))
+ fail("control pipe write failed");
+}
+
+// execute_one executes program stored in input_data.
+void execute_one()
+{
+ // Duplicate global collide variable on stack.
+ // Fuzzer once come up with ioctl(fd, FIONREAD, 0x920000),
+ // where 0x920000 was exactly collide address, so every iteration reset collide to 0.
+ bool colliding = false;
+#if SYZ_EXECUTOR_USES_SHMEM
+ output_pos = output_data;
+ write_output(0); // Number of executed syscalls (updated later).
+#endif
+ uint64 start = current_time_ms();
+
+retry:
+ uint64* input_pos = (uint64*)input_data;
+
+ if (flag_cover && !colliding && !flag_threaded)
+ cover_enable(&threads[0].cov, flag_collect_comps);
+
+ int call_index = 0;
+ for (;;) {
+ uint64 call_num = read_input(&input_pos);
+ if (call_num == instr_eof)
+ break;
+ if (call_num == instr_copyin) {
+ char* addr = (char*)read_input(&input_pos);
+ uint64 typ = read_input(&input_pos);
+ switch (typ) {
+ case arg_const: {
+ uint64 size, bf, bf_off, bf_len;
+ uint64 arg = read_const_arg(&input_pos, &size, &bf, &bf_off, &bf_len);
+ copyin(addr, arg, size, bf, bf_off, bf_len);
+ break;
+ }
+ case arg_result: {
+ uint64 meta = read_input(&input_pos);
+ uint64 size = meta & 0xff;
+ uint64 bf = meta >> 8;
+ uint64 val = read_result(&input_pos);
+ copyin(addr, val, size, bf, 0, 0);
+ break;
+ }
+ case arg_data: {
+ uint64 size = read_input(&input_pos);
+ NONFAILING(memcpy(addr, input_pos, size));
+ // Read out the data.
+ for (uint64 i = 0; i < (size + 7) / 8; i++)
+ read_input(&input_pos);
+ break;
+ }
+ case arg_csum: {
+ debug("checksum found at %p\n", addr);
+ uint64 size = read_input(&input_pos);
+ char* csum_addr = addr;
+ uint64 csum_kind = read_input(&input_pos);
+ switch (csum_kind) {
+ case arg_csum_inet: {
+ if (size != 2)
+ fail("inet checksum must be 2 bytes, not %llu", size);
+ debug("calculating checksum for %p\n", csum_addr);
+ struct csum_inet csum;
+ csum_inet_init(&csum);
+ uint64 chunks_num = read_input(&input_pos);
+ uint64 chunk;
+ for (chunk = 0; chunk < chunks_num; chunk++) {
+ uint64 chunk_kind = read_input(&input_pos);
+ uint64 chunk_value = read_input(&input_pos);
+ uint64 chunk_size = read_input(&input_pos);
+ switch (chunk_kind) {
+ case arg_csum_chunk_data:
+ debug("#%lld: data chunk, addr: %llx, size: %llu\n", chunk, chunk_value, chunk_size);
+ NONFAILING(csum_inet_update(&csum, (const uint8*)chunk_value, chunk_size));
+ break;
+ case arg_csum_chunk_const:
+ if (chunk_size != 2 && chunk_size != 4 && chunk_size != 8) {
+ fail("bad checksum const chunk size %lld\n", chunk_size);
+ }
+ // Here we assume that const values come to us big endian.
+ debug("#%lld: const chunk, value: %llx, size: %llu\n", chunk, chunk_value, chunk_size);
+ csum_inet_update(&csum, (const uint8*)&chunk_value, chunk_size);
+ break;
+ default:
+ fail("bad checksum chunk kind %llu", chunk_kind);
+ }
+ }
+ uint16 csum_value = csum_inet_digest(&csum);
+ debug("writing inet checksum %hx to %p\n", csum_value, csum_addr);
+ copyin(csum_addr, csum_value, 2, binary_format_native, 0, 0);
+ break;
+ }
+ default:
+ fail("bad checksum kind %llu", csum_kind);
+ }
+ break;
+ }
+ default:
+ fail("bad argument type %llu", typ);
+ }
+ continue;
+ }
+ if (call_num == instr_copyout) {
+ read_input(&input_pos); // index
+ read_input(&input_pos); // addr
+ read_input(&input_pos); // size
+ // The copyout will happen when/if the call completes.
+ continue;
+ }
+
+ // Normal syscall.
+ if (call_num >= ARRAY_SIZE(syscalls))
+ fail("invalid command number %llu", call_num);
+ uint64 copyout_index = read_input(&input_pos);
+ uint64 num_args = read_input(&input_pos);
+ if (num_args > kMaxArgs)
+ fail("command has bad number of arguments %llu", num_args);
+ uint64 args[kMaxArgs] = {};
+ for (uint64 i = 0; i < num_args; i++)
+ args[i] = read_arg(&input_pos);
+ for (uint64 i = num_args; i < 6; i++)
+ args[i] = 0;
+ thread_t* th = schedule_call(call_index++, call_num, colliding, copyout_index, num_args, args, input_pos);
+
+ if (colliding && (call_index % 2) == 0) {
+ // Don't wait for every other call.
+ // We already have results from the previous execution.
+ } else if (flag_threaded) {
+ // Wait for call completion.
+ // Note: sys knows about this 25ms timeout when it generates
+ // timespec/timeval values.
+ const uint64 timeout_ms = flag_debug ? 3000 : 25;
+ if (event_timedwait(&th->done, timeout_ms))
+ handle_completion(th);
+ // Check if any of previous calls have completed.
+ // Give them some additional time, because they could have been
+ // just unblocked by the current call.
+ if (running < 0)
+ fail("running = %d", running);
+ if (running > 0) {
+ bool last = read_input(&input_pos, true) == instr_eof;
+ uint64 wait = last ? 100 : 2;
+ uint64 wait_start = current_time_ms();
+ uint64 wait_end = wait_start + wait;
+ if (!colliding && wait_end < start + 800)
+ wait_end = start + 800;
+ while (running > 0 && current_time_ms() <= wait_end) {
+ sleep_ms(1);
+ for (int i = 0; i < kMaxThreads; i++) {
+ th = &threads[i];
+ if (!th->handled && event_isset(&th->done))
+ handle_completion(th);
+ }
+ }
+ }
+ } else {
+ // Execute directly.
+ if (th != &threads[0])
+ fail("using non-main thread in non-thread mode");
+ event_reset(&th->ready);
+ execute_call(th);
+ event_set(&th->done);
+ handle_completion(th);
+ }
+ }
+
+ if (flag_collide && !flag_inject_fault && !colliding && !collide) {
+ debug("enabling collider\n");
+ collide = colliding = true;
+ goto retry;
+ }
+}
+
+thread_t* schedule_call(int call_index, int call_num, bool colliding, uint64 copyout_index, uint64 num_args, uint64* args, uint64* pos)
+{
+ // Find a spare thread to execute the call.
+ int i;
+ for (i = 0; i < kMaxThreads; i++) {
+ thread_t* th = &threads[i];
+ if (!th->created)
+ thread_create(th, i);
+ if (event_isset(&th->done)) {
+ if (!th->handled)
+ handle_completion(th);
+ break;
+ }
+ }
+ if (i == kMaxThreads)
+ exitf("out of threads");
+ thread_t* th = &threads[i];
+ debug("scheduling call %d [%s] on thread %d\n", call_index, syscalls[call_num].name, th->id);
+ if (event_isset(&th->ready) || !event_isset(&th->done) || !th->handled)
+ fail("bad thread state in schedule: ready=%d done=%d handled=%d",
+ event_isset(&th->ready), event_isset(&th->done), th->handled);
+ th->colliding = colliding;
+ th->copyout_pos = pos;
+ th->copyout_index = copyout_index;
+ event_reset(&th->done);
+ th->handled = false;
+ th->call_index = call_index;
+ th->call_num = call_num;
+ th->num_args = num_args;
+ for (int i = 0; i < kMaxArgs; i++)
+ th->args[i] = args[i];
+ event_set(&th->ready);
+ running++;
+ return th;
+}
+
+#if SYZ_EXECUTOR_USES_SHMEM
+template <typename cover_t>
+void write_coverage_signal(thread_t* th, uint32* signal_count_pos, uint32* cover_count_pos)
+{
+ // Write out feedback signals.
+ // Currently it is code edges computed as xor of two subsequent basic block PCs.
+ cover_t* cover_data = ((cover_t*)th->cov.data) + 1;
+ uint32 nsig = 0;
+ cover_t prev = 0;
+ for (uint32 i = 0; i < th->cov.size; i++) {
+ cover_t pc = cover_data[i];
+ if (!cover_check(pc)) {
+ debug("got bad pc: 0x%llx\n", (uint64)pc);
+ doexit(0);
+ }
+ cover_t sig = pc ^ prev;
+ prev = hash(pc);
+ if (dedup(sig))
+ continue;
+ write_output(sig);
+ nsig++;
+ }
+ // Write out number of signals.
+ *signal_count_pos = nsig;
+
+ if (!flag_collect_cover)
+ return;
+ // Write out real coverage (basic block PCs).
+ uint32 cover_size = th->cov.size;
+ if (flag_dedup_cover) {
+ cover_t* end = cover_data + cover_size;
+ std::sort(cover_data, end);
+ cover_size = std::unique(cover_data, end) - cover_data;
+ }
+ // Truncate PCs to uint32 assuming that they fit into 32-bits.
+ // True for x86_64 and arm64 without KASLR.
+ for (uint32 i = 0; i < cover_size; i++)
+ write_output(cover_data[i]);
+ *cover_count_pos = cover_size;
+}
+#endif
+
+void handle_completion(thread_t* th)
+{
+ debug("completion of call %d [%s] on thread %d\n", th->call_index, syscalls[th->call_num].name, th->id);
+ if (event_isset(&th->ready) || !event_isset(&th->done) || th->handled)
+ fail("bad thread state in completion: ready=%d done=%d handled=%d",
+ event_isset(&th->ready), event_isset(&th->done), th->handled);
+ if (th->res != (long)-1) {
+ if (th->copyout_index != no_copyout) {
+ if (th->copyout_index >= kMaxCommands)
+ fail("result idx %lld overflows kMaxCommands", th->copyout_index);
+ results[th->copyout_index].executed = true;
+ results[th->copyout_index].val = th->res;
+ }
+ for (bool done = false; !done;) {
+ uint64 instr = read_input(&th->copyout_pos);
+ switch (instr) {
+ case instr_copyout: {
+ uint64 index = read_input(&th->copyout_pos);
+ if (index >= kMaxCommands)
+ fail("result idx %lld overflows kMaxCommands", index);
+ char* addr = (char*)read_input(&th->copyout_pos);
+ uint64 size = read_input(&th->copyout_pos);
+ uint64 val = 0;
+ if (copyout(addr, size, &val)) {
+ results[index].executed = true;
+ results[index].val = val;
+ }
+ debug("copyout 0x%llx from %p\n", val, addr);
+ break;
+ }
+ default:
+ done = true;
+ break;
+ }
+ }
+ }
+ if (!collide && !th->colliding) {
+ uint32 reserrno = th->res != -1 ? 0 : th->reserrno;
+#if SYZ_EXECUTOR_USES_SHMEM
+ write_output(th->call_index);
+ write_output(th->call_num);
+ write_output(reserrno);
+ write_output(th->fault_injected);
+ uint32* signal_count_pos = write_output(0); // filled in later
+ uint32* cover_count_pos = write_output(0); // filled in later
+ uint32* comps_count_pos = write_output(0); // filled in later
+
+ if (flag_collect_comps) {
+ // Collect only the comparisons
+ uint32 ncomps = th->cov.size;
+ kcov_comparison_t* start = (kcov_comparison_t*)(th->cov.data + sizeof(uint64));
+ kcov_comparison_t* end = start + ncomps;
+ if ((char*)end > th->cov.data_end)
+ fail("too many comparisons %u", ncomps);
+ std::sort(start, end);
+ ncomps = std::unique(start, end) - start;
+ uint32 comps_size = 0;
+ for (uint32 i = 0; i < ncomps; ++i) {
+ if (start[i].ignore())
+ continue;
+ comps_size++;
+ start[i].write();
+ }
+ // Write out number of comparisons.
+ *comps_count_pos = comps_size;
+ } else if (flag_cover) {
+ if (is_kernel_64_bit)
+ write_coverage_signal<uint64>(th, signal_count_pos, cover_count_pos);
+ else
+ write_coverage_signal<uint32>(th, signal_count_pos, cover_count_pos);
+ }
+ debug("out #%u: index=%u num=%u errno=%d sig=%u cover=%u comps=%u\n",
+ completed, th->call_index, th->call_num, reserrno,
+ *signal_count_pos, *cover_count_pos, *comps_count_pos);
+ completed++;
+ write_completed(completed);
+#else
+ call_reply reply;
+ reply.header.magic = kOutMagic;
+ reply.header.done = 0;
+ reply.header.status = 0;
+ reply.call_index = th->call_index;
+ reply.call_num = th->call_num;
+ reply.reserrno = reserrno;
+ reply.fault_injected = th->fault_injected;
+ reply.signal_size = 0;
+ reply.cover_size = 0;
+ reply.comps_size = 0;
+ if (write(kOutPipeFd, &reply, sizeof(reply)) != sizeof(reply))
+ fail("control pipe call write failed");
+ debug("out: index=%u num=%u errno=%d\n", th->call_index, th->call_num, reserrno);
+#endif
+ }
+ th->handled = true;
+ running--;
+}
+
+void thread_create(thread_t* th, int id)
+{
+ th->created = true;
+ th->id = id;
+ th->handled = true;
+ event_init(&th->ready);
+ event_init(&th->done);
+ event_set(&th->done);
+ if (flag_threaded)
+ thread_start(worker_thread, th);
+}
+
+void* worker_thread(void* arg)
+{
+ thread_t* th = (thread_t*)arg;
+
+ if (flag_cover)
+ cover_enable(&th->cov, flag_collect_comps);
+ for (;;) {
+ event_wait(&th->ready);
+ event_reset(&th->ready);
+ execute_call(th);
+ event_set(&th->done);
+ }
+ return 0;
+}
+
+void execute_call(thread_t* th)
+{
+ const call_t* call = &syscalls[th->call_num];
+ debug("#%d: %s(", th->id, call->name);
+ for (int i = 0; i < th->num_args; i++) {
+ if (i != 0)
+ debug(", ");
+ debug("0x%lx", th->args[i]);
+ }
+ debug(")\n");
+
+ int fail_fd = -1;
+ if (flag_inject_fault && th->call_index == flag_fault_call) {
+ if (collide)
+ fail("both collide and fault injection are enabled");
+ debug("injecting fault into %d-th operation\n", flag_fault_nth);
+ fail_fd = inject_fault(flag_fault_nth);
+ }
+
+ if (flag_cover)
+ cover_reset(&th->cov);
+ errno = 0;
+ th->res = execute_syscall(call, th->args);
+ th->reserrno = errno;
+ if (th->res == -1 && th->reserrno == 0)
+ th->reserrno = EINVAL; // our syz syscalls may misbehave
+ if (flag_cover) {
+ cover_collect(&th->cov);
+ debug("#%d: read cover size = %u\n", th->id, th->cov.size);
+ if (th->cov.size >= kCoverSize)
+ fail("#%d: too much cover %u", th->id, th->cov.size);
+ }
+ th->fault_injected = false;
+
+ if (flag_inject_fault && th->call_index == flag_fault_call) {
+ th->fault_injected = fault_injected(fail_fd);
+ debug("fault injected: %d\n", th->fault_injected);
+ }
+
+ if (th->res == -1)
+ debug("#%d: %s = errno(%d)\n", th->id, call->name, th->reserrno);
+ else
+ debug("#%d: %s = 0x%lx\n", th->id, call->name, th->res);
+}
+
+#if SYZ_EXECUTOR_USES_SHMEM
+static uint32 hash(uint32 a)
+{
+ a = (a ^ 61) ^ (a >> 16);
+ a = a + (a << 3);
+ a = a ^ (a >> 4);
+ a = a * 0x27d4eb2d;
+ a = a ^ (a >> 15);
+ return a;
+}
+
+const uint32 dedup_table_size = 8 << 10;
+uint32 dedup_table[dedup_table_size];
+
+// Poorman's best-effort hashmap-based deduplication.
+// The hashmap is global which means that we deduplicate across different calls.
+// This is OK because we are interested only in new signals.
+static bool dedup(uint32 sig)
+{
+ for (uint32 i = 0; i < 4; i++) {
+ uint32 pos = (sig + i) % dedup_table_size;
+ if (dedup_table[pos] == sig)
+ return true;
+ if (dedup_table[pos] == 0) {
+ dedup_table[pos] = sig;
+ return false;
+ }
+ }
+ dedup_table[sig % dedup_table_size] = sig;
+ return false;
+}
+#endif
+
+void copyin(char* addr, uint64 val, uint64 size, uint64 bf, uint64 bf_off, uint64 bf_len)
+{
+ if (bf != binary_format_native && (bf_off != 0 || bf_len != 0))
+ fail("bitmask for string format %llu/%llu", bf_off, bf_len);
+ switch (bf) {
+ case binary_format_native:
+ NONFAILING(switch (size) {
+ case 1:
+ STORE_BY_BITMASK(uint8, addr, val, bf_off, bf_len);
+ break;
+ case 2:
+ STORE_BY_BITMASK(uint16, addr, val, bf_off, bf_len);
+ break;
+ case 4:
+ STORE_BY_BITMASK(uint32, addr, val, bf_off, bf_len);
+ break;
+ case 8:
+ STORE_BY_BITMASK(uint64, addr, val, bf_off, bf_len);
+ break;
+ default:
+ fail("copyin: bad argument size %llu", size);
+ });
+ break;
+ case binary_format_strdec:
+ if (size != 20)
+ fail("bad strdec size %llu", size);
+ NONFAILING(sprintf((char*)addr, "%020llu", val));
+ break;
+ case binary_format_strhex:
+ if (size != 18)
+ fail("bad strhex size %llu", size);
+ NONFAILING(sprintf((char*)addr, "0x%016llx", val));
+ break;
+ case binary_format_stroct:
+ if (size != 23)
+ fail("bad stroct size %llu", size);
+ NONFAILING(sprintf((char*)addr, "%023llo", val));
+ break;
+ default:
+ fail("unknown binary format %llu", bf);
+ }
+}
+
+bool copyout(char* addr, uint64 size, uint64* res)
+{
+ bool ok = false;
+ NONFAILING(
+ switch (size) {
+ case 1:
+ *res = *(uint8*)addr;
+ break;
+ case 2:
+ *res = *(uint16*)addr;
+ break;
+ case 4:
+ *res = *(uint32*)addr;
+ break;
+ case 8:
+ *res = *(uint64*)addr;
+ break;
+ default:
+ fail("copyout: bad argument size %llu", size);
+ } __atomic_store_n(&ok, true, __ATOMIC_RELEASE););
+ return ok;
+}
+
+uint64 read_arg(uint64** input_posp)
+{
+ uint64 typ = read_input(input_posp);
+ switch (typ) {
+ case arg_const: {
+ uint64 size, bf, bf_off, bf_len;
+ uint64 val = read_const_arg(input_posp, &size, &bf, &bf_off, &bf_len);
+ if (bf != binary_format_native)
+ fail("bad argument binary format %llu", bf);
+ if (bf_off != 0 || bf_len != 0)
+ fail("bad argument bitfield %llu/%llu", bf_off, bf_len);
+ return val;
+ }
+ case arg_result: {
+ uint64 meta = read_input(input_posp);
+ uint64 bf = meta >> 8;
+ if (bf != binary_format_native)
+ fail("bad result argument format %llu", bf);
+ return read_result(input_posp);
+ }
+ default:
+ fail("bad argument type %llu", typ);
+ }
+}
+
+uint64 read_const_arg(uint64** input_posp, uint64* size_p, uint64* bf_p, uint64* bf_off_p, uint64* bf_len_p)
+{
+ uint64 meta = read_input(input_posp);
+ uint64 val = read_input(input_posp);
+ *size_p = meta & 0xff;
+ uint64 bf = (meta >> 8) & 0xff;
+ *bf_off_p = (meta >> 16) & 0xff;
+ *bf_len_p = (meta >> 24) & 0xff;
+ uint64 pid_stride = meta >> 32;
+ val += pid_stride * procid;
+ if (bf == binary_format_bigendian) {
+ bf = binary_format_native;
+ switch (*size_p) {
+ case 2:
+ val = htobe16(val);
+ break;
+ case 4:
+ val = htobe32(val);
+ break;
+ case 8:
+ val = htobe64(val);
+ break;
+ default:
+ fail("bad big-endian int size %llu", *size_p);
+ }
+ }
+ *bf_p = bf;
+ return val;
+}
+
+uint64 read_result(uint64** input_posp)
+{
+ uint64 idx = read_input(input_posp);
+ uint64 op_div = read_input(input_posp);
+ uint64 op_add = read_input(input_posp);
+ uint64 arg = read_input(input_posp);
+ if (idx >= kMaxCommands)
+ fail("command refers to bad result %lld", idx);
+ if (results[idx].executed) {
+ arg = results[idx].val;
+ if (op_div != 0)
+ arg = arg / op_div;
+ arg += op_add;
+ }
+ return arg;
+}
+
+uint64 read_input(uint64** input_posp, bool peek)
+{
+ uint64* input_pos = *input_posp;
+ if ((char*)input_pos >= input_data + kMaxInput)
+ fail("input command overflows input %p: [%p:%p)", input_pos, input_data, input_data + kMaxInput);
+ if (!peek)
+ *input_posp = input_pos + 1;
+ return *input_pos;
+}
+
+#if SYZ_EXECUTOR_USES_SHMEM
+uint32* write_output(uint32 v)
+{
+ if (output_pos < output_data || (char*)output_pos >= (char*)output_data + kMaxOutput)
+ fail("output overflow: pos=%p region=[%p:%p]",
+ output_pos, output_data, (char*)output_data + kMaxOutput);
+ *output_pos = v;
+ return output_pos++;
+}
+
+void write_completed(uint32 completed)
+{
+ __atomic_store_n(output_data, completed, __ATOMIC_RELEASE);
+}
+#endif
+
+#if SYZ_EXECUTOR_USES_SHMEM
+void kcov_comparison_t::write()
+{
+ // Write order: type arg1 arg2 pc.
+ write_output((uint32)type);
+
+ // KCOV converts all arguments of size x first to uintx_t and then to
+ // uint64. We want to properly extend signed values, e.g we want
+ // int8 c = 0xfe to be represented as 0xfffffffffffffffe.
+ // Note that uint8 c = 0xfe will be represented the same way.
+ // This is ok because during hints processing we will anyways try
+ // the value 0x00000000000000fe.
+ switch (type & KCOV_CMP_SIZE_MASK) {
+ case KCOV_CMP_SIZE1:
+ arg1 = (uint64)(long long)(signed char)arg1;
+ arg2 = (uint64)(long long)(signed char)arg2;
+ break;
+ case KCOV_CMP_SIZE2:
+ arg1 = (uint64)(long long)(short)arg1;
+ arg2 = (uint64)(long long)(short)arg2;
+ break;
+ case KCOV_CMP_SIZE4:
+ arg1 = (uint64)(long long)(int)arg1;
+ arg2 = (uint64)(long long)(int)arg2;
+ break;
+ }
+ bool is_size_8 = (type & KCOV_CMP_SIZE_MASK) == KCOV_CMP_SIZE8;
+ if (!is_size_8) {
+ write_output((uint32)arg1);
+ write_output((uint32)arg2);
+ return;
+ }
+ // If we have 64 bits arguments then write them in Little-endian.
+ write_output((uint32)(arg1 & 0xFFFFFFFF));
+ write_output((uint32)(arg1 >> 32));
+ write_output((uint32)(arg2 & 0xFFFFFFFF));
+ write_output((uint32)(arg2 >> 32));
+}
+
+bool kcov_comparison_t::ignore() const
+{
+ // Comparisons with 0 are not interesting, fuzzer should be able to guess 0's without help.
+ if (arg1 == 0 && (arg2 == 0 || (type & KCOV_CMP_CONST)))
+ return true;
+ if ((type & KCOV_CMP_SIZE_MASK) == KCOV_CMP_SIZE8) {
+ // This can be a pointer (assuming 64-bit kernel).
+ // First of all, we want avert fuzzer from our output region.
+ // Without this fuzzer manages to discover and corrupt it.
+ uint64 out_start = (uint64)output_data;
+ uint64 out_end = out_start + kMaxOutput;
+ if (arg1 >= out_start && arg1 <= out_end)
+ return true;
+ if (arg2 >= out_start && arg2 <= out_end)
+ return true;
+#if defined(GOOS_linux)
+ // Filter out kernel physical memory addresses.
+ // These are internal kernel comparisons and should not be interesting.
+ // The range covers first 1TB of physical mapping.
+ uint64 kmem_start = (uint64)0xffff880000000000ull;
+ uint64 kmem_end = (uint64)0xffff890000000000ull;
+ bool kptr1 = arg1 >= kmem_start && arg1 <= kmem_end;
+ bool kptr2 = arg2 >= kmem_start && arg2 <= kmem_end;
+ if (kptr1 && kptr2)
+ return true;
+ if (kptr1 && arg2 == 0)
+ return true;
+ if (kptr2 && arg1 == 0)
+ return true;
+#endif
+ }
+ return false;
+}
+
+bool kcov_comparison_t::operator==(const struct kcov_comparison_t& other) const
+{
+ // We don't check for PC equality now, because it is not used.
+ return type == other.type && arg1 == other.arg1 && arg2 == other.arg2;
+}
+
+bool kcov_comparison_t::operator<(const struct kcov_comparison_t& other) const
+{
+ if (type != other.type)
+ return type < other.type;
+ if (arg1 != other.arg1)
+ return arg1 < other.arg1;
+ // We don't check for PC equality now, because it is not used.
+ return arg2 < other.arg2;
+}
+#endif
+
+void fail(const char* msg, ...)
+{
+ int e = errno;
+ va_list args;
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fprintf(stderr, " (errno %d)\n", e);
+ // ENOMEM/EAGAIN is frequent cause of failures in fuzzing context,
+ // so handle it here as non-fatal error.
+ doexit((e == ENOMEM || e == EAGAIN) ? kRetryStatus : kFailStatus);
+}
+
+void error(const char* msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+ doexit(kErrorStatus);
+}
+
+void exitf(const char* msg, ...)
+{
+ int e = errno;
+ va_list args;
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fprintf(stderr, " (errno %d)\n", e);
+ doexit(kRetryStatus);
+}
+
+void debug(const char* msg, ...)
+{
+ if (!flag_debug)
+ return;
+ va_list args;
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fflush(stderr);
+}
+
+void debug_dump_data(const char* data, int length)
+{
+ if (!flag_debug)
+ return;
+ for (int i = 0; i < length; i++) {
+ debug("%02x ", data[i] & 0xff);
+ if (i % 16 == 15)
+ debug("\n");
+ }
+ debug("\n");
+}