diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2015-12-23 19:19:45 +0100 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2015-12-23 19:19:45 +0100 |
| commit | d4180ca5c7a24b486b0a941e710b4c287a490f2c (patch) | |
| tree | fa96fbfb02da9007238ee77061358bfe642409cf /tools | |
| parent | 546347d93146b85aa931b8f09390865922d127ef (diff) | |
tools/syz-repro: add reproduction tool
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/syz-repro/repro.go | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/tools/syz-repro/repro.go b/tools/syz-repro/repro.go new file mode 100644 index 000000000..3b80f2adf --- /dev/null +++ b/tools/syz-repro/repro.go @@ -0,0 +1,250 @@ +// Copyright 2015 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. + +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "sort" + "time" + + "github.com/google/syzkaller/config" + "github.com/google/syzkaller/csource" + "github.com/google/syzkaller/fileutil" + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/vm" + _ "github.com/google/syzkaller/vm/kvm" + _ "github.com/google/syzkaller/vm/qemu" +) + +var ( + flagConfig = flag.String("config", "", "configuration file") + flagCount = flag.Int("count", 0, "number of VMs to use (overrides config count param)") + + instances chan vm.Instance + bootRequests chan bool +) + +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.Instance, 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) + } + if err := inst.Copy(filepath.Join(cfg.Syzkaller, "bin/syz-execprog"), "/syz-execprog"); err != nil { + log.Fatalf("failed to copy to VM: %v", err) + } + if err := inst.Copy(filepath.Join(cfg.Syzkaller, "bin/syz-executor"), "/syz-executor"); err != nil { + log.Fatalf("failed to copy to VM: %v", err) + } + instances <- inst + } + }() + } + + repro(cfg, entries, crashLoc) + + for { + select { + case inst := <-instances: + inst.Close() + default: + return + } + } +} + +func repro(cfg *config.Config, entries []*prog.LogEntry, crashLoc []int) { + // Cut programs that were executed after crash. + for i, ent := range entries { + if ent.Start > crashLoc[0] { + entries = entries[:i] + break + } + } + // Extract last program on every proc. + procs := make(map[int]int) + for i, ent := range entries { + procs[ent.Proc] = i + } + var indices []int + for _, idx := range procs { + indices = append(indices, idx) + } + sort.Ints(indices) + var suspected []*prog.LogEntry + for i := len(indices) - 1; i >= 0; i-- { + suspected = append(suspected, entries[indices[i]]) + } + // Execute the suspected programs. + log.Printf("the suspected programs are:") + for _, ent := range suspected { + log.Printf("on proc %v:\n%s\n", ent.Proc, ent.P.Serialize()) + } + var p *prog.Prog + multiplier := 1 + for ; p == nil && multiplier <= 100; multiplier *= 10 { + for _, ent := range suspected { + if testProg(cfg, ent.P, multiplier, true, true) { + p = ent.P + break + } + } + } + if p == nil { + log.Printf("no program crashed") + return + } + multiplier *= 10 + log.Printf("minimizing program") + + p, _ = prog.Minimize(p, -1, func(p1 *prog.Prog, callIndex int) bool { + return testProg(cfg, p1, multiplier, true, true) + }) + + opts := csource.Options{ + Threaded: true, + Collide: true, + } + if testProg(cfg, p, multiplier, true, false) { + opts.Collide = false + if testProg(cfg, p, multiplier, false, false) { + opts.Threaded = false + } + } + + src := csource.Write(p, opts) + log.Printf("C source:\n%s\n", src) + srcf, err := fileutil.WriteTempFile(src) + if err != nil { + log.Fatalf("%v", err) + } + bin, err := csource.Build(srcf) + if err != nil { + log.Fatalf("%v", err) + } + defer os.Remove(bin) + testBin(cfg, bin) +} + +func returnInstance(inst vm.Instance, res bool) { + if res { + // The test crashed, discard the VM and issue another boot request. + bootRequests <- true + inst.Close() + } else { + // The test did not crash, reuse the same VM in future. + instances <- inst + } +} + +func testProg(cfg *config.Config, p *prog.Prog, multiplier int, threaded, collide bool) (res bool) { + log.Printf("booting VM") + inst := <-instances + defer func() { + returnInstance(inst, res) + }() + + pstr := p.Serialize() + progFile, err := fileutil.WriteTempFile(pstr) + if err != nil { + log.Fatalf("%v", err) + } + defer os.Remove(progFile) + if err := inst.Copy(progFile, "/syz-prog"); err != nil { + log.Fatalf("failed to copy to VM: %v", err) + } + + repeat := 100 + timeoutSec := 10 * repeat / cfg.Procs + if threaded { + repeat *= 10 + timeoutSec *= 1 + } + repeat *= multiplier + timeoutSec *= multiplier + timeout := time.Duration(timeoutSec) * time.Second + command := fmt.Sprintf("/syz-execprog -executor /syz-executor -cover=0 -procs=%v -repeat=%v -threaded=%v -collide=%v /syz-prog", + cfg.Procs, repeat, threaded, collide) + log.Printf("testing program (threaded=%v, collide=%v, repeat=%v, timeout=%v):\n%s\n", + threaded, collide, repeat, timeout, pstr) + return testImpl(inst, command, timeout) +} + +func testBin(cfg *config.Config, bin string) (res bool) { + log.Printf("booting VM") + inst := <-instances + defer func() { + returnInstance(inst, res) + }() + + if err := inst.Copy(bin, "/syz-bin"); err != nil { + log.Fatalf("failed to copy to VM: %v", err) + } + log.Printf("testing compiled C program") + return testImpl(inst, "/syz-bin", 10*time.Second) +} + +func testImpl(inst vm.Instance, command string, timeout time.Duration) (res bool) { + outc, errc, err := inst.Run(timeout, command) + if err != nil { + log.Fatalf("failed to run command in VM: %v", err) + } + var output []byte + for { + select { + case out := <-outc: + output = append(output, out...) + if loc := vm.CrashRe.FindIndex(output); loc != nil { + log.Printf("program crashed with '%s'", output[loc[0]:loc[1]]) + return true + } + case err := <-errc: + if err != nil { + log.Printf("program crashed with result '%v'", err) + return true + } + log.Printf("program did not crash") + return false + } + } +} |
