diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2022-01-19 17:38:24 +0000 |
|---|---|---|
| committer | Aleksandr Nogikh <wp32pw@gmail.com> | 2022-01-21 11:28:49 +0100 |
| commit | 214351e168def9426c79e1f65a93ddb112cee906 (patch) | |
| tree | 7bd960ca4a7dd2cf5d720d7f3159385ba0eb3fc1 /executor | |
| parent | ab3d9f17d3b73b74f89b4ea3bd951e09ab4149a8 (diff) | |
executor: fail on SEGV during clone()
As was found out in #2921, fork bombs are still possible in Linux-based
instances. One of the possible reasons is described below.
An invalid stack can be passed to the clone() call, thus causing it to stumble
on an invalid memory access right during returning from the clone() call. This
is in turn catched by the NONFAILING() macro and the control actually jumps
over it and eventually both the child and the parent continue executing the
same code.
Prevent it by handling SIGSEGV and SIGBUS differently during the clone process.
Co-authored-by: Andrei Vagin <avagin@google.com>
Diffstat (limited to 'executor')
| -rw-r--r-- | executor/common.h | 17 | ||||
| -rw-r--r-- | executor/common_linux.h | 12 | ||||
| -rw-r--r-- | executor/executor.cc | 1 | ||||
| -rw-r--r-- | executor/executor_linux.h | 11 |
4 files changed, 40 insertions, 1 deletions
diff --git a/executor/common.h b/executor/common.h index 74e75d33b..ddb33bec7 100644 --- a/executor/common.h +++ b/executor/common.h @@ -57,6 +57,11 @@ NORETURN void doexit(int status) for (;;) { } } +NORETURN void doexit_thread(int status) +{ + // For BSD systems, _exit seems to do exactly what's needed. + doexit(status); +} #endif #if SYZ_EXECUTOR || SYZ_MULTI_PROC || SYZ_REPEAT && SYZ_CGROUPS || \ @@ -76,6 +81,7 @@ static unsigned long long procid; #include <sys/syscall.h> #endif +static __thread int clone_ongoing; static __thread int skip_segv; static __thread jmp_buf segv_env; @@ -95,6 +101,17 @@ static void segv_handler(int sig, siginfo_t* info, void* ctx) // 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. + + if (__atomic_load_n(&clone_ongoing, __ATOMIC_RELAXED) != 0) { + // During clone, we always exit on a SEGV. If we do not, then + // it might prevent us from running child-specific code. E.g. + // if an invalid stack is passed to the clone() call, then it + // will trigger a seg fault, which in turn causes the child to + // jump over the NONFAILING macro and continue execution in + // parallel with the parent. + doexit_thread(sig); + } + uintptr_t addr = (uintptr_t)info->si_addr; const uintptr_t prog_start = 1 << 20; const uintptr_t prog_end = 100 << 20; diff --git a/executor/common_linux.h b/executor/common_linux.h index 520c13506..88c129f85 100644 --- a/executor/common_linux.h +++ b/executor/common_linux.h @@ -5226,8 +5226,12 @@ static long syz_80211_join_ibss(volatile long a0, volatile long a1, volatile lon static long handle_clone_ret(long ret) { - if (ret != 0) + if (ret != 0) { +#if SYZ_EXECUTOR || SYZ_HANDLE_SEGV + __atomic_store_n(&clone_ongoing, 0, __ATOMIC_RELAXED); +#endif return ret; + } // Exit if we're in the child process - not all kernels provide the proper means // to prevent fork-bombs. // But first sleep for some time. This will hopefully foster IPC fuzzing. @@ -5247,6 +5251,9 @@ static long syz_clone(volatile long flags, volatile long stack, volatile long st { // ABI requires 16-byte stack alignment. long sp = (stack + stack_len) & ~15; +#if SYZ_EXECUTOR || SYZ_HANDLE_SEGV + __atomic_store_n(&clone_ongoing, 1, __ATOMIC_RELAXED); +#endif // Clear the CLONE_VM flag. Otherwise it'll very likely corrupt syz-executor. long ret = (long)syscall(__NR_clone, flags & ~CLONE_VM, sp, ptid, ctid, tls); return handle_clone_ret(ret); @@ -5270,6 +5277,9 @@ static long syz_clone3(volatile long a0, volatile long a1) // As in syz_clone, clear the CLONE_VM flag. Flags are in the first 8-byte integer field. uint64* flags = (uint64*)&clone_args; *flags &= ~CLONE_VM; +#if SYZ_EXECUTOR || SYZ_HANDLE_SEGV + __atomic_store_n(&clone_ongoing, 1, __ATOMIC_RELAXED); +#endif return handle_clone_ret((long)syscall(__NR_clone3, &clone_args, copy_size)); } diff --git a/executor/executor.cc b/executor/executor.cc index 923fccc63..28ab72206 100644 --- a/executor/executor.cc +++ b/executor/executor.cc @@ -90,6 +90,7 @@ static NORETURN PRINTF(2, 3) void failmsg(const char* err, const char* msg, ...) // Just exit (e.g. due to temporal ENOMEM error). static NORETURN PRINTF(1, 2) void exitf(const char* msg, ...); static NORETURN void doexit(int status); +static NORETURN void doexit_thread(int status); // Print debug output that is visible when running syz-manager/execprog with -debug flag. // Debug output is supposed to be relatively high-level (syscalls executed, return values, timing, etc) diff --git a/executor/executor_linux.h b/executor/executor_linux.h index 8666d929b..3f422f6f2 100644 --- a/executor/executor_linux.h +++ b/executor/executor_linux.h @@ -224,6 +224,17 @@ NORETURN void doexit(int status) } } +// If we need to kill just a single thread (e.g. after cloning), exit_group is not +// the right choice - it will kill all threads, which might eventually lead to +// unnecessary SYZFAIL errors. +NORETURN void doexit_thread(int status) +{ + volatile unsigned i; + syscall(__NR_exit, status); + for (i = 0;; i++) { + } +} + #define SYZ_HAVE_FEATURES 1 static feature_t features[] = { {"leak", setup_leak}, |
