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  }