mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-27 05:10:43 +00:00
424dd8e7b5
We generally use the newer C99 var declarations combined with initialization because: - declarations are more local, reduced scope - fewer lines of code - less potential for using uninit vars and other bugs However, we have some relic code from times when we did not understand if we need to stick with C89 or not. Also some external contributions that don't follow style around. Add a static check for C89-style declarations and fix existing precedents. Akaros toolchain uses -std=gnu89 (or something) and does not allow variable declarations inside of for init statement. And we can't switch it to -std=c99 because Akaros headers are C89 themselves. So in common.h we need to declare loop counters outside of for.
752 lines
18 KiB
C
752 lines
18 KiB
C
// Copyright 2016 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.
|
|
|
|
// This file is shared between executor and csource package.
|
|
// csource does a bunch of transformations with this file:
|
|
// - unused parts are stripped using #if SYZ* defines
|
|
// - includes are hoisted to the top and deduplicated
|
|
// - comments and empty lines are stripped
|
|
// - NORETURN/PRINTF/debug are removed
|
|
// - exitf/fail are replaced with exit
|
|
// - uintN types are replaced with uintN_t
|
|
// - /*{{{FOO}}}*/ placeholders are replaced by actual values
|
|
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
|
|
#if GOOS_freebsd || GOOS_test && HOSTGOOS_freebsd
|
|
#include <sys/endian.h> // for htobe*.
|
|
#else
|
|
#include <endian.h> // for htobe*.
|
|
#endif
|
|
#include <stdint.h>
|
|
#include <stdio.h> // for fmt arguments
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if SYZ_TRACE
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR && !GOOS_linux
|
|
#include <unistd.h>
|
|
NORETURN void doexit(int status)
|
|
{
|
|
_exit(status);
|
|
for (;;) {
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_MULTI_PROC || SYZ_REPEAT && SYZ_CGROUPS || \
|
|
SYZ_NET_DEVICES || __NR_syz_mount_image || __NR_syz_read_part_table || \
|
|
__NR_syz_usb_connect || __NR_syz_usb_connect_ath9k || \
|
|
(GOOS_freebsd || GOOS_openbsd || GOOS_netbsd) && SYZ_NET_INJECTION
|
|
static unsigned long long procid;
|
|
#endif
|
|
|
|
#if !GOOS_fuchsia && !GOOS_windows
|
|
#if SYZ_EXECUTOR || SYZ_HANDLE_SEGV
|
|
#include <setjmp.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
|
|
#if GOOS_linux
|
|
#include <sys/syscall.h>
|
|
#endif
|
|
|
|
static __thread int skip_segv;
|
|
static __thread jmp_buf segv_env;
|
|
|
|
#if GOOS_akaros
|
|
#include <parlib/parlib.h>
|
|
static void recover(void)
|
|
{
|
|
_longjmp(segv_env, 1);
|
|
}
|
|
#endif
|
|
|
|
static void segv_handler(int sig, siginfo_t* info, void* ctx)
|
|
{
|
|
// Generated programs can contain bad (unmapped/protected) addresses,
|
|
// which cause SIGSEGVs during copyin/copyout.
|
|
// This handler ignores such crashes to allow the program to proceed.
|
|
// We additionally opportunistically check that the faulty address
|
|
// is not within executable data region, because such accesses can corrupt
|
|
// output region and then fuzzer will fail on corrupted data.
|
|
uintptr_t addr = (uintptr_t)info->si_addr;
|
|
const uintptr_t prog_start = 1 << 20;
|
|
const uintptr_t prog_end = 100 << 20;
|
|
int skip = __atomic_load_n(&skip_segv, __ATOMIC_RELAXED) != 0;
|
|
int valid = addr < prog_start || addr > prog_end;
|
|
#if GOOS_freebsd || (GOOS_test && HOSTGOOS_freebsd)
|
|
// FreeBSD delivers SIGBUS in response to any fault that isn't a page
|
|
// fault. In this case it tries to be helpful and sets si_addr to the
|
|
// address of the faulting instruction rather than zero as other
|
|
// operating systems seem to do. However, such faults should always be
|
|
// ignored.
|
|
if (sig == SIGBUS) {
|
|
valid = 1;
|
|
}
|
|
#endif
|
|
if (skip && valid) {
|
|
debug("SIGSEGV on %p, skipping\n", (void*)addr);
|
|
#if GOOS_akaros
|
|
struct user_context* uctx = (struct user_context*)ctx;
|
|
uctx->tf.hw_tf.tf_rip = (long)(void*)recover;
|
|
return;
|
|
#else
|
|
_longjmp(segv_env, 1);
|
|
#endif
|
|
}
|
|
debug("SIGSEGV on %p, exiting\n", (void*)addr);
|
|
doexit(sig);
|
|
}
|
|
|
|
static void install_segv_handler(void)
|
|
{
|
|
struct sigaction sa;
|
|
#if GOOS_linux
|
|
// Don't need that SIGCANCEL/SIGSETXID glibc stuff.
|
|
// SIGCANCEL sent to main thread causes it to exit
|
|
// without bringing down the whole group.
|
|
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);
|
|
#endif
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_sigaction = segv_handler;
|
|
sa.sa_flags = SA_NODEFER | SA_SIGINFO;
|
|
sigaction(SIGSEGV, &sa, NULL);
|
|
sigaction(SIGBUS, &sa, NULL);
|
|
}
|
|
|
|
#define NONFAILING(...) \
|
|
{ \
|
|
__atomic_fetch_add(&skip_segv, 1, __ATOMIC_SEQ_CST); \
|
|
if (_setjmp(segv_env) == 0) { \
|
|
__VA_ARGS__; \
|
|
} \
|
|
__atomic_fetch_sub(&skip_segv, 1, __ATOMIC_SEQ_CST); \
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if !GOOS_linux
|
|
#if (SYZ_EXECUTOR || SYZ_REPEAT) && SYZ_EXECUTOR_USES_FORK_SERVER
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
static void kill_and_wait(int pid, int* status)
|
|
{
|
|
kill(pid, SIGKILL);
|
|
while (waitpid(-1, status, 0) != pid) {
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if !GOOS_windows
|
|
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER || \
|
|
__NR_syz_usb_connect || __NR_syz_usb_connect_ath9k
|
|
static void sleep_ms(uint64 ms)
|
|
{
|
|
usleep(ms * 1000);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER || \
|
|
SYZ_LEAK
|
|
#include <time.h>
|
|
|
|
static uint64 current_time_ms(void)
|
|
{
|
|
struct timespec ts;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts))
|
|
fail("clock_gettime failed");
|
|
return (uint64)ts.tv_sec * 1000 + (uint64)ts.tv_nsec / 1000000;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_ANDROID || SYZ_USE_TMP_DIR
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
static void use_temporary_dir(void)
|
|
{
|
|
#if SYZ_SANDBOX_ANDROID
|
|
char tmpdir_template[] = "/data/data/syzkaller/syzkaller.XXXXXX";
|
|
#elif GOOS_fuchsia
|
|
char tmpdir_template[] = "/tmp/syzkaller.XXXXXX";
|
|
#else
|
|
char tmpdir_template[] = "./syzkaller.XXXXXX";
|
|
#endif
|
|
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
|
|
#endif
|
|
|
|
#if GOOS_akaros || GOOS_netbsd || GOOS_freebsd || GOOS_openbsd || GOOS_test
|
|
#if (SYZ_EXECUTOR || SYZ_REPEAT) && SYZ_EXECUTOR_USES_FORK_SERVER && (SYZ_EXECUTOR || SYZ_USE_TMP_DIR)
|
|
#include <dirent.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
static void remove_dir(const char* dir)
|
|
{
|
|
DIR* dp = opendir(dir);
|
|
if (dp == NULL)
|
|
exitf("opendir(%s) failed", dir);
|
|
struct dirent* ep = 0;
|
|
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
|
|
#endif
|
|
|
|
#if !GOOS_linux && !GOOS_netbsd
|
|
#if SYZ_EXECUTOR
|
|
static int inject_fault(int nth)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR
|
|
static int fault_injected(int fail_fd)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if !GOOS_windows
|
|
#if SYZ_EXECUTOR || SYZ_THREADED
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
|
|
static void thread_start(void* (*fn)(void*), void* arg)
|
|
{
|
|
pthread_t th;
|
|
pthread_attr_t attr;
|
|
pthread_attr_init(&attr);
|
|
pthread_attr_setstacksize(&attr, 128 << 10);
|
|
// Clone can fail spuriously with EAGAIN if there is a concurrent execve in progress.
|
|
// (see linux kernel commit 498052bba55ec). But it can also be a true limit imposed by cgroups.
|
|
// In one case we want to retry infinitely, in another -- fail immidiately...
|
|
int i = 0;
|
|
for (; i < 100; i++) {
|
|
if (pthread_create(&th, &attr, fn, arg) == 0) {
|
|
pthread_attr_destroy(&attr);
|
|
return;
|
|
}
|
|
if (errno == EAGAIN) {
|
|
usleep(50);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
exitf("pthread_create failed");
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
|
|
#if GOOS_freebsd || GOOS_netbsd || GOOS_openbsd || GOOS_akaros || GOOS_test
|
|
#if SYZ_EXECUTOR || SYZ_THREADED
|
|
|
|
#include <pthread.h>
|
|
#include <time.h>
|
|
|
|
typedef struct {
|
|
pthread_mutex_t mu;
|
|
pthread_cond_t cv;
|
|
int state;
|
|
} event_t;
|
|
|
|
static void event_init(event_t* ev)
|
|
{
|
|
if (pthread_mutex_init(&ev->mu, 0))
|
|
exitf("pthread_mutex_init failed");
|
|
if (pthread_cond_init(&ev->cv, 0))
|
|
exitf("pthread_cond_init failed");
|
|
ev->state = 0;
|
|
}
|
|
|
|
static void event_reset(event_t* ev)
|
|
{
|
|
ev->state = 0;
|
|
}
|
|
|
|
static void event_set(event_t* ev)
|
|
{
|
|
pthread_mutex_lock(&ev->mu);
|
|
if (ev->state)
|
|
fail("event already set");
|
|
ev->state = 1;
|
|
pthread_mutex_unlock(&ev->mu);
|
|
pthread_cond_broadcast(&ev->cv);
|
|
}
|
|
|
|
static void event_wait(event_t* ev)
|
|
{
|
|
pthread_mutex_lock(&ev->mu);
|
|
while (!ev->state)
|
|
pthread_cond_wait(&ev->cv, &ev->mu);
|
|
pthread_mutex_unlock(&ev->mu);
|
|
}
|
|
|
|
static int event_isset(event_t* ev)
|
|
{
|
|
pthread_mutex_lock(&ev->mu);
|
|
int res = ev->state;
|
|
pthread_mutex_unlock(&ev->mu);
|
|
return res;
|
|
}
|
|
|
|
static int event_timedwait(event_t* ev, uint64 timeout)
|
|
{
|
|
uint64 start = current_time_ms();
|
|
uint64 now = start;
|
|
pthread_mutex_lock(&ev->mu);
|
|
for (;;) {
|
|
if (ev->state)
|
|
break;
|
|
uint64 remain = timeout - (now - start);
|
|
struct timespec ts;
|
|
ts.tv_sec = remain / 1000;
|
|
ts.tv_nsec = (remain % 1000) * 1000 * 1000;
|
|
pthread_cond_timedwait(&ev->cv, &ev->mu, &ts);
|
|
now = current_time_ms();
|
|
if (now - start > timeout)
|
|
break;
|
|
}
|
|
int res = ev->state;
|
|
pthread_mutex_unlock(&ev->mu);
|
|
return res;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_USE_BITMASKS
|
|
#define BITMASK(bf_off, bf_len) (((1ull << (bf_len)) - 1) << (bf_off))
|
|
#define STORE_BY_BITMASK(type, htobe, addr, val, bf_off, bf_len) \
|
|
*(type*)(addr) = htobe((htobe(*(type*)(addr)) & ~BITMASK((bf_off), (bf_len))) | \
|
|
(((type)(val) << (bf_off)) & BITMASK((bf_off), (bf_len))))
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_USE_CHECKSUMS
|
|
struct csum_inet {
|
|
uint32 acc;
|
|
};
|
|
|
|
static void csum_inet_init(struct csum_inet* csum)
|
|
{
|
|
csum->acc = 0;
|
|
}
|
|
|
|
static void csum_inet_update(struct csum_inet* csum, const uint8* data, size_t length)
|
|
{
|
|
if (length == 0)
|
|
return;
|
|
|
|
size_t i = 0;
|
|
for (; i < length - 1; i += 2)
|
|
csum->acc += *(uint16*)&data[i];
|
|
|
|
if (length & 1)
|
|
csum->acc += le16toh((uint16)data[length - 1]);
|
|
|
|
while (csum->acc > 0xffff)
|
|
csum->acc = (csum->acc & 0xffff) + (csum->acc >> 16);
|
|
}
|
|
|
|
static uint16 csum_inet_digest(struct csum_inet* csum)
|
|
{
|
|
return ~csum->acc;
|
|
}
|
|
#endif
|
|
|
|
#if GOOS_akaros
|
|
#include "common_akaros.h"
|
|
#elif GOOS_freebsd || GOOS_netbsd || GOOS_openbsd
|
|
#include "common_bsd.h"
|
|
#elif GOOS_fuchsia
|
|
#include "common_fuchsia.h"
|
|
#elif GOOS_linux
|
|
#include "common_linux.h"
|
|
#elif GOOS_test
|
|
#include "common_test.h"
|
|
#elif GOOS_windows
|
|
#include "common_windows.h"
|
|
#else
|
|
#error "unknown OS"
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_execute_func
|
|
// syz_execute_func(text ptr[in, text[taget]])
|
|
static long syz_execute_func(volatile long text)
|
|
{
|
|
// Here we just to random code which is inherently unsafe.
|
|
// But we only care about coverage in the output region.
|
|
// The following code tries to remove left-over pointers in registers
|
|
// from the reach of the random code, otherwise it's known to reach
|
|
// the output region somehow. The asm block is arch-independent except
|
|
// for the number of available registers.
|
|
volatile long p[8] = {0};
|
|
(void)p;
|
|
#if GOARCH_amd64
|
|
asm volatile("" ::"r"(0l), "r"(1l), "r"(2l), "r"(3l), "r"(4l), "r"(5l), "r"(6l),
|
|
"r"(7l), "r"(8l), "r"(9l), "r"(10l), "r"(11l), "r"(12l), "r"(13l));
|
|
#endif
|
|
((void (*)(void))(text))();
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_THREADED
|
|
struct thread_t {
|
|
int created, call;
|
|
event_t ready, done;
|
|
};
|
|
|
|
static struct thread_t threads[16];
|
|
static void execute_call(int call);
|
|
static int running;
|
|
|
|
static void* thr(void* arg)
|
|
{
|
|
struct thread_t* th = (struct thread_t*)arg;
|
|
for (;;) {
|
|
event_wait(&th->ready);
|
|
event_reset(&th->ready);
|
|
execute_call(th->call);
|
|
__atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED);
|
|
event_set(&th->done);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if SYZ_REPEAT
|
|
static void execute_one(void)
|
|
#else
|
|
static void loop(void)
|
|
#endif
|
|
{
|
|
#if SYZ_REPRO
|
|
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {
|
|
}
|
|
#endif
|
|
#if SYZ_TRACE
|
|
fprintf(stderr, "### start\n");
|
|
#endif
|
|
int i, call, thread;
|
|
#if SYZ_COLLIDE
|
|
int collide = 0;
|
|
again:
|
|
#endif
|
|
for (call = 0; call < /*{{{NUM_CALLS}}}*/; call++) {
|
|
for (thread = 0; thread < (int)(sizeof(threads) / sizeof(threads[0])); thread++) {
|
|
struct thread_t* th = &threads[thread];
|
|
if (!th->created) {
|
|
th->created = 1;
|
|
event_init(&th->ready);
|
|
event_init(&th->done);
|
|
event_set(&th->done);
|
|
thread_start(thr, th);
|
|
}
|
|
if (!event_isset(&th->done))
|
|
continue;
|
|
event_reset(&th->done);
|
|
th->call = call;
|
|
__atomic_fetch_add(&running, 1, __ATOMIC_RELAXED);
|
|
event_set(&th->ready);
|
|
#if SYZ_COLLIDE
|
|
if (collide && (call % 2) == 0)
|
|
break;
|
|
#endif
|
|
event_timedwait(&th->done, /*{{{CALL_TIMEOUT}}}*/);
|
|
break;
|
|
}
|
|
}
|
|
for (i = 0; i < 100 && __atomic_load_n(&running, __ATOMIC_RELAXED); i++)
|
|
sleep_ms(1);
|
|
#if SYZ_HAVE_CLOSE_FDS
|
|
close_fds();
|
|
#endif
|
|
#if SYZ_COLLIDE
|
|
if (!collide) {
|
|
collide = 1;
|
|
goto again;
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_REPEAT
|
|
static void execute_one(void);
|
|
#if SYZ_EXECUTOR_USES_FORK_SERVER
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
#if GOOS_linux
|
|
#define WAIT_FLAGS __WALL
|
|
#else
|
|
#define WAIT_FLAGS 0
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR
|
|
static void reply_handshake();
|
|
#endif
|
|
|
|
static void loop(void)
|
|
{
|
|
#if SYZ_HAVE_SETUP_LOOP
|
|
setup_loop();
|
|
#endif
|
|
#if SYZ_EXECUTOR
|
|
// Tell parent that we are ready to serve.
|
|
reply_handshake();
|
|
#endif
|
|
#if SYZ_EXECUTOR && GOOS_akaros
|
|
// For akaros we do exec in the child process because new threads can't be created in the fork child.
|
|
// Thus we proxy input program over the child_pipe to the child process.
|
|
int child_pipe[2];
|
|
if (pipe(child_pipe))
|
|
fail("pipe failed");
|
|
#endif
|
|
int iter = 0;
|
|
#if SYZ_REPEAT_TIMES
|
|
for (; iter < /*{{{REPEAT_TIMES}}}*/; iter++) {
|
|
#else
|
|
for (;; iter++) {
|
|
#endif
|
|
#if SYZ_EXECUTOR || 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 SYZ_HAVE_RESET_LOOP
|
|
reset_loop();
|
|
#endif
|
|
#if SYZ_EXECUTOR
|
|
receive_execute();
|
|
#endif
|
|
int pid = fork();
|
|
if (pid < 0)
|
|
fail("clone failed");
|
|
if (pid == 0) {
|
|
#if SYZ_EXECUTOR || SYZ_USE_TMP_DIR
|
|
if (chdir(cwdbuf))
|
|
fail("failed to chdir");
|
|
#endif
|
|
#if SYZ_HAVE_SETUP_TEST
|
|
setup_test();
|
|
#endif
|
|
#if GOOS_akaros
|
|
#if SYZ_EXECUTOR
|
|
dup2(child_pipe[0], kInPipeFd);
|
|
close(child_pipe[0]);
|
|
close(child_pipe[1]);
|
|
#endif
|
|
execl(program_name, program_name, "child", NULL);
|
|
fail("execl failed");
|
|
#else
|
|
#if SYZ_EXECUTOR
|
|
close(kInPipeFd);
|
|
#endif
|
|
#if SYZ_EXECUTOR && SYZ_EXECUTOR_USES_SHMEM
|
|
close(kOutPipeFd);
|
|
#endif
|
|
execute_one();
|
|
#if SYZ_HAVE_CLOSE_FDS && !SYZ_THREADED
|
|
close_fds();
|
|
#endif
|
|
doexit(0);
|
|
#endif
|
|
}
|
|
debug("spawned worker pid %d\n", pid);
|
|
|
|
#if SYZ_EXECUTOR && GOOS_akaros
|
|
resend_execute(child_pipe[1]);
|
|
#endif
|
|
// 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 SYZ_EXECUTOR && SYZ_EXECUTOR_USES_SHMEM
|
|
uint64 last_executed = start;
|
|
uint32 executed_calls = __atomic_load_n(output_data, __ATOMIC_RELAXED);
|
|
#endif
|
|
for (;;) {
|
|
if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid)
|
|
break;
|
|
sleep_ms(1);
|
|
#if SYZ_EXECUTOR && SYZ_EXECUTOR_USES_SHMEM
|
|
// 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 1s 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;
|
|
}
|
|
// TODO: adjust timeout for progs with syz_usb_connect call.
|
|
if ((now - start < 5 * 1000) && (now - start < 3 * 1000 || now - last_executed < 1000))
|
|
continue;
|
|
#else
|
|
if (current_time_ms() - start < 5 * 1000)
|
|
continue;
|
|
#endif
|
|
debug("killing hanging pid %d\n", pid);
|
|
kill_and_wait(pid, &status);
|
|
break;
|
|
}
|
|
#if SYZ_EXECUTOR
|
|
if (WEXITSTATUS(status) == kFailStatus) {
|
|
errno = 0;
|
|
fail("child failed");
|
|
}
|
|
reply_execute(0);
|
|
#endif
|
|
#if SYZ_EXECUTOR || SYZ_USE_TMP_DIR
|
|
remove_dir(cwdbuf);
|
|
#endif
|
|
#if SYZ_LEAK
|
|
// Note: this will fail under setuid sandbox because we don't have
|
|
// write permissions for the kmemleak file.
|
|
check_leaks();
|
|
#endif
|
|
}
|
|
}
|
|
#else
|
|
static void loop(void)
|
|
{
|
|
execute_one();
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if !SYZ_EXECUTOR
|
|
/*{{{SYSCALL_DEFINES}}}*/
|
|
|
|
/*{{{RESULTS}}}*/
|
|
|
|
#if SYZ_THREADED || SYZ_REPEAT || SYZ_SANDBOX_NONE || SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID
|
|
#if SYZ_THREADED
|
|
void execute_call(int call)
|
|
#elif SYZ_REPEAT
|
|
void execute_one(void)
|
|
#else
|
|
void loop(void)
|
|
#endif
|
|
{
|
|
/*{{{SYSCALLS}}}*/
|
|
#if SYZ_HAVE_CLOSE_FDS && !SYZ_THREADED && !SYZ_REPEAT
|
|
close_fds();
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
// This is the main function for csource.
|
|
#if GOOS_akaros && SYZ_REPEAT
|
|
#include <string.h>
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
/*{{{MMAP_DATA}}}*/
|
|
|
|
program_name = argv[0];
|
|
if (argc == 2 && strcmp(argv[1], "child") == 0)
|
|
child();
|
|
#else
|
|
int main(void)
|
|
{
|
|
/*{{{MMAP_DATA}}}*/
|
|
#endif
|
|
|
|
#if SYZ_BINFMT_MISC
|
|
setup_binfmt_misc();
|
|
#endif
|
|
#if SYZ_LEAK
|
|
setup_leak();
|
|
#endif
|
|
#if SYZ_FAULT
|
|
setup_fault();
|
|
#endif
|
|
#if SYZ_KCSAN
|
|
setup_kcsan();
|
|
#endif
|
|
#if SYZ_USB
|
|
setup_usb();
|
|
#endif
|
|
|
|
#if SYZ_HANDLE_SEGV
|
|
install_segv_handler();
|
|
#endif
|
|
#if SYZ_MULTI_PROC
|
|
for (procid = 0; procid < /*{{{PROCS}}}*/; procid++) {
|
|
if (fork() == 0) {
|
|
#endif
|
|
#if SYZ_USE_TMP_DIR || SYZ_SANDBOX_ANDROID
|
|
use_temporary_dir();
|
|
#endif
|
|
/*{{{SANDBOX_FUNC}}}*/
|
|
#if SYZ_HAVE_CLOSE_FDS && !SYZ_THREADED && !SYZ_REPEAT && !SYZ_SANDBOX_NONE && \
|
|
!SYZ_SANDBOX_SETUID && !SYZ_SANDBOX_NAMESPACE && !SYZ_SANDBOX_ANDROID
|
|
close_fds();
|
|
#endif
|
|
#if SYZ_MULTI_PROC
|
|
}
|
|
}
|
|
sleep(1000000);
|
|
#endif
|
|
#if !SYZ_MULTI_PROC && !SYZ_REPEAT && SYZ_LEAK
|
|
check_leaks();
|
|
#endif
|
|
return 0;
|
|
}
|
|
#endif
|