github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/tools/syz-crush/crush.go (about) 1 // Copyright 2016 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 // syz-crush replays crash log on multiple VMs. Usage: 5 // 6 // syz-crush -config=config.file execution.log 7 // 8 // Intended for reproduction of particularly elusive crashes. 9 package main 10 11 import ( 12 "flag" 13 "fmt" 14 "log" 15 "os" 16 "path/filepath" 17 "strings" 18 "sync/atomic" 19 "time" 20 21 "github.com/google/syzkaller/pkg/csource" 22 "github.com/google/syzkaller/pkg/hash" 23 "github.com/google/syzkaller/pkg/instance" 24 "github.com/google/syzkaller/pkg/mgrconfig" 25 "github.com/google/syzkaller/pkg/osutil" 26 "github.com/google/syzkaller/pkg/report" 27 "github.com/google/syzkaller/vm" 28 ) 29 30 var ( 31 flagConfig = flag.String("config", "", "manager configuration file") 32 flagDebug = flag.Bool("debug", false, "dump all VM output to console") 33 flagRestartTime = flag.Duration("restart_time", 0, "how long to run the test") 34 flagInfinite = flag.Bool("infinite", true, "by default test is run for ever, -infinite=false to stop on crash") 35 flagStrace = flag.Bool("strace", false, "run under strace (binary must be set in the config file") 36 ) 37 38 type FileType int 39 40 const ( 41 LogFile FileType = iota 42 CProg 43 ) 44 45 func main() { 46 flag.Parse() 47 if len(flag.Args()) != 1 || *flagConfig == "" { 48 fmt.Fprintf(os.Stderr, "usage: syz-crush [flags] <execution.log|creprog.c>\n") 49 flag.PrintDefaults() 50 os.Exit(1) 51 } 52 cfg, err := mgrconfig.LoadFile(*flagConfig) 53 if err != nil { 54 log.Fatal(err) 55 } 56 if *flagRestartTime == 0 { 57 *flagRestartTime = cfg.Timeouts.VMRunningTime 58 } 59 if *flagInfinite { 60 log.Printf("running infinitely and restarting VM every %v", *flagRestartTime) 61 } else { 62 log.Printf("running until crash is found or till %v", *flagRestartTime) 63 } 64 if *flagStrace && cfg.StraceBin == "" { 65 log.Fatalf("strace_bin must not be empty in order to run with -strace") 66 } 67 68 vmPool, err := vm.Create(cfg, *flagDebug) 69 if err != nil { 70 log.Fatalf("%v", err) 71 } 72 defer vmPool.Close() 73 74 reporter, err := report.NewReporter(cfg) 75 if err != nil { 76 log.Fatalf("%v", err) 77 } 78 79 reproduceMe := flag.Args()[0] 80 if cfg.Tag == "" { 81 // If no tag is given, use reproducer name as the tag. 82 cfg.Tag = filepath.Base(reproduceMe) 83 } 84 runType := LogFile 85 if strings.HasSuffix(reproduceMe, ".c") { 86 runType = CProg 87 log.Printf("reproducing from C source file: %v", reproduceMe) 88 } else { 89 log.Printf("reproducing from log file: %v", reproduceMe) 90 } 91 log.Printf("booting %v test machines...", vmPool.Count()) 92 runDone := make(chan *instance.RunResult) 93 var shutdown, stoppedWorkers uint32 94 95 for i := 0; i < vmPool.Count(); i++ { 96 go func(index int) { 97 for { 98 runDone <- runInstance(cfg, reporter, vmPool, index, *flagRestartTime, runType) 99 if atomic.LoadUint32(&shutdown) != 0 || !*flagInfinite { 100 // If this is the last worker then we can close the channel. 101 if atomic.AddUint32(&stoppedWorkers, 1) == uint32(vmPool.Count()) { 102 log.Printf("vm-%v: closing channel", index) 103 close(runDone) 104 } 105 break 106 } 107 } 108 log.Printf("vm-%v: done", index) 109 }(i) 110 } 111 112 shutdownC := make(chan struct{}) 113 osutil.HandleInterrupts(shutdownC) 114 go func() { 115 <-shutdownC 116 atomic.StoreUint32(&shutdown, 1) 117 close(vm.Shutdown) 118 }() 119 120 var count, crashes int 121 for rep := range runDone { 122 count++ 123 if rep != nil { 124 crashes++ 125 storeCrash(cfg, rep) 126 } 127 log.Printf("instances executed: %v, crashes: %v", count, crashes) 128 } 129 130 log.Printf("all done. reproduced %v crashes. reproduce rate %.2f%%", crashes, float64(crashes)/float64(count)*100.0) 131 } 132 133 func storeCrash(cfg *mgrconfig.Config, res *instance.RunResult) { 134 rep := res.Report 135 id := hash.String([]byte(rep.Title)) 136 dir := filepath.Join(filepath.Dir(flag.Args()[0]), "crashes", id) 137 osutil.MkdirAll(dir) 138 139 index := 0 140 for ; osutil.IsExist(filepath.Join(dir, fmt.Sprintf("log%v", index))); index++ { 141 } 142 log.Printf("saving crash '%v' with index %v in %v", rep.Title, index, dir) 143 144 if err := osutil.WriteFile(filepath.Join(dir, "description"), []byte(rep.Title+"\n")); err != nil { 145 log.Printf("failed to write crash description: %v", err) 146 } 147 if err := osutil.WriteFile(filepath.Join(dir, fmt.Sprintf("log%v", index)), res.Output); err != nil { 148 log.Printf("failed to write crash log: %v", err) 149 } 150 if err := osutil.WriteFile(filepath.Join(dir, fmt.Sprintf("tag%v", index)), []byte(cfg.Tag)); err != nil { 151 log.Printf("failed to write crash tag: %v", err) 152 } 153 if len(rep.Report) > 0 { 154 if err := osutil.WriteFile(filepath.Join(dir, fmt.Sprintf("report%v", index)), rep.Report); err != nil { 155 log.Printf("failed to write crash report: %v", err) 156 } 157 } 158 if err := osutil.CopyFile(flag.Args()[0], filepath.Join(dir, fmt.Sprintf("reproducer%v", index))); err != nil { 159 log.Printf("failed to write crash reproducer: %v", err) 160 } 161 } 162 163 func runInstance(cfg *mgrconfig.Config, reporter *report.Reporter, 164 vmPool *vm.Pool, index int, timeout time.Duration, runType FileType) *instance.RunResult { 165 log.Printf("vm-%v: starting", index) 166 optArgs := &instance.OptionalConfig{} 167 if *flagStrace { 168 optArgs.StraceBin = cfg.StraceBin 169 } 170 var err error 171 inst, err := instance.CreateExecProgInstance(vmPool, index, cfg, reporter, optArgs) 172 if err != nil { 173 log.Printf("failed to set up instance: %v", err) 174 return nil 175 } 176 defer inst.VMInstance.Close() 177 file := flag.Args()[0] 178 var res *instance.RunResult 179 if runType == LogFile { 180 opts := csource.DefaultOpts(cfg) 181 opts.Repeat, opts.Threaded = true, true 182 res, err = inst.RunSyzProgFile(file, timeout, opts, instance.SyzExitConditions) 183 } else { 184 var src []byte 185 src, err = os.ReadFile(file) 186 if err != nil { 187 log.Fatalf("error reading source file from '%s'", file) 188 } 189 res, err = inst.RunCProgRaw(src, cfg.Target, timeout) 190 } 191 if err != nil { 192 log.Printf("failed to execute program: %v", err) 193 return nil 194 } 195 if res.Report != nil { 196 log.Printf("vm-%v: crash: %v", index, res.Report.Title) 197 return res 198 } 199 log.Printf("vm-%v: running long enough, stopping", index) 200 return nil 201 }