From 8285069f89c9942f65ce760a8f0a5a12254bfeeb Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Sat, 18 May 2019 17:54:03 +0200 Subject: 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. --- pkg/csource/common.go | 3 + pkg/csource/csource.go | 6 -- pkg/csource/generated.go | 189 +++++++++++++++++++++++++++++++++++++++-------- pkg/csource/options.go | 5 ++ pkg/host/host.go | 41 +++++----- pkg/host/host_linux.go | 120 ------------------------------ pkg/ipc/ipc.go | 2 - pkg/repro/repro.go | 50 ++++++++++--- 8 files changed, 220 insertions(+), 196 deletions(-) (limited to 'pkg') diff --git a/pkg/csource/common.go b/pkg/csource/common.go index d0c63402a..587c0f587 100644 --- a/pkg/csource/common.go +++ b/pkg/csource/common.go @@ -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, diff --git a/pkg/csource/csource.go b/pkg/csource/csource.go index c5beb68c6..c8513286f 100644 --- a/pkg/csource/csource.go +++ b/pkg/csource/csource.go @@ -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. diff --git a/pkg/csource/generated.go b/pkg/csource/generated.go index 7f1424f36..53458f2f6 100644 --- a/pkg/csource/generated.go +++ b/pkg/csource/generated.go @@ -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 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 #include #include @@ -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 -#include -#include -#include - -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 #include @@ -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 @@ -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 + +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 +#include +#include +#include +#include + +#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 +#include +#include +#include + +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 @@ -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; } diff --git a/pkg/csource/options.go b/pkg/csource/options.go index 61094456f..e63a76890 100644 --- a/pkg/csource/options.go +++ b/pkg/csource/options.go @@ -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 } diff --git a/pkg/host/host.go b/pkg/host/host.go index d8d83916d..00bcde314 100644 --- a/pkg/host/host.go +++ b/pkg/host/host.go @@ -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") + } + if features[FeatureFaultInjection].Enabled { + args = append(args, "fault") + } + if featureFlags["binfmt_misc"].Enabled { + args = append(args, "binfmt_misc") } - return callback, nil + _, err := osutil.RunCmd(time.Minute, "", executor, args...) + return err } diff --git a/pkg/host/host_linux.go b/pkg/host/host_linux.go index 8bd2cc65a..d0c3cd0f4 100644 --- a/pkg/host/host_linux.go +++ b/pkg/host/host_linux.go @@ -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() diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go index ab6c93a6f..89799b45a 100644 --- a/pkg/ipc/ipc.go +++ b/pkg/ipc/ipc.go @@ -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 diff --git a/pkg/repro/repro.go b/pkg/repro/repro.go index b3b8d739c..592761b9e 100644 --- a/pkg/repro/repro.go +++ b/pkg/repro/repro.go @@ -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 -- cgit mrf-deployment