Exemple #1
0
func main() {
	flag.Parse()
	cfg, syscalls := parseConfig()
	params, err := json.Marshal(cfg.Params)
	if err != nil {
		fatalf("failed to marshal config params: %v", err)
	}
	enabledSyscalls := ""
	if len(syscalls) != 0 {
		buf := new(bytes.Buffer)
		for c := range syscalls {
			fmt.Fprintf(buf, ",%v", c)
		}
		enabledSyscalls = buf.String()[1:]
	}
	vmCfg := &vm.Config{
		Workdir:         cfg.Workdir,
		ManagerPort:     cfg.Port,
		Params:          params,
		EnabledSyscalls: enabledSyscalls,
		NoCover:         cfg.Nocover,
	}
	var instances []vm.Instance
	for i := 0; i < cfg.Count; i++ {
		inst, err := vm.Create(cfg.Type, vmCfg, i)
		if err != nil {
			fatalf("failed to create an instance: %v", err)
		}
		instances = append(instances, inst)
	}
	RunManager(cfg, syscalls, instances)
}
Exemple #2
0
func (mgr *Manager) runInstance(vmCfg *vm.Config, first bool) bool {
	inst, err := vm.Create(mgr.cfg.Type, vmCfg)
	if err != nil {
		Logf(0, "failed to create instance: %v", err)
		return false
	}
	defer inst.Close()

	fwdAddr, err := inst.Forward(mgr.port)
	if err != nil {
		Logf(0, "failed to setup port forwarding: %v", err)
		return false
	}
	fuzzerBin, err := inst.Copy(filepath.Join(mgr.cfg.Syzkaller, "bin", "syz-fuzzer"))
	if err != nil {
		Logf(0, "failed to copy binary: %v", err)
		return false
	}
	executorBin, err := inst.Copy(filepath.Join(mgr.cfg.Syzkaller, "bin", "syz-executor"))
	if err != nil {
		Logf(0, "failed to copy binary: %v", err)
		return false
	}

	// Leak detection significantly slows down fuzzing, so detect leaks only on the first instance.
	leak := first && mgr.cfg.Leak
	fuzzerV := 0
	if *flagDebug {
		fuzzerV = 100
	}

	// Run the fuzzer binary.
	outc, errc, err := inst.Run(time.Hour, fmt.Sprintf(
		"%v -executor=%v -name=%v -manager=%v -output=%v -procs=%v -leak=%v -cover=%v -sandbox=%v -debug=%v -v=%d",
		fuzzerBin, executorBin, vmCfg.Name, fwdAddr, mgr.cfg.Output, mgr.cfg.Procs, leak, mgr.cfg.Cover, mgr.cfg.Sandbox, *flagDebug, fuzzerV))
	if err != nil {
		Logf(0, "failed to run fuzzer: %v", err)
		return false
	}

	desc, text, output, crashed, timedout := vm.MonitorExecution(outc, errc, mgr.cfg.Type == "local", true)
	if timedout {
		// This is the only "OK" outcome.
		Logf(0, "%v: running long enough, restarting", vmCfg.Name)
	} else {
		if !crashed {
			// syz-fuzzer exited, but it should not.
			desc = "lost connection to test machine"
		}
		mgr.saveCrasher(vmCfg, desc, text, output)
	}
	return true
}
Exemple #3
0
func runInstance(cfg *config.Config, vmCfg *vm.Config) {
	inst, err := vm.Create(cfg.Type, vmCfg)
	if err != nil {
		Logf(0, "failed to create instance: %v", err)
		return
	}
	defer inst.Close()

	execprogBin, err := inst.Copy(filepath.Join(cfg.Syzkaller, "bin", "syz-execprog"))
	if err != nil {
		Logf(0, "failed to copy execprog: %v", err)
		return
	}
	executorBin, err := inst.Copy(filepath.Join(cfg.Syzkaller, "bin", "syz-executor"))
	if err != nil {
		Logf(0, "failed to copy executor: %v", err)
		return
	}
	logFile, err := inst.Copy(flag.Args()[0])
	if err != nil {
		Logf(0, "failed to copy log: %v", err)
		return
	}

	cmd := fmt.Sprintf("%v -executor=%v -repeat=0 -procs=%v -cover=0 -sandbox=%v %v",
		execprogBin, executorBin, cfg.Procs, cfg.Sandbox, logFile)
	outc, errc, err := inst.Run(time.Hour, nil, cmd)
	if err != nil {
		Logf(0, "failed to run execprog: %v", err)
		return
	}

	Logf(0, "%v: crushing...", vmCfg.Name)
	desc, _, output, crashed, timedout := vm.MonitorExecution(outc, errc, cfg.Type == "local", true, cfg.ParsedIgnores)
	if timedout {
		// This is the only "OK" outcome.
		Logf(0, "%v: running long enough, restarting", vmCfg.Name)
	} else {
		if !crashed {
			// syz-execprog exited, but it should not.
			desc = "lost connection to test machine"
		}
		f, err := ioutil.TempFile(".", "syz-crush")
		if err != nil {
			Logf(0, "failed to create temp file: %v", err)
			return
		}
		defer f.Close()
		Logf(0, "%v: crashed: %v, saving to %v", vmCfg.Name, desc, f.Name())
		f.Write(output)
	}
	return
}
Exemple #4
0
func (mgr *Manager) runInstance(vmCfg *vm.Config, first bool) (*Crash, error) {
	inst, err := vm.Create(mgr.cfg.Type, vmCfg)
	if err != nil {
		return nil, fmt.Errorf("failed to create instance: %v", err)
	}
	defer inst.Close()

	fwdAddr, err := inst.Forward(mgr.port)
	if err != nil {
		return nil, fmt.Errorf("failed to setup port forwarding: %v", err)
	}
	fuzzerBin, err := inst.Copy(filepath.Join(mgr.cfg.Syzkaller, "bin", "syz-fuzzer"))
	if err != nil {
		return nil, fmt.Errorf("failed to copy binary: %v", err)
	}
	executorBin, err := inst.Copy(filepath.Join(mgr.cfg.Syzkaller, "bin", "syz-executor"))
	if err != nil {
		return nil, fmt.Errorf("failed to copy binary: %v", err)
	}

	// Leak detection significantly slows down fuzzing, so detect leaks only on the first instance.
	leak := first && mgr.cfg.Leak
	fuzzerV := 0
	procs := mgr.cfg.Procs
	if *flagDebug {
		fuzzerV = 100
		procs = 1
	}

	// Run the fuzzer binary.
	start := time.Now()
	cmd := fmt.Sprintf("%v -executor=%v -name=%v -manager=%v -output=%v -procs=%v -leak=%v -cover=%v -sandbox=%v -debug=%v -v=%d",
		fuzzerBin, executorBin, vmCfg.Name, fwdAddr, mgr.cfg.Output, procs, leak, mgr.cfg.Cover, mgr.cfg.Sandbox, *flagDebug, fuzzerV)
	outc, errc, err := inst.Run(time.Hour, mgr.vmStop, cmd)
	if err != nil {
		return nil, fmt.Errorf("failed to run fuzzer: %v", err)
	}

	desc, text, output, crashed, timedout := vm.MonitorExecution(outc, errc, mgr.cfg.Type == "local", true, mgr.cfg.ParsedIgnores)
	if timedout {
		// This is the only "OK" outcome.
		Logf(0, "%v: running for %v, restarting (%v)", vmCfg.Name, time.Since(start), desc)
		return nil, nil
	}
	if !crashed {
		// syz-fuzzer exited, but it should not.
		desc = "lost connection to test machine"
	}
	return &Crash{vmCfg.Name, desc, text, output}, nil
}
Exemple #5
0
func main() {
	flag.Parse()
	cfg, syscalls := parseConfig()
	params, err := json.Marshal(cfg.Params)
	if err != nil {
		fatalf("failed to marshal config params: %v", err)
	}
	enabledSyscalls := ""
	if len(syscalls) != 0 {
		buf := new(bytes.Buffer)
		for c := range syscalls {
			fmt.Fprintf(buf, ",%v", c)
		}
		enabledSyscalls = buf.String()[1:]
		logf(1, "enabled syscalls: %v", enabledSyscalls)
	}
	vmCfg := &vm.Config{
		Workdir:         cfg.Workdir,
		ManagerPort:     cfg.Port,
		Params:          params,
		EnabledSyscalls: enabledSyscalls,
		NoCover:         cfg.Nocover,
	}

	// Add some builtin suppressions.
	cfg.Suppressions = append(cfg.Suppressions, []string{
		"panic: failed to start executor binary",
		"panic: executor failed: pthread_create failed",
		"panic: failed to create temp dir",
		"Out of memory: Kill process .* \\(syzkaller_fuzze\\)",
	}...)
	for _, s := range cfg.Suppressions {
		re, err := regexp.Compile(s)
		if err != nil {
			fatalf("failed to compile suppression '%v': %v", s, err)
		}
		vmCfg.Suppressions = append(vmCfg.Suppressions, re)
	}

	var instances []vm.Instance
	for i := 0; i < cfg.Count; i++ {
		inst, err := vm.Create(cfg.Type, vmCfg, i)
		if err != nil {
			fatalf("failed to create an instance: %v", err)
		}
		instances = append(instances, inst)
	}
	RunManager(cfg, syscalls, instances)
}
Exemple #6
0
func main() {
	flag.Parse()
	cfg, syscalls := parseConfig()
	var instances []vm.Instance
	for i := 0; i < cfg.Count; i++ {
		params, err := json.Marshal(cfg.Params)
		if err != nil {
			fatalf("failed to marshal config params: %v", err)
		}
		inst, err := vm.Create(cfg.Type, cfg.Workdir, syscalls, cfg.Port, i, params)
		if err != nil {
			fatalf("failed to create an instance: %v", err)
		}
		instances = append(instances, inst)
	}
	RunManager(cfg, syscalls, instances)
}
Exemple #7
0
func main() {
	flag.Parse()
	cfg, _, _, err := config.Parse(*flagConfig)
	if err != nil {
		log.Fatalf("%v", err)
	}
	if *flagCount > 0 {
		cfg.Count = *flagCount
	}

	if len(flag.Args()) != 1 {
		log.Fatalf("usage: syz-repro -config=config.file execution.log")
	}
	data, err := ioutil.ReadFile(flag.Args()[0])
	if err != nil {
		log.Fatalf("failed to open log file: %v", err)
	}
	entries := prog.ParseLog(data)
	log.Printf("parsed %v programs", len(entries))

	crashLoc := vm.CrashRe.FindIndex(data)
	if crashLoc == nil {
		log.Fatalf("can't find crash message in the log")
	}
	log.Printf("target crash: '%s'", data[crashLoc[0]:crashLoc[1]])

	instances = make(chan VM, cfg.Count)
	bootRequests = make(chan bool, cfg.Count)
	for i := 0; i < cfg.Count; i++ {
		bootRequests <- true
		go func() {
			for range bootRequests {
				vmCfg, err := config.CreateVMConfig(cfg)
				if err != nil {
					log.Fatalf("failed to create VM config: %v", err)
				}
				inst, err := vm.Create(cfg.Type, vmCfg)
				if err != nil {
					log.Fatalf("failed to create VM: %v", err)
				}
				execprogBin, err := inst.Copy(filepath.Join(cfg.Syzkaller, "bin/syz-execprog"))
				if err != nil {
					log.Fatalf("failed to copy to VM: %v", err)
				}
				executorBin, err := inst.Copy(filepath.Join(cfg.Syzkaller, "bin/syz-executor"))
				if err != nil {
					log.Fatalf("failed to copy to VM: %v", err)
				}
				instances <- VM{inst, execprogBin, executorBin}
			}
		}()
	}

	repro(cfg, entries, crashLoc)

	for {
		select {
		case inst := <-instances:
			inst.Close()
		default:
			return
		}
	}
}
Exemple #8
0
func Run(crashLog []byte, cfg *config.Config, vmIndexes []int) (*Result, error) {
	if len(vmIndexes) == 0 {
		return nil, fmt.Errorf("no VMs provided")
	}
	if _, err := os.Stat(filepath.Join(cfg.Syzkaller, "bin/syz-execprog")); err != nil {
		return nil, fmt.Errorf("bin/syz-execprog is missing (run 'make execprog')")
	}
	entries := prog.ParseLog(crashLog)
	if len(entries) == 0 {
		return nil, fmt.Errorf("crash log does not contain any programs")
	}
	crashDesc, _, crashStart, _ := report.Parse(crashLog, cfg.ParsedIgnores)
	if crashDesc == "" {
		crashStart = len(crashLog) // assuming VM hanged
		crashDesc = "hang"
	}
	Logf(0, "reproducing crash '%v': %v programs, %v VMs", crashDesc, len(entries), len(vmIndexes))

	ctx := &context{
		cfg:          cfg,
		crashDesc:    crashDesc,
		instances:    make(chan *instance, len(vmIndexes)),
		bootRequests: make(chan int, len(vmIndexes)),
	}
	var wg sync.WaitGroup
	wg.Add(len(vmIndexes))
	for _, vmIndex := range vmIndexes {
		ctx.bootRequests <- vmIndex
		go func() {
			defer wg.Done()
			for vmIndex := range ctx.bootRequests {
				var inst *instance
				for try := 0; try < 3; try++ {
					vmCfg, err := config.CreateVMConfig(cfg, vmIndex)
					if err != nil {
						Logf(0, "reproducing crash '%v': failed to create VM config: %v", crashDesc, err)
						time.Sleep(10 * time.Second)
						continue
					}
					vmInst, err := vm.Create(cfg.Type, vmCfg)
					if err != nil {
						Logf(0, "reproducing crash '%v': failed to create VM: %v", crashDesc, err)
						time.Sleep(10 * time.Second)
						continue

					}
					execprogBin, err := vmInst.Copy(filepath.Join(cfg.Syzkaller, "bin/syz-execprog"))
					if err != nil {
						Logf(0, "reproducing crash '%v': failed to copy to VM: %v", crashDesc, err)
						vmInst.Close()
						time.Sleep(10 * time.Second)
						continue
					}
					executorBin, err := vmInst.Copy(filepath.Join(cfg.Syzkaller, "bin/syz-executor"))
					if err != nil {
						Logf(0, "reproducing crash '%v': failed to copy to VM: %v", crashDesc, err)
						vmInst.Close()
						time.Sleep(10 * time.Second)
						continue
					}
					inst = &instance{vmInst, vmIndex, execprogBin, executorBin}
					break
				}
				if inst == nil {
					break
				}
				ctx.instances <- inst
			}
		}()
	}
	go func() {
		wg.Wait()
		close(ctx.instances)
	}()

	res, err := ctx.repro(entries, crashStart)

	close(ctx.bootRequests)
	for inst := range ctx.instances {
		inst.Close()
	}
	return res, err
}
Exemple #9
0
func (mgr *Manager) runInstance(vmCfg *vm.Config, first bool) bool {
	inst, err := vm.Create(mgr.cfg.Type, vmCfg)
	if err != nil {
		logf(0, "failed to create instance: %v", err)
		return false
	}
	defer inst.Close()

	fwdAddr, err := inst.Forward(mgr.port)
	if err != nil {
		logf(0, "failed to setup port forwarding: %v", err)
		return false
	}
	fuzzerBin, err := inst.Copy(filepath.Join(mgr.cfg.Syzkaller, "bin", "syz-fuzzer"))
	if err != nil {
		logf(0, "failed to copy binary: %v", err)
		return false
	}
	executorBin, err := inst.Copy(filepath.Join(mgr.cfg.Syzkaller, "bin", "syz-executor"))
	if err != nil {
		logf(0, "failed to copy binary: %v", err)
		return false
	}

	// TODO: this should be present in the image.
	_, errc, err := inst.Run(10*time.Second, "echo -n 0 > /proc/sys/debug/exception-trace")
	if err == nil {
		<-errc
	}

	// Run the fuzzer binary.
	cover := ""
	if mgr.cfg.NoCover {
		cover = "-nocover=1"
	}
	dropprivs := ""
	if mgr.cfg.NoDropPrivs {
		dropprivs = "-dropprivs=0"
	}
	calls := ""
	if mgr.enabledSyscalls != "" {
		calls = "-calls=" + mgr.enabledSyscalls
	}
	// Leak detection significantly slows down fuzzing, so detect leaks only on the first instance.
	leak := first && mgr.cfg.Leak

	outputC, errorC, err := inst.Run(time.Hour, fmt.Sprintf("%v -executor %v -name %v -manager %v -output=%v -procs %v -leak=%v %v %v %v",
		fuzzerBin, executorBin, vmCfg.Name, fwdAddr, mgr.cfg.Output, mgr.cfg.Procs, leak, cover, dropprivs, calls))
	if err != nil {
		logf(0, "failed to run fuzzer: %v", err)
		return false
	}
	var output []byte
	matchPos := 0
	const (
		beforeContext = 256 << 10
		afterContext  = 64 << 10
	)
	ticker := time.NewTimer(time.Minute)
	for {
		if !ticker.Reset(time.Minute) {
			<-ticker.C
		}
		select {
		case err := <-errorC:
			switch err {
			case vm.TimeoutErr:
				logf(0, "%v: running long enough, restarting", vmCfg.Name)
				return true
			default:
				logf(0, "%v: lost connection: %v", vmCfg.Name, err)
				mgr.saveCrasher(vmCfg.Name, "lost connection", output)
				return true
			}
		case out := <-outputC:
			output = append(output, out...)
			if _, _, _, found := vm.FindCrash(output[matchPos:]); found {
				// Give it some time to finish writing the error message.
				timer := time.NewTimer(10 * time.Second).C
			loop:
				for {
					select {
					case out = <-outputC:
						output = append(output, out...)
					case <-timer:
						break loop
					}
				}
				desc, start, end, _ := vm.FindCrash(output[matchPos:])
				start = start + matchPos - beforeContext
				if start < 0 {
					start = 0
				}
				end = end + matchPos + afterContext
				if end > len(output) {
					end = len(output)
				}
				mgr.saveCrasher(vmCfg.Name, desc, output[start:end])
			}
			if len(output) > 2*beforeContext {
				copy(output, output[len(output)-beforeContext:])
				output = output[:beforeContext]
			}
			matchPos = len(output) - 128
			if matchPos < 0 {
				matchPos = 0
			}
		case <-ticker.C:
			mgr.saveCrasher(vmCfg.Name, "no output", output)
			return true
		}
	}
}
Exemple #10
0
func (mgr *Manager) runInstance(vmCfg *vm.Config, first bool) bool {
	inst, err := vm.Create(mgr.cfg.Type, vmCfg)
	if err != nil {
		logf(0, "failed to create instance: %v", err)
		return false
	}
	defer inst.Close()

	fwdAddr, err := inst.Forward(mgr.port)
	if err != nil {
		logf(0, "failed to setup port forwarding: %v", err)
		return false
	}
	fuzzerBin, err := inst.Copy(filepath.Join(mgr.cfg.Syzkaller, "bin", "syz-fuzzer"))
	if err != nil {
		logf(0, "failed to copy binary: %v", err)
		return false
	}
	executorBin, err := inst.Copy(filepath.Join(mgr.cfg.Syzkaller, "bin", "syz-executor"))
	if err != nil {
		logf(0, "failed to copy binary: %v", err)
		return false
	}

	// TODO: this should be present in the image.
	_, errc, err := inst.Run(10*time.Second, "echo -n 0 > /proc/sys/debug/exception-trace")
	if err == nil {
		<-errc
	}

	// Leak detection significantly slows down fuzzing, so detect leaks only on the first instance.
	leak := first && mgr.cfg.Leak

	// Run the fuzzer binary.
	outputC, errorC, err := inst.Run(time.Hour, fmt.Sprintf("%v -executor %v -name %v -manager %v -output=%v -procs %v -leak=%v -cover=%v -nobody=%v -v %d",
		fuzzerBin, executorBin, vmCfg.Name, fwdAddr, mgr.cfg.Output, mgr.cfg.Procs, leak, mgr.cfg.Cover, mgr.cfg.DropPrivs, *flagV))
	if err != nil {
		logf(0, "failed to run fuzzer: %v", err)
		return false
	}
	startTime := time.Now()
	var crashes []string

	saveCrasher := func(what string, output []byte) {
		for _, re := range mgr.suppressions {
			if re.Match(output) {
				logf(1, "%v: suppressing '%v' with '%v'", vmCfg.Name, what, re.String())
				return
			}
		}
		buf := new(bytes.Buffer)
		fmt.Fprintf(buf, "\n\n")
		if len(crashes) != 0 {
			fmt.Fprintf(buf, "previous crashes:\n")
			for _, c := range crashes {
				fmt.Fprintf(buf, "\t%s\n", c)
			}
		}
		crashes = append(crashes, what)
		fmt.Fprintf(buf, "after running for %v:\n", time.Since(startTime))
		fmt.Fprintf(buf, "%v\n", what)
		output = append([]byte{}, output...)
		output = append(output, buf.Bytes()...)
		filename := fmt.Sprintf("crash-%v-%v", vmCfg.Name, time.Now().UnixNano())
		logf(0, "%v: saving crash '%v' to %v", vmCfg.Name, what, filename)
		ioutil.WriteFile(filepath.Join(mgr.crashdir, filename), output, 0660)
	}

	var output []byte
	matchPos := 0
	const (
		beforeContext = 256 << 10
		afterContext  = 128 << 10
	)
	lastExecuteTime := time.Now()
	ticker := time.NewTimer(time.Minute)
	for {
		if !ticker.Reset(time.Minute) {
			<-ticker.C
		}
		select {
		case err := <-errorC:
			switch err {
			case vm.TimeoutErr:
				logf(0, "%v: running long enough, restarting", vmCfg.Name)
				return true
			default:
				logf(0, "%v: lost connection: %v", vmCfg.Name, err)
				saveCrasher("lost connection", output)
				return true
			}
		case out := <-outputC:
			output = append(output, out...)
			if bytes.Index(output[matchPos:], []byte("executing program")) != -1 {
				lastExecuteTime = time.Now()
			}
			if _, _, _, found := vm.FindCrash(output[matchPos:]); found {
				// Give it some time to finish writing the error message.
				timer := time.NewTimer(10 * time.Second).C
			loop:
				for {
					select {
					case out = <-outputC:
						output = append(output, out...)
					case <-timer:
						break loop
					}
				}
				desc, start, end, _ := vm.FindCrash(output[matchPos:])
				start = start + matchPos - beforeContext
				if start < 0 {
					start = 0
				}
				end = end + matchPos + afterContext
				if end > len(output) {
					end = len(output)
				}
				saveCrasher(desc, output[start:end])
			}
			if len(output) > 2*beforeContext {
				copy(output, output[len(output)-beforeContext:])
				output = output[:beforeContext]
			}
			matchPos = len(output) - 128
			if matchPos < 0 {
				matchPos = 0
			}
			// In some cases kernel constantly prints something to console,
			// but fuzzer is not actually executing programs.
			if mgr.cfg.Type != "local" && time.Since(lastExecuteTime) > 3*time.Minute {
				saveCrasher("not executing programs", output)
				return true
			}
		case <-ticker.C:
			if mgr.cfg.Type != "local" {
				saveCrasher("no output", output)
				return true
			}
		}
	}
}
Exemple #11
0
func main() {
	flag.Parse()
	cfg, _, _, err := config.Parse(*flagConfig)
	if err != nil {
		Fatalf("%v", err)
	}
	if *flagCount > 0 {
		cfg.Count = *flagCount
	}
	if _, err := os.Stat(filepath.Join(cfg.Syzkaller, "bin/syz-execprog")); err != nil {
		Fatalf("bin/syz-execprog is missing (run 'make execprog')")
	}

	if len(flag.Args()) != 1 {
		Fatalf("usage: syz-repro -config=config.file execution.log")
	}
	data, err := ioutil.ReadFile(flag.Args()[0])
	if err != nil {
		Fatalf("failed to open log file: %v", err)
	}
	entries := prog.ParseLog(data)
	Logf(0, "parsed %v programs", len(entries))

	crashDesc, _, crashStart, _ := report.Parse(data)
	if crashDesc == "" {
		crashStart = len(data) // assuming VM hanged
	}

	instances = make(chan VM, cfg.Count)
	bootRequests = make(chan int, cfg.Count)
	for i := 0; i < cfg.Count; i++ {
		bootRequests <- i
		go func() {
			for index := range bootRequests {
				vmCfg, err := config.CreateVMConfig(cfg, index)
				if err != nil {
					Fatalf("failed to create VM config: %v", err)
				}
				inst, err := vm.Create(cfg.Type, vmCfg)
				if err != nil {
					Fatalf("failed to create VM: %v", err)
				}
				execprogBin, err := inst.Copy(filepath.Join(cfg.Syzkaller, "bin/syz-execprog"))
				if err != nil {
					Fatalf("failed to copy to VM: %v", err)
				}
				executorBin, err := inst.Copy(filepath.Join(cfg.Syzkaller, "bin/syz-executor"))
				if err != nil {
					Fatalf("failed to copy to VM: %v", err)
				}
				instances <- VM{inst, index, execprogBin, executorBin}
			}
		}()
	}

	go func() {
		c := make(chan os.Signal, 2)
		signal.Notify(c, syscall.SIGINT)
		<-c
		close(shutdown)
		Logf(-1, "shutting down...")
		<-c
		Fatalf("terminating")
	}()

	repro(cfg, entries, crashStart)
	exit()
}