executor: implement support for leak checking

Leak checking support was half done and did not really work.
This is heavy-lifting to make it work.

1. Move leak/fault setup into executor.
pkg/host was a wrong place for them because we need then in C repros too.
The pkg/host periodic callback functionality did not work too,
we need it in executor so that we can reuse it in C repros too.
Remove setup/callback functions in pkg/host entirely.

2. Do leak setup/checking in C repros.
The way leak checking is invoked is slightly different from fuzzer,
but much better then no support at all.
At least the checking code is shared.

3. Add Leak option to pkg/csource and -leak flag to syz-prog2c.

4. Don't enalbe leak checking in fuzzer while we are triaging initial corpus.
It's toooo slow.

5. Fix pkg/repro to do something more sane for leak bugs.

Few other minor fixes here and there.
This commit is contained in:
Dmitry Vyukov 2019-05-18 17:54:03 +02:00
parent 7b3084af2e
commit 8285069f89
16 changed files with 513 additions and 280 deletions

View File

@ -145,7 +145,8 @@ static void sleep_ms(uint64 ms)
}
#endif
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER || \
SYZ_ENABLE_LEAK
#include <time.h>
static uint64 current_time_ms(void)
@ -218,7 +219,7 @@ static void remove_dir(const char* dir)
#endif
#if !GOOS_linux
#if SYZ_EXECUTOR || SYZ_FAULT_INJECTION
#if SYZ_EXECUTOR
static int inject_fault(int nth)
{
return 0;
@ -637,6 +638,11 @@ static void loop(void)
#endif
#if SYZ_EXECUTOR || SYZ_USE_TMP_DIR
remove_dir(cwdbuf);
#endif
#if SYZ_ENABLE_LEAK
// Note: this will fail under setuid sandbox because we don't have
// write permissions for the kmemleak file.
check_leaks();
#endif
}
}
@ -686,6 +692,16 @@ int main(void)
/*MMAP_DATA*/
#endif
#if SYZ_ENABLE_BINFMT_MISC
setup_binfmt_misc();
#endif
#if SYZ_ENABLE_LEAK
setup_leak();
#endif
#if SYZ_FAULT_INJECTION
setup_fault();
#endif
#if SYZ_HANDLE_SEGV
install_segv_handler();
#endif
@ -705,6 +721,9 @@ int main(void)
}
}
sleep(1000000);
#endif
#if !SYZ_PROCS && !SYZ_REPEAT && SYZ_ENABLE_LEAK
check_leaks();
#endif
return 0;
}

View File

@ -71,7 +71,8 @@ static int event_timedwait(event_t* ev, uint64 timeout)
#endif
#if SYZ_EXECUTOR || SYZ_REPEAT || SYZ_TUN_ENABLE || SYZ_FAULT_INJECTION || SYZ_SANDBOX_NONE || \
SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP
SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP || \
SYZ_FAULT_INJECTION || SYZ_ENABLE_LEAK || SYZ_ENABLE_BINFMT_MISC
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
@ -1868,26 +1869,6 @@ void initialize_cgroups()
#endif
#endif
#if SYZ_EXECUTOR || (SYZ_ENABLE_BINFMT_MISC && (SYZ_SANDBOX_NONE || SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP))
#include <fcntl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
static void setup_binfmt_misc()
{
#if SYZ_EXECUTOR
if (!flag_enable_binfmt_misc)
return;
#endif
if (mount(0, "/proc/sys/fs/binfmt_misc", "binfmt_misc", 0, 0)) {
debug("mount(binfmt_misc) failed: %d\n", errno);
}
write_file("/proc/sys/fs/binfmt_misc/register", ":syz0:M:0:\x01::./file0:");
write_file("/proc/sys/fs/binfmt_misc/register", ":syz1:M:1:\x02::./file0:POC");
}
#endif
#if SYZ_EXECUTOR || SYZ_SANDBOX_NONE || SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP
#include <errno.h>
#include <sys/mount.h>
@ -1900,9 +1881,6 @@ static void setup_common()
#if SYZ_EXECUTOR || SYZ_ENABLE_CGROUPS
setup_cgroups();
#endif
#if SYZ_EXECUTOR || SYZ_ENABLE_BINFMT_MISC
setup_binfmt_misc();
#endif
}
#include <sched.h>
@ -2475,10 +2453,6 @@ retry:
static int inject_fault(int nth)
{
#if SYZ_EXECUTOR
if (!flag_enable_fault_injection)
return 0;
#endif
int fd;
fd = open("/proc/thread-self/fail-nth", O_RDWR);
// We treat errors here as temporal/non-critical because we see
@ -2497,8 +2471,6 @@ static int inject_fault(int nth)
#if SYZ_EXECUTOR
static int fault_injected(int fail_fd)
{
if (!flag_enable_fault_injection)
return 0;
char buf[16];
int n = read(fail_fd, buf, sizeof(buf) - 1);
if (n <= 0)
@ -2646,3 +2618,153 @@ static void close_fds()
close(fd);
}
#endif
#if SYZ_EXECUTOR || SYZ_FAULT_INJECTION
#include <errno.h>
static void setup_fault()
{
static struct {
const char* file;
const char* val;
bool fatal;
} files[] = {
{"/sys/kernel/debug/failslab/ignore-gfp-wait", "N", true},
// These are enabled by separate configs (e.g. CONFIG_FAIL_FUTEX)
// and we did not check all of them in host.checkFaultInjection, so we ignore errors.
{"/sys/kernel/debug/fail_futex/ignore-private", "N", false},
{"/sys/kernel/debug/fail_page_alloc/ignore-gfp-highmem", "N", false},
{"/sys/kernel/debug/fail_page_alloc/ignore-gfp-wait", "N", false},
{"/sys/kernel/debug/fail_page_alloc/min-order", "0", false},
};
unsigned i;
for (i = 0; i < sizeof(files) / sizeof(files[0]); i++) {
if (!write_file(files[i].file, files[i].val)) {
debug("failed to write %s: %d\n", files[i].file, errno);
if (files[i].fatal)
fail("failed to write %s", files[i].file);
}
}
}
#endif
#if SYZ_EXECUTOR || SYZ_ENABLE_LEAK
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#define KMEMLEAK_FILE "/sys/kernel/debug/kmemleak"
static void setup_leak()
{
// Flush boot leaks.
if (!write_file(KMEMLEAK_FILE, "scan"))
fail("failed to write %s", KMEMLEAK_FILE);
sleep(5); // account for MSECS_MIN_AGE
if (!write_file(KMEMLEAK_FILE, "scan"))
fail("failed to write %s", KMEMLEAK_FILE);
if (!write_file(KMEMLEAK_FILE, "clear"))
fail("failed to write %s", KMEMLEAK_FILE);
}
#define SYZ_HAVE_LEAK_CHECK 1
#if SYZ_EXECUTOR
static void check_leaks(char** frames, int nframes)
#else
static void check_leaks(void)
#endif
{
int fd = open(KMEMLEAK_FILE, O_RDWR);
if (fd == -1)
fail("failed to open(\"%s\")", KMEMLEAK_FILE);
// KMEMLEAK has false positives. To mitigate most of them, it checksums
// potentially leaked objects, and reports them only on the next scan
// iff the checksum does not change. Because of that we do the following
// intricate dance:
// Scan, sleep, scan again. At this point we can get some leaks.
// If there are leaks, we sleep and scan again, this can remove
// false leaks. Then, read kmemleak again. If we get leaks now, then
// hopefully these are true positives during the previous testing cycle.
uint64 start = current_time_ms();
if (write(fd, "scan", 4) != 4)
fail("failed to write(%s, \"scan\")", KMEMLEAK_FILE);
sleep(1);
// Account for MSECS_MIN_AGE
// (1 second less because scanning will take at least a second).
while (current_time_ms() - start < 4 * 1000)
sleep(1);
if (write(fd, "scan", 4) != 4)
fail("failed to write(%s, \"scan\")", KMEMLEAK_FILE);
static char buf[128 << 10];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n < 0)
fail("failed to read(%s)", KMEMLEAK_FILE);
#if SYZ_EXECUTOR
int nleaks = 0;
#endif
if (n != 0) {
sleep(1);
if (write(fd, "scan", 4) != 4)
fail("failed to write(%s, \"scan\")", KMEMLEAK_FILE);
if (lseek(fd, 0, SEEK_SET) < 0)
fail("failed to lseek(%s)", KMEMLEAK_FILE);
n = read(fd, buf, sizeof(buf) - 1);
if (n < 0)
fail("failed to read(%s)", KMEMLEAK_FILE);
buf[n] = 0;
char* pos = buf;
char* end = buf + n;
while (pos < end) {
char* next = strstr(pos + 1, "unreferenced object");
if (!next)
next = end;
char prev = *next;
*next = 0;
#if SYZ_EXECUTOR
int f;
for (f = 0; f < nframes; f++) {
if (strstr(pos, frames[f]))
break;
}
if (f != nframes) {
*next = prev;
pos = next;
continue;
}
#endif
// BUG in output should be recognized by manager.
fprintf(stderr, "BUG: memory leak\n%s\n", pos);
*next = prev;
pos = next;
#if SYZ_EXECUTOR
nleaks++;
#endif
}
}
if (write(fd, "clear", 5) != 5)
fail("failed to write(%s, \"clear\")", KMEMLEAK_FILE);
close(fd);
#if SYZ_EXECUTOR
if (nleaks)
doexit(1);
#endif
}
#endif
#if SYZ_EXECUTOR || SYZ_ENABLE_BINFMT_MISC
#include <fcntl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
static void setup_binfmt_misc()
{
if (mount(0, "/proc/sys/fs/binfmt_misc", "binfmt_misc", 0, 0)) {
debug("mount(binfmt_misc) failed: %d\n", errno);
}
write_file("/proc/sys/fs/binfmt_misc/register", ":syz0:M:0:\x01::./file0:");
write_file("/proc/sys/fs/binfmt_misc/register", ":syz1:M:1:\x02::./file0:POC");
}
#endif

View File

@ -113,12 +113,10 @@ static bool flag_debug;
static bool flag_cover;
static sandbox_type flag_sandbox;
static bool flag_extra_cover;
static bool flag_enable_fault_injection;
static bool flag_enable_tun;
static bool flag_enable_net_dev;
static bool flag_enable_net_reset;
static bool flag_enable_cgroups;
static bool flag_enable_binfmt_misc;
static bool flag_enable_close_fds;
static bool flag_collect_cover;
@ -287,6 +285,11 @@ struct kcov_comparison_t {
bool operator<(const struct kcov_comparison_t& other) const;
};
struct feature_t {
const char* name;
void (*setup)();
};
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 copyout_call_results(thread_t* th);
@ -303,6 +306,7 @@ static uint64 swap(uint64 v, uint64 size, uint64 bf);
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();
static void setup_features(char** enable, int n);
#include "syscalls.h"
@ -330,6 +334,18 @@ int main(int argc, char** argv)
puts(GOOS " " GOARCH " " SYZ_REVISION " " GIT_REVISION);
return 0;
}
if (argc >= 2 && strcmp(argv[1], "setup") == 0) {
setup_features(argv + 2, argc - 2);
return 0;
}
if (argc >= 2 && strcmp(argv[1], "leak") == 0) {
#if SYZ_HAVE_LEAK_CHECK
check_leaks(argv + 2, argc - 2);
#else
fail("leak checking is not implemented");
#endif
return 0;
}
if (argc == 2 && strcmp(argv[1], "test") == 0)
return run_tests();
@ -449,13 +465,11 @@ void parse_env_flags(uint64 flags)
else if (flags & (1 << 4))
flag_sandbox = sandbox_android_untrusted_app;
flag_extra_cover = flags & (1 << 5);
flag_enable_fault_injection = flags & (1 << 6);
flag_enable_tun = flags & (1 << 7);
flag_enable_net_dev = flags & (1 << 8);
flag_enable_net_reset = flags & (1 << 9);
flag_enable_cgroups = flags & (1 << 10);
flag_enable_binfmt_misc = flags & (1 << 11);
flag_enable_close_fds = flags & (1 << 12);
flag_enable_tun = flags & (1 << 6);
flag_enable_net_dev = flags & (1 << 7);
flag_enable_net_reset = flags & (1 << 8);
flag_enable_cgroups = flags & (1 << 9);
flag_enable_close_fds = flags & (1 << 10);
}
#if SYZ_EXECUTOR_USES_FORK_SERVER
@ -1359,6 +1373,26 @@ bool kcov_comparison_t::operator<(const struct kcov_comparison_t& other) const
}
#endif
void setup_features(char** enable, int n)
{
// This does any one-time setup for the requested features on the machine.
// Note: this can be called multiple times and must be idempotent.
for (int i = 0; i < n; i++) {
bool found = false;
#if SYZ_HAVE_FEATURES
for (unsigned f = 0; f < sizeof(features) / sizeof(features[0]); f++) {
if (strcmp(enable[i], features[f].name) == 0) {
features[f].setup();
found = true;
break;
}
}
#endif
if (!found)
fail("unknown feature %s", enable[i]);
}
}
void fail(const char* msg, ...)
{
int e = errno;

View File

@ -167,3 +167,10 @@ NORETURN void doexit(int status)
for (i = 0;; i++) {
}
}
#define SYZ_HAVE_FEATURES 1
static feature_t features[] = {
{"leak", setup_leak},
{"fault", setup_fault},
{"binfmt_misc", setup_binfmt_misc},
};

View File

@ -8,6 +8,7 @@ package csource
import (
"bytes"
"fmt"
"regexp"
"runtime"
"sort"
"strings"
@ -61,6 +62,7 @@ func createCommonHeader(p, mmapProg *prog.Prog, replacements map[string]string,
} {
src = bytes.Replace(src, []byte(from), []byte(to), -1)
}
src = regexp.MustCompile("#define SYZ_HAVE_.*").ReplaceAll(src, nil)
return src, nil
}
@ -84,6 +86,7 @@ func defineList(p, mmapProg *prog.Prog, opts Options) (defines []string) {
"SYZ_REPEAT_TIMES": opts.RepeatTimes > 1,
"SYZ_PROCS": opts.Procs > 1,
"SYZ_FAULT_INJECTION": opts.Fault,
"SYZ_ENABLE_LEAK": opts.Leak,
"SYZ_TUN_ENABLE": opts.EnableTun,
"SYZ_ENABLE_CGROUPS": opts.EnableCgroups,
"SYZ_ENABLE_NETDEV": opts.EnableNetDev,

View File

@ -174,12 +174,6 @@ func (ctx *context) generateCalls(p prog.ExecProg, trace bool) ([]string, []uint
}
if ctx.opts.Fault && ctx.opts.FaultCall == ci {
// Note: these files are also hardcoded in pkg/host/host_linux.go.
fmt.Fprintf(w, "\twrite_file(\"/sys/kernel/debug/failslab/ignore-gfp-wait\", \"N\");\n")
fmt.Fprintf(w, "\twrite_file(\"/sys/kernel/debug/fail_futex/ignore-private\", \"N\");\n")
fmt.Fprintf(w, "\twrite_file(\"/sys/kernel/debug/fail_page_alloc/ignore-gfp-highmem\", \"N\");\n")
fmt.Fprintf(w, "\twrite_file(\"/sys/kernel/debug/fail_page_alloc/ignore-gfp-wait\", \"N\");\n")
fmt.Fprintf(w, "\twrite_file(\"/sys/kernel/debug/fail_page_alloc/min-order\", \"0\");\n")
fmt.Fprintf(w, "\tinject_fault(%v);\n", ctx.opts.FaultNth)
}
// Call itself.

View File

@ -130,7 +130,8 @@ static void sleep_ms(uint64 ms)
}
#endif
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER || \
SYZ_ENABLE_LEAK
#include <time.h>
static uint64 current_time_ms(void)
@ -203,7 +204,7 @@ static void remove_dir(const char* dir)
#endif
#if !GOOS_linux
#if SYZ_EXECUTOR || SYZ_FAULT_INJECTION
#if SYZ_EXECUTOR
static int inject_fault(int nth)
{
return 0;
@ -1072,7 +1073,8 @@ static int event_timedwait(event_t* ev, uint64 timeout)
#endif
#if SYZ_EXECUTOR || SYZ_REPEAT || SYZ_TUN_ENABLE || SYZ_FAULT_INJECTION || SYZ_SANDBOX_NONE || \
SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP
SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP || \
SYZ_FAULT_INJECTION || SYZ_ENABLE_LEAK || SYZ_ENABLE_BINFMT_MISC
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
@ -4156,26 +4158,6 @@ void initialize_cgroups()
#endif
#endif
#if SYZ_EXECUTOR || (SYZ_ENABLE_BINFMT_MISC && (SYZ_SANDBOX_NONE || SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP))
#include <fcntl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
static void setup_binfmt_misc()
{
#if SYZ_EXECUTOR
if (!flag_enable_binfmt_misc)
return;
#endif
if (mount(0, "/proc/sys/fs/binfmt_misc", "binfmt_misc", 0, 0)) {
debug("mount(binfmt_misc) failed: %d\n", errno);
}
write_file("/proc/sys/fs/binfmt_misc/register", ":syz0:M:0:\x01::./file0:");
write_file("/proc/sys/fs/binfmt_misc/register", ":syz1:M:1:\x02::./file0:POC");
}
#endif
#if SYZ_EXECUTOR || SYZ_SANDBOX_NONE || SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP
#include <errno.h>
#include <sys/mount.h>
@ -4188,9 +4170,6 @@ static void setup_common()
#if SYZ_EXECUTOR || SYZ_ENABLE_CGROUPS
setup_cgroups();
#endif
#if SYZ_EXECUTOR || SYZ_ENABLE_BINFMT_MISC
setup_binfmt_misc();
#endif
}
#include <sched.h>
@ -4687,10 +4666,6 @@ retry:
static int inject_fault(int nth)
{
#if SYZ_EXECUTOR
if (!flag_enable_fault_injection)
return 0;
#endif
int fd;
fd = open("/proc/thread-self/fail-nth", O_RDWR);
if (fd == -1)
@ -4706,8 +4681,6 @@ static int inject_fault(int nth)
#if SYZ_EXECUTOR
static int fault_injected(int fail_fd)
{
if (!flag_enable_fault_injection)
return 0;
char buf[16];
int n = read(fail_fd, buf, sizeof(buf) - 1);
if (n <= 0)
@ -4842,6 +4815,142 @@ static void close_fds()
}
#endif
#if SYZ_EXECUTOR || SYZ_FAULT_INJECTION
#include <errno.h>
static void setup_fault()
{
static struct {
const char* file;
const char* val;
bool fatal;
} files[] = {
{"/sys/kernel/debug/failslab/ignore-gfp-wait", "N", true},
{"/sys/kernel/debug/fail_futex/ignore-private", "N", false},
{"/sys/kernel/debug/fail_page_alloc/ignore-gfp-highmem", "N", false},
{"/sys/kernel/debug/fail_page_alloc/ignore-gfp-wait", "N", false},
{"/sys/kernel/debug/fail_page_alloc/min-order", "0", false},
};
unsigned i;
for (i = 0; i < sizeof(files) / sizeof(files[0]); i++) {
if (!write_file(files[i].file, files[i].val)) {
debug("failed to write %s: %d\n", files[i].file, errno);
if (files[i].fatal)
fail("failed to write %s", files[i].file);
}
}
}
#endif
#if SYZ_EXECUTOR || SYZ_ENABLE_LEAK
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#define KMEMLEAK_FILE "/sys/kernel/debug/kmemleak"
static void setup_leak()
{
if (!write_file(KMEMLEAK_FILE, "scan"))
fail("failed to write %s", KMEMLEAK_FILE);
sleep(5);
if (!write_file(KMEMLEAK_FILE, "scan"))
fail("failed to write %s", KMEMLEAK_FILE);
if (!write_file(KMEMLEAK_FILE, "clear"))
fail("failed to write %s", KMEMLEAK_FILE);
}
#define SYZ_HAVE_LEAK_CHECK 1
#if SYZ_EXECUTOR
static void check_leaks(char** frames, int nframes)
#else
static void check_leaks(void)
#endif
{
int fd = open(KMEMLEAK_FILE, O_RDWR);
if (fd == -1)
fail("failed to open(\"%s\")", KMEMLEAK_FILE);
uint64 start = current_time_ms();
if (write(fd, "scan", 4) != 4)
fail("failed to write(%s, \"scan\")", KMEMLEAK_FILE);
sleep(1);
while (current_time_ms() - start < 4 * 1000)
sleep(1);
if (write(fd, "scan", 4) != 4)
fail("failed to write(%s, \"scan\")", KMEMLEAK_FILE);
static char buf[128 << 10];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n < 0)
fail("failed to read(%s)", KMEMLEAK_FILE);
#if SYZ_EXECUTOR
int nleaks = 0;
#endif
if (n != 0) {
sleep(1);
if (write(fd, "scan", 4) != 4)
fail("failed to write(%s, \"scan\")", KMEMLEAK_FILE);
if (lseek(fd, 0, SEEK_SET) < 0)
fail("failed to lseek(%s)", KMEMLEAK_FILE);
n = read(fd, buf, sizeof(buf) - 1);
if (n < 0)
fail("failed to read(%s)", KMEMLEAK_FILE);
buf[n] = 0;
char* pos = buf;
char* end = buf + n;
while (pos < end) {
char* next = strstr(pos + 1, "unreferenced object");
if (!next)
next = end;
char prev = *next;
*next = 0;
#if SYZ_EXECUTOR
int f;
for (f = 0; f < nframes; f++) {
if (strstr(pos, frames[f]))
break;
}
if (f != nframes) {
*next = prev;
pos = next;
continue;
}
#endif
fprintf(stderr, "BUG: memory leak\n%s\n", pos);
*next = prev;
pos = next;
#if SYZ_EXECUTOR
nleaks++;
#endif
}
}
if (write(fd, "clear", 5) != 5)
fail("failed to write(%s, \"clear\")", KMEMLEAK_FILE);
close(fd);
#if SYZ_EXECUTOR
if (nleaks)
doexit(1);
#endif
}
#endif
#if SYZ_EXECUTOR || SYZ_ENABLE_BINFMT_MISC
#include <fcntl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
static void setup_binfmt_misc()
{
if (mount(0, "/proc/sys/fs/binfmt_misc", "binfmt_misc", 0, 0)) {
debug("mount(binfmt_misc) failed: %d\n", errno);
}
write_file("/proc/sys/fs/binfmt_misc/register", ":syz0:M:0:\x01::./file0:");
write_file("/proc/sys/fs/binfmt_misc/register", ":syz1:M:1:\x02::./file0:POC");
}
#endif
#elif GOOS_test
#include <stdlib.h>
@ -5261,6 +5370,9 @@ static void loop(void)
#endif
#if SYZ_EXECUTOR || SYZ_USE_TMP_DIR
remove_dir(cwdbuf);
#endif
#if SYZ_ENABLE_LEAK
check_leaks();
#endif
}
}
@ -5308,6 +5420,16 @@ int main(void)
/*MMAP_DATA*/
#endif
#if SYZ_ENABLE_BINFMT_MISC
setup_binfmt_misc();
#endif
#if SYZ_ENABLE_LEAK
setup_leak();
#endif
#if SYZ_FAULT_INJECTION
setup_fault();
#endif
#if SYZ_HANDLE_SEGV
install_segv_handler();
#endif
@ -5327,6 +5449,9 @@ int main(void)
}
}
sleep(1000000);
#endif
#if !SYZ_PROCS && !SYZ_REPEAT && SYZ_ENABLE_LEAK
check_leaks();
#endif
return 0;
}

View File

@ -28,6 +28,8 @@ type Options struct {
FaultCall int `json:"fault_call,omitempty"`
FaultNth int `json:"fault_nth,omitempty"`
Leak bool `json:"leak,omitempty"` // do leak checking
// These options allow for a more fine-tuned control over the generated C code.
EnableTun bool `json:"tun,omitempty"`
EnableNetDev bool `json:"netdev,omitempty"`
@ -129,6 +131,9 @@ func (opts Options) checkLinuxOnly(OS string) error {
if opts.Fault {
return fmt.Errorf("option Fault is not supported on %v", OS)
}
if opts.Leak {
return fmt.Errorf("option Leak is not supported on %v", OS)
}
return nil
}

View File

@ -4,7 +4,11 @@
package host
import (
"time"
"github.com/google/syzkaller/pkg/csource"
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/prog"
)
@ -67,8 +71,6 @@ type Feature struct {
type Features [numFeatures]Feature
var checkFeature [numFeatures]func() string
var setupFeature [numFeatures]func() error
var callbFeature [numFeatures]func(leakFrames [][]byte)
func unconditionallyEnabled() string { return "" }
@ -108,29 +110,20 @@ func Check(target *prog.Target) (*Features, error) {
// Setup enables and does any one-time setup for the requested features on the host.
// Note: this can be called multiple times and must be idempotent.
func Setup(target *prog.Target, features *Features) (func(leakFrames [][]byte), error) {
func Setup(target *prog.Target, features *Features, featureFlags csource.Features, executor string) error {
if target.OS == "akaros" || target.OS == "test" {
return nil, nil
return nil
}
var callback func([][]byte)
for n, setup := range setupFeature {
if setup == nil || !features[n].Enabled {
continue
}
if err := setup(); err != nil {
return nil, err
}
cb := callbFeature[n]
if cb == nil {
continue
}
prev := callback
callback = func(leakFrames [][]byte) {
cb(leakFrames)
if prev != nil {
prev(leakFrames)
}
}
args := []string{"setup"}
if features[FeatureLeakChecking].Enabled {
args = append(args, "leak")
}
return callback, nil
if features[FeatureFaultInjection].Enabled {
args = append(args, "fault")
}
if featureFlags["binfmt_misc"].Enabled {
args = append(args, "binfmt_misc")
}
_, err := osutil.RunCmd(time.Minute, "", executor, args...)
return err
}

View File

@ -367,10 +367,7 @@ func init() {
checkFeature[FeatureSandboxNamespace] = checkSandboxNamespace
checkFeature[FeatureSandboxAndroidUntrustedApp] = checkSandboxAndroidUntrustedApp
checkFeature[FeatureFaultInjection] = checkFaultInjection
setupFeature[FeatureFaultInjection] = setupFaultInjection
checkFeature[FeatureLeakChecking] = checkLeakChecking
setupFeature[FeatureLeakChecking] = setupLeakChecking
callbFeature[FeatureLeakChecking] = callbackLeakChecking
checkFeature[FeatureNetworkInjection] = checkNetworkInjection
checkFeature[FeatureNetworkDevices] = checkNetworkDevices
}
@ -482,28 +479,6 @@ func checkFaultInjection() string {
return ""
}
func setupFaultInjection() error {
// Note: these files are also hardcoded in pkg/csource/csource.go.
if err := osutil.WriteFile("/sys/kernel/debug/failslab/ignore-gfp-wait", []byte("N")); err != nil {
return fmt.Errorf("failed to write /failslab/ignore-gfp-wait: %v", err)
}
// These are enabled by separate configs (e.g. CONFIG_FAIL_FUTEX)
// and we did not check all of them in checkFaultInjection, so we ignore errors.
if err := osutil.WriteFile("/sys/kernel/debug/fail_futex/ignore-private", []byte("N")); err != nil {
log.Logf(0, "failed to write /sys/kernel/debug/fail_futex/ignore-private: %v", err)
}
if err := osutil.WriteFile("/sys/kernel/debug/fail_page_alloc/ignore-gfp-highmem", []byte("N")); err != nil {
log.Logf(0, "failed to write /sys/kernel/debug/fail_page_alloc/ignore-gfp-highmem: %v", err)
}
if err := osutil.WriteFile("/sys/kernel/debug/fail_page_alloc/ignore-gfp-wait", []byte("N")); err != nil {
log.Logf(0, "failed to write /sys/kernel/debug/fail_page_alloc/ignore-gfp-wait: %v", err)
}
if err := osutil.WriteFile("/sys/kernel/debug/fail_page_alloc/min-order", []byte("0")); err != nil {
log.Logf(0, "failed to write /sys/kernel/debug/fail_page_alloc/min-order: %v", err)
}
return nil
}
func checkLeakChecking() string {
if reason := checkDebugFS(); reason != "" {
return reason
@ -522,101 +497,6 @@ func checkLeakChecking() string {
return ""
}
func setupLeakChecking() error {
fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0)
if err != nil {
return fmt.Errorf("failed to open /sys/kernel/debug/kmemleak: %v", err)
}
defer syscall.Close(fd)
// Flush boot leaks.
if _, err := syscall.Write(fd, []byte("scan")); err != nil {
return fmt.Errorf("write(kmemleak, scan) failed: %v", err)
}
time.Sleep(5 * time.Second) // account for MSECS_MIN_AGE
if _, err := syscall.Write(fd, []byte("scan")); err != nil {
return fmt.Errorf("write(kmemleak, scan) failed: %v", err)
}
if _, err := syscall.Write(fd, []byte("clear")); err != nil {
return fmt.Errorf("write(kmemleak, clear) failed: %v", err)
}
return nil
}
func callbackLeakChecking(leakFrames [][]byte) {
start := time.Now()
fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0)
if err != nil {
panic(err)
}
defer syscall.Close(fd)
// KMEMLEAK has false positives. To mitigate most of them, it checksums
// potentially leaked objects, and reports them only on the next scan
// iff the checksum does not change. Because of that we do the following
// intricate dance:
// Scan, sleep, scan again. At this point we can get some leaks.
// If there are leaks, we sleep and scan again, this can remove
// false leaks. Then, read kmemleak again. If we get leaks now, then
// hopefully these are true positives during the previous testing cycle.
if _, err := syscall.Write(fd, []byte("scan")); err != nil {
panic(err)
}
time.Sleep(time.Second)
// Account for MSECS_MIN_AGE
// (1 second less because scanning will take at least a second).
for time.Since(start) < 4*time.Second {
time.Sleep(time.Second)
}
if _, err := syscall.Write(fd, []byte("scan")); err != nil {
panic(err)
}
buf := make([]byte, 128<<10)
n, err := syscall.Read(fd, buf)
if err != nil {
panic(err)
}
if n != 0 {
time.Sleep(time.Second)
if _, err := syscall.Write(fd, []byte("scan")); err != nil {
panic(err)
}
if _, err := syscall.Seek(fd, 0, 0); err != nil {
panic(err)
}
n, err := syscall.Read(fd, buf)
if err != nil {
panic(err)
}
nleaks := 0
nextLeak:
for buf = buf[:n]; len(buf) != 0; {
end := bytes.Index(buf[1:], []byte("unreferenced object"))
if end != -1 {
end++
} else {
end = len(buf)
}
report := buf[:end]
buf = buf[end:]
for _, frame := range leakFrames {
if bytes.Contains(report, frame) {
continue nextLeak
}
}
// BUG in output should be recognized by manager.
fmt.Printf("BUG: memory leak\n%s\n", report)
nleaks++
}
if nleaks != 0 {
// If we exit right away, dying executors will dump lots of garbage to console.
time.Sleep(time.Hour)
os.Exit(1)
}
}
if _, err := syscall.Write(fd, []byte("clear")); err != nil {
panic(err)
}
}
func checkSandboxNamespace() string {
if err := osutil.IsAccessible("/proc/self/ns/user"); err != nil {
return err.Error()

View File

@ -32,12 +32,10 @@ const (
FlagSandboxNamespace // use namespaces for sandboxing
FlagSandboxAndroidUntrustedApp // use Android sandboxing for the untrusted_app domain
FlagExtraCover // collect extra coverage
FlagEnableFault // enable fault injection support
FlagEnableTun // setup and use /dev/tun for packet injection
FlagEnableNetDev // setup more network devices for testing
FlagEnableNetReset // reset network namespace between programs
FlagEnableCgroups // setup cgroups for testing
FlagEnableBinfmtMisc // setup binfmt_misc for testing
FlagEnableCloseFds // close fds after each program
// Executor does not know about these:
FlagUseShmem // use shared memory instead of pipes for communication

View File

@ -44,8 +44,10 @@ type context struct {
cfg *mgrconfig.Config
reporter report.Reporter
crashTitle string
crashType report.Type
instances chan *instance
bootRequests chan int
timeouts []time.Duration
stats *Stats
report *report.Report
}
@ -70,22 +72,42 @@ func Run(crashLog []byte, cfg *mgrconfig.Config, reporter report.Reporter, vmPoo
if len(entries) == 0 {
return nil, nil, fmt.Errorf("crash log does not contain any programs")
}
crashStart := len(crashLog) // assuming VM hanged
crashTitle := "hang"
crashStart := len(crashLog)
crashTitle, crashType := "", report.Unknown
if rep := reporter.Parse(crashLog); rep != nil {
crashStart = rep.StartPos
crashTitle = rep.Title
crashType = rep.Type
}
// The shortest duration is 10 seconds to detect simple crashes (i.e. no races and no hangs).
// The longest duration is 6 minutes to catch races and hangs.
noOutputTimeout := vm.NoOutputTimeout + time.Minute
timeouts := []time.Duration{15 * time.Second, time.Minute, noOutputTimeout}
switch {
case crashTitle == "":
crashTitle = "no output/lost connection"
// Lost connection can be detected faster,
// but theoretically if it's caused by a race it may need the largest timeout.
// No output can only be reproduced with the max timeout.
// As a compromise we use the smallest and the largest timeouts.
timeouts = []time.Duration{15 * time.Second, noOutputTimeout}
case crashType == report.MemoryLeak:
// Memory leaks can't be detected quickly because of expensive setup and scanning.
timeouts = []time.Duration{time.Minute, noOutputTimeout}
case crashType == report.Hang:
timeouts = []time.Duration{noOutputTimeout}
}
ctx := &context{
cfg: cfg,
reporter: reporter,
crashTitle: crashTitle,
crashType: crashType,
instances: make(chan *instance, len(vmIndexes)),
bootRequests: make(chan int, len(vmIndexes)),
timeouts: timeouts,
stats: new(Stats),
}
ctx.reproLog(0, "%v programs, %v VMs", len(entries), len(vmIndexes))
ctx.reproLog(0, "%v programs, %v VMs, timeouts %v", len(entries), len(vmIndexes), timeouts)
var wg sync.WaitGroup
wg.Add(len(vmIndexes))
for _, vmIndex := range vmIndexes {
@ -252,13 +274,7 @@ func (ctx *context) extractProg(entries []*prog.LogEntry) (*Result, error) {
for i := len(indices) - 1; i >= 0; i-- {
lastEntries = append(lastEntries, entries[indices[i]])
}
// The shortest duration is 10 seconds to detect simple crashes (i.e. no races and no hangs).
// The longest duration is 6 minutes to catch races and hangs. Note that this value must be larger
// than hang/no output detection duration in vm.MonitorExecution, which is currently set to 5 mins.
timeouts := []time.Duration{10 * time.Second, 1 * time.Minute, vm.NoOutputTimeout + time.Minute}
for _, timeout := range timeouts {
for _, timeout := range ctx.timeouts {
// Execute each program separately to detect simple crashes caused by a single program.
// Programs are executed in reverse order, usually the last program is the guilty one.
res, err := ctx.extractProgSingle(reverseEntries(lastEntries), timeout)
@ -294,6 +310,9 @@ func (ctx *context) extractProgSingle(entries []*prog.LogEntry, duration time.Du
ctx.reproLog(3, "single: executing %d programs separately with timeout %s", len(entries), duration)
opts := csource.DefaultOpts(ctx.cfg)
if ctx.crashType == report.MemoryLeak {
opts.Leak = true
}
for _, ent := range entries {
opts.Fault = ent.Fault
opts.FaultCall = ent.FaultCall
@ -324,8 +343,11 @@ func (ctx *context) extractProgBisect(entries []*prog.LogEntry, baseDuration tim
ctx.reproLog(3, "bisect: bisecting %d programs with base timeout %s", len(entries), baseDuration)
opts := csource.DefaultOpts(ctx.cfg)
if ctx.crashType == report.MemoryLeak {
opts.Leak = true
}
duration := func(entries int) time.Duration {
return baseDuration + time.Duration((entries/4))*time.Second
return baseDuration + time.Duration(entries/4)*time.Second
}
// Bisect the log to find multiple guilty programs.
@ -599,6 +621,10 @@ func (ctx *context) testImpl(inst *vm.Instance, command string, duration time.Du
ctx.reproLog(2, "suppressed program crash: %v", rep.Title)
return false, nil
}
if ctx.crashType == report.MemoryLeak && rep.Type != report.MemoryLeak {
ctx.reproLog(2, "not a leak crash: %v", rep.Title)
return false, nil
}
ctx.report = rep
ctx.reproLog(2, "program crashed: %v", rep.Title)
return true, nil

View File

@ -5,6 +5,7 @@ package main
import (
"flag"
"fmt"
"net/http"
_ "net/http/pprof"
"os"
@ -14,6 +15,7 @@ import (
"sync/atomic"
"time"
"github.com/google/syzkaller/pkg/csource"
"github.com/google/syzkaller/pkg/hash"
"github.com/google/syzkaller/pkg/host"
"github.com/google/syzkaller/pkg/ipc"
@ -27,18 +29,19 @@ import (
)
type Fuzzer struct {
name string
outputType OutputType
config *ipc.Config
execOpts *ipc.ExecOpts
procs []*Proc
gate *ipc.Gate
workQueue *WorkQueue
needPoll chan struct{}
choiceTable *prog.ChoiceTable
stats [StatCount]uint64
manager *rpctype.RPCClient
target *prog.Target
name string
outputType OutputType
config *ipc.Config
execOpts *ipc.ExecOpts
procs []*Proc
gate *ipc.Gate
workQueue *WorkQueue
needPoll chan struct{}
choiceTable *prog.ChoiceTable
stats [StatCount]uint64
manager *rpctype.RPCClient
target *prog.Target
triagedCandidates uint32
faultInjectionEnabled bool
comparisonTracingEnabled bool
@ -180,20 +183,16 @@ func main() {
for _, feat := range r.CheckResult.Features {
log.Logf(0, "%v: %v", feat.Name, feat.Reason)
}
periodicCallback, err := host.Setup(target, r.CheckResult.Features)
featureFlags, err := csource.ParseFeaturesFlags("none", "none", true)
if err != nil {
log.Fatalf("BUG: %v", err)
log.Fatal(err)
}
var gateCallback func()
if periodicCallback != nil {
gateCallback = func() { periodicCallback(nil) }
if err = host.Setup(target, r.CheckResult.Features, featureFlags, config.Executor); err != nil {
log.Fatal(err)
}
if r.CheckResult.Features[host.FeatureExtraCoverage].Enabled {
config.Flags |= ipc.FlagExtraCover
}
if r.CheckResult.Features[host.FeatureFaultInjection].Enabled {
config.Flags |= ipc.FlagEnableFault
}
if r.CheckResult.Features[host.FeatureNetworkInjection].Enabled {
config.Flags |= ipc.FlagEnableTun
}
@ -202,7 +201,6 @@ func main() {
}
config.Flags |= ipc.FlagEnableNetReset
config.Flags |= ipc.FlagEnableCgroups
config.Flags |= ipc.FlagEnableBinfmtMisc
config.Flags |= ipc.FlagEnableCloseFds
if *flagRunTest {
@ -217,7 +215,6 @@ func main() {
outputType: outputType,
config: config,
execOpts: execOpts,
gate: ipc.NewGate(2**flagProcs, gateCallback),
workQueue: newWorkQueue(*flagProcs, needPoll),
needPoll: needPoll,
manager: manager,
@ -226,6 +223,11 @@ func main() {
comparisonTracingEnabled: r.CheckResult.Features[host.FeatureComparisons].Enabled,
corpusHashes: make(map[hash.Sig]struct{}),
}
var gateCallback func()
if r.CheckResult.Features[host.FeatureLeakChecking].Enabled {
gateCallback = func() { fuzzer.gateCallback(r.MemoryLeakFrames) }
}
fuzzer.gate = ipc.NewGate(2**flagProcs, gateCallback)
for i := 0; fuzzer.poll(i == 0, nil); i++ {
}
calls := make(map[*prog.Syscall]bool)
@ -247,6 +249,30 @@ func main() {
fuzzer.pollLoop()
}
func (fuzzer *Fuzzer) gateCallback(leakFrames []string) {
// Leak checking is very slow so we don't do it while triaging the corpus
// (otherwise it takes infinity). When we have presumably triaged the corpus
// (triagedCandidates == 1), we run leak checking bug ignore the result
// to flush any previous leaks. After that (triagedCandidates == 2)
// we do actual leak checking and report leaks.
triagedCandidates := atomic.LoadUint32(&fuzzer.triagedCandidates)
if triagedCandidates == 0 {
return
}
args := append([]string{"leak"}, leakFrames...)
output, err := osutil.RunCmd(10*time.Minute, "", fuzzer.config.Executor, args...)
if err != nil && triagedCandidates == 2 {
// If we exit right away, dying executors will dump lots of garbage to console.
os.Stdout.Write(output)
fmt.Printf("BUG: leak checking failed")
time.Sleep(time.Hour)
os.Exit(1)
}
if triagedCandidates == 1 {
atomic.StoreUint32(&fuzzer.triagedCandidates, 2)
}
}
func (fuzzer *Fuzzer) pollLoop() {
var execTotal uint64
var lastPoll time.Time
@ -321,6 +347,9 @@ func (fuzzer *Fuzzer) poll(needCandidates bool, stats map[string]uint64) bool {
flags: flags,
})
}
if needCandidates && len(r.Candidates) == 0 && atomic.LoadUint32(&fuzzer.triagedCandidates) == 0 {
atomic.StoreUint32(&fuzzer.triagedCandidates, 1)
}
return len(r.NewInputs) != 0 || len(r.Candidates) != 0 || maxSignal.Len() != 0
}

View File

@ -73,16 +73,25 @@ func main() {
log.Logf(0, "%-24v: %v", feat.Name, feat.Reason)
}
}
if _, err = host.Setup(target, features); err != nil {
log.Fatalf("%v", err)
}
config, execOpts := createConfig(target, entries, features, featuresFlags)
if err = host.Setup(target, features, featuresFlags, config.Executor); err != nil {
log.Fatal(err)
}
var gateCallback func()
if features[host.FeatureLeakChecking].Enabled {
gateCallback = func() {
output, err := osutil.RunCmd(10*time.Minute, "", config.Executor, "leak")
if err != nil {
os.Stdout.Write(output)
os.Exit(1)
}
}
}
ctx := &Context{
entries: entries,
config: config,
execOpts: execOpts,
gate: ipc.NewGate(2**flagProcs, nil),
gate: ipc.NewGate(2**flagProcs, gateCallback),
shutdown: make(chan struct{}),
repeat: *flagRepeat,
}
@ -298,17 +307,10 @@ func createConfig(target *prog.Target, entries []*prog.LogEntry,
config.Flags |= ipc.FlagExtraCover
}
if *flagFaultCall >= 0 {
config.Flags |= ipc.FlagEnableFault
execOpts.Flags |= ipc.FlagInjectFault
execOpts.FaultCall = *flagFaultCall
execOpts.FaultNth = *flagFaultNth
}
for _, entry := range entries {
if entry.Fault {
config.Flags |= ipc.FlagEnableFault
break
}
}
if featuresFlags["tun"].Enabled && features[host.FeatureNetworkInjection].Enabled {
config.Flags |= ipc.FlagEnableTun
}
@ -321,9 +323,6 @@ func createConfig(target *prog.Target, entries []*prog.LogEntry,
if featuresFlags["cgroups"].Enabled {
config.Flags |= ipc.FlagEnableCgroups
}
if featuresFlags["binfmt_misc"].Enabled {
config.Flags |= ipc.FlagEnableBinfmtMisc
}
if featuresFlags["close_fds"].Enabled {
config.Flags |= ipc.FlagEnableCloseFds
}

View File

@ -32,6 +32,7 @@ var (
flagUseTmpDir = flag.Bool("tmpdir", false, "create a temporary dir and execute inside it")
flagTrace = flag.Bool("trace", false, "trace syscall results")
flagStrict = flag.Bool("strict", false, "parse input program in strict mode")
flagLeak = flag.Bool("leak", false, "do leak checking")
flagEnable = flag.String("enable", "none", "enable only listed additional features")
flagDisable = flag.String("disable", "none", "enable all additional features except listed")
)
@ -79,6 +80,7 @@ func main() {
Fault: *flagFaultCall >= 0,
FaultCall: *flagFaultCall,
FaultNth: *flagFaultNth,
Leak: *flagLeak,
EnableTun: features["tun"].Enabled,
EnableNetDev: features["net_dev"].Enabled,
EnableNetReset: features["net_reset"].Enabled,

View File

@ -67,9 +67,6 @@ func main() {
if err != nil {
log.Fatalf("%v", err)
}
if _, err = host.Setup(target, features); err != nil {
log.Fatalf("%v", err)
}
calls := buildCallList(target, strings.Split(*flagSyscalls, ","))
prios := target.CalculatePriorities(corpus)
@ -91,12 +88,12 @@ func main() {
if featuresFlags["cgroups"].Enabled {
config.Flags |= ipc.FlagEnableCgroups
}
if featuresFlags["binfmt_misc"].Enabled {
config.Flags |= ipc.FlagEnableBinfmtMisc
}
if featuresFlags["close_fds"].Enabled {
config.Flags |= ipc.FlagEnableCloseFds
}
if err = host.Setup(target, features, featuresFlags, config.Executor); err != nil {
log.Fatal(err)
}
gate = ipc.NewGate(2**flagProcs, nil)
for pid := 0; pid < *flagProcs; pid++ {
pid := pid