// Copyright 2018 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. // Runtest runs syzkaller test programs in sys/*/test/*. Start as: // $ syz-runtest -config manager.config // Also see pkg/runtest docs. package main import ( "errors" "flag" "fmt" "io/ioutil" "log" "net" "os" "path/filepath" "sync" "time" "github.com/google/syzkaller/pkg/instance" "github.com/google/syzkaller/pkg/mgrconfig" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/pkg/report" "github.com/google/syzkaller/pkg/rpctype" "github.com/google/syzkaller/pkg/runtest" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys" "github.com/google/syzkaller/vm" ) var ( flagConfig = flag.String("config", "", "manager config") flagDebug = flag.Bool("debug", false, "debug mode") ) func main() { flag.Parse() cfg, err := mgrconfig.LoadFile(*flagConfig) if err != nil { log.Fatal(err) } target, err := prog.GetTarget(cfg.TargetOS, cfg.TargetArch) if err != nil { log.Fatal(err) } vmPool, err := vm.Create(cfg, *flagDebug) if err != nil { log.Fatal(err) } reporter, err := report.NewReporter(cfg) if err != nil { log.Fatal(err) } osutil.MkdirAll(cfg.Workdir) mgr := &Manager{ cfg: cfg, target: target, vmPool: vmPool, reporter: reporter, debug: *flagDebug, requests: make(chan *runtest.RunRequest, 2*vmPool.Count()), checkResultC: make(chan *rpctype.CheckArgs, 1), checkResultReady: make(chan bool), vmStop: make(chan bool), reqMap: make(map[int]*runtest.RunRequest), lastReq: make(map[string]int), } s, err := rpctype.NewRPCServer(cfg.RPC, mgr) if err != nil { log.Fatalf("failed to create rpc server: %v", err) } mgr.port = s.Addr().(*net.TCPAddr).Port go s.Serve() var wg sync.WaitGroup wg.Add(vmPool.Count()) fmt.Printf("booting VMs...\n") for i := 0; i < vmPool.Count(); i++ { i := i go func() { defer wg.Done() name := fmt.Sprintf("vm-%v", i) for { rep, err := mgr.boot(name, i) if err != nil { log.Fatal(err) } if rep == nil { return } if err := mgr.finishRequest(name, rep); err != nil { log.Fatal(err) } } }() } mgr.checkResult = <-mgr.checkResultC close(mgr.checkResultReady) enabledCalls := make(map[string]map[*prog.Syscall]bool) for sandbox, ids := range mgr.checkResult.EnabledCalls { calls := make(map[*prog.Syscall]bool) for _, id := range ids { calls[target.Syscalls[id]] = true } enabledCalls[sandbox] = calls } for _, feat := range mgr.checkResult.Features { fmt.Printf("%-24v: %v\n", feat.Name, feat.Reason) } for sandbox, calls := range enabledCalls { fmt.Printf("%-24v: %v calls enabled\n", sandbox+" sandbox", len(calls)) } ctx := &runtest.Context{ Dir: filepath.Join(cfg.Syzkaller, "sys", target.OS, "test"), Target: target, Features: mgr.checkResult.Features, EnabledCalls: enabledCalls, Requests: mgr.requests, LogFunc: func(text string) { fmt.Println(text) }, } err = ctx.Run() close(vm.Shutdown) wg.Wait() if err != nil { fmt.Println(err) os.Exit(1) } } type Manager struct { cfg *mgrconfig.Config target *prog.Target vmPool *vm.Pool reporter report.Reporter requests chan *runtest.RunRequest checkResult *rpctype.CheckArgs checkResultReady chan bool checkResultC chan *rpctype.CheckArgs vmStop chan bool port int debug bool reqMu sync.Mutex reqSeq int reqMap map[int]*runtest.RunRequest lastReq map[string]int } func (mgr *Manager) boot(name string, index int) (*report.Report, error) { inst, err := mgr.vmPool.Create(index) 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(mgr.cfg.SyzFuzzerBin) if err != nil { return nil, fmt.Errorf("failed to copy binary: %v", err) } executorBin, err := inst.Copy(mgr.cfg.SyzExecutorBin) if err != nil { return nil, fmt.Errorf("failed to copy binary: %v", err) } cmd := instance.FuzzerCmd(fuzzerBin, executorBin, name, mgr.cfg.TargetOS, mgr.cfg.TargetArch, fwdAddr, mgr.cfg.Sandbox, mgr.cfg.Procs, 0, mgr.cfg.Cover, mgr.debug, false, true) outc, errc, err := inst.Run(time.Hour, mgr.vmStop, cmd) if err != nil { return nil, fmt.Errorf("failed to run fuzzer: %v", err) } rep := inst.MonitorExecution(outc, errc, mgr.reporter, true) return rep, nil } func (mgr *Manager) finishRequest(name string, rep *report.Report) error { mgr.reqMu.Lock() defer mgr.reqMu.Unlock() lastReq := mgr.lastReq[name] req := mgr.reqMap[lastReq] if lastReq == 0 || req == nil { return fmt.Errorf("vm crash: %v\n%s\n%s", rep.Title, rep.Report, rep.Output) } delete(mgr.reqMap, lastReq) delete(mgr.lastReq, name) req.Err = fmt.Errorf("%v", rep.Title) req.Output = rep.Report if len(req.Output) == 0 { req.Output = rep.Output } close(req.Done) return nil } func (mgr *Manager) Connect(a *rpctype.ConnectArgs, r *rpctype.ConnectRes) error { r.GitRevision = sys.GitRevision r.TargetRevision = mgr.target.Revision r.AllSandboxes = true select { case <-mgr.checkResultReady: r.CheckResult = mgr.checkResult default: } return nil } func (mgr *Manager) Check(a *rpctype.CheckArgs, r *int) error { if a.Error != "" { log.Fatalf("machine check: %v", a.Error) } select { case mgr.checkResultC <- a: default: } return nil } func (mgr *Manager) Poll(a *rpctype.RunTestPollReq, r *rpctype.RunTestPollRes) error { req := <-mgr.requests if req == nil { return nil } mgr.reqMu.Lock() if mgr.lastReq[a.Name] != 0 { log.Fatalf("double poll req from %v", a.Name) } mgr.reqSeq++ r.ID = mgr.reqSeq mgr.reqMap[mgr.reqSeq] = req mgr.lastReq[a.Name] = mgr.reqSeq mgr.reqMu.Unlock() if req.Bin != "" { data, err := ioutil.ReadFile(req.Bin) if err != nil { log.Fatalf("failed to read bin file: %v", err) } r.Bin = data return nil } r.Prog = req.P.Serialize() r.Cfg = req.Cfg r.Opts = req.Opts r.Repeat = req.Repeat return nil } func (mgr *Manager) Done(a *rpctype.RunTestDoneArgs, r *int) error { mgr.reqMu.Lock() lastReq := mgr.lastReq[a.Name] if lastReq != a.ID { log.Fatalf("wrong done id %v from %v", a.ID, a.Name) } req := mgr.reqMap[a.ID] delete(mgr.reqMap, a.ID) delete(mgr.lastReq, a.Name) mgr.reqMu.Unlock() if req == nil { log.Fatalf("got done request for unknown id %v", a.ID) } req.Output = a.Output req.Info = a.Info if a.Error != "" { req.Err = errors.New(a.Error) } close(req.Done) return nil }