github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/tools/syz-runtest/runtest.go (about)

     1  // Copyright 2018 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  // Runtest runs syzkaller test programs in sys/*/test/*. Start as:
     5  // $ syz-runtest -config manager.config
     6  // Also see pkg/runtest docs.
     7  package main
     8  
     9  import (
    10  	"errors"
    11  	"flag"
    12  	"fmt"
    13  	"log"
    14  	"net"
    15  	"os"
    16  	"path/filepath"
    17  	"slices"
    18  	"sync"
    19  	"sync/atomic"
    20  	"time"
    21  
    22  	"github.com/google/syzkaller/pkg/flatrpc"
    23  	"github.com/google/syzkaller/pkg/fuzzer/queue"
    24  	"github.com/google/syzkaller/pkg/instance"
    25  	"github.com/google/syzkaller/pkg/mgrconfig"
    26  	"github.com/google/syzkaller/pkg/osutil"
    27  	"github.com/google/syzkaller/pkg/report"
    28  	"github.com/google/syzkaller/pkg/rpctype"
    29  	"github.com/google/syzkaller/pkg/runtest"
    30  	"github.com/google/syzkaller/pkg/vminfo"
    31  	"github.com/google/syzkaller/prog"
    32  	_ "github.com/google/syzkaller/sys"
    33  	"github.com/google/syzkaller/vm"
    34  )
    35  
    36  var (
    37  	flagConfig = flag.String("config", "", "manager config")
    38  	flagDebug  = flag.Bool("debug", false, "debug mode")
    39  	flagTests  = flag.String("tests", "", "prefix to match test file names")
    40  )
    41  
    42  func main() {
    43  	flag.Parse()
    44  	cfg, err := mgrconfig.LoadFile(*flagConfig)
    45  	if err != nil {
    46  		log.Fatal(err)
    47  	}
    48  	vmPool, err := vm.Create(cfg, *flagDebug)
    49  	if err != nil {
    50  		log.Fatal(err)
    51  	}
    52  	reporter, err := report.NewReporter(cfg)
    53  	if err != nil {
    54  		log.Fatal(err)
    55  	}
    56  	osutil.MkdirAll(cfg.Workdir)
    57  	mgr := &Manager{
    58  		cfg:          cfg,
    59  		vmPool:       vmPool,
    60  		checker:      vminfo.New(cfg),
    61  		reporter:     reporter,
    62  		debug:        *flagDebug,
    63  		checkResultC: make(chan *rpctype.CheckArgs, 1),
    64  		vmStop:       make(chan bool),
    65  		reqMap:       make(map[int64]*queue.Request),
    66  		pending:      make(map[string]map[int64]bool),
    67  	}
    68  	mgr.checkFiles = mgr.checker.RequiredFiles()
    69  	mgr.source = queue.DynamicSource(mgr.checker)
    70  	s, err := rpctype.NewRPCServer(cfg.RPC, "Manager", mgr)
    71  	if err != nil {
    72  		log.Fatalf("failed to create rpc server: %v", err)
    73  	}
    74  	mgr.port = s.Addr().(*net.TCPAddr).Port
    75  	go s.Serve()
    76  	var wg sync.WaitGroup
    77  	wg.Add(vmPool.Count())
    78  	fmt.Printf("booting VMs...\n")
    79  	var nameSeq atomic.Uint64
    80  	for i := 0; i < vmPool.Count(); i++ {
    81  		i := i
    82  		go func() {
    83  			defer wg.Done()
    84  			for {
    85  				name := fmt.Sprintf("vm-%v", nameSeq.Add(1))
    86  				rep, err := mgr.boot(name, i)
    87  				if err != nil {
    88  					log.Fatal(err)
    89  				}
    90  				if rep == nil {
    91  					return
    92  				}
    93  				if err := mgr.finishRequests(name, rep); err != nil {
    94  					log.Fatal(err)
    95  				}
    96  			}
    97  		}()
    98  	}
    99  	checkResult := <-mgr.checkResultC
   100  	calls, _, features, err := mgr.checker.Run(checkResult.Files, checkResult.Features)
   101  	if err != nil {
   102  		log.Fatalf("failed to detect enabled syscalls: %v", err)
   103  	}
   104  	calls, _ = cfg.Target.TransitivelyEnabledCalls(calls)
   105  	enabledCalls := make(map[string]map[*prog.Syscall]bool)
   106  	// TODO: restore checking/testing of all other sandboxes (we used to test them).
   107  	// Note: syz_emit_ethernet/syz_extract_tcp_res were manually disabled for "" ("no") sandbox,
   108  	// b/c tun is not setup without sandbox.
   109  	enabledCalls[mgr.cfg.Sandbox] = calls
   110  	for feat, info := range features {
   111  		fmt.Printf("%-24v: %v\n", flatrpc.EnumNamesFeature[feat], info.Reason)
   112  	}
   113  	for sandbox, calls := range enabledCalls {
   114  		if sandbox == "" {
   115  			sandbox = "no"
   116  		}
   117  		fmt.Printf("%-24v: %v calls enabled\n", sandbox+" sandbox", len(calls))
   118  	}
   119  	ctx := &runtest.Context{
   120  		Dir:          filepath.Join(cfg.Syzkaller, "sys", cfg.Target.OS, "test"),
   121  		Target:       cfg.Target,
   122  		Features:     features.Enabled(),
   123  		EnabledCalls: enabledCalls,
   124  		LogFunc:      func(text string) { fmt.Println(text) },
   125  		Verbose:      true,
   126  		Debug:        *flagDebug,
   127  		Tests:        *flagTests,
   128  	}
   129  	mgr.source.Store(ctx)
   130  	err = ctx.Run()
   131  	close(vm.Shutdown)
   132  	wg.Wait()
   133  	if err != nil {
   134  		fmt.Println(err)
   135  		os.Exit(1)
   136  	}
   137  }
   138  
   139  type Manager struct {
   140  	cfg          *mgrconfig.Config
   141  	vmPool       *vm.Pool
   142  	checker      *vminfo.Checker
   143  	checkFiles   []string
   144  	reporter     *report.Reporter
   145  	checkResultC chan *rpctype.CheckArgs
   146  	vmStop       chan bool
   147  	port         int
   148  	debug        bool
   149  	source       *queue.DynamicSourceCtl
   150  
   151  	reqMu   sync.Mutex
   152  	reqSeq  int64
   153  	reqMap  map[int64]*queue.Request
   154  	pending map[string]map[int64]bool
   155  }
   156  
   157  func (mgr *Manager) boot(name string, index int) (*report.Report, error) {
   158  	inst, err := mgr.vmPool.Create(index)
   159  	if err != nil {
   160  		return nil, fmt.Errorf("failed to create instance: %w", err)
   161  	}
   162  	defer inst.Close()
   163  
   164  	fwdAddr, err := inst.Forward(mgr.port)
   165  	if err != nil {
   166  		return nil, fmt.Errorf("failed to setup port forwarding: %w", err)
   167  	}
   168  
   169  	fuzzerBin, err := inst.Copy(mgr.cfg.FuzzerBin)
   170  	if err != nil {
   171  		return nil, fmt.Errorf("failed to copy binary: %w", err)
   172  	}
   173  
   174  	// If SyzExecutorCmd is provided, it means that syz-executor is already in
   175  	// the image, so no need to copy it.
   176  	executorBin := mgr.cfg.SysTarget.ExecutorBin
   177  	if executorBin == "" {
   178  		executorBin, err = inst.Copy(mgr.cfg.ExecutorBin)
   179  		if err != nil {
   180  			return nil, fmt.Errorf("failed to copy binary: %w", err)
   181  		}
   182  	}
   183  	args := &instance.FuzzerCmdArgs{
   184  		Fuzzer:    fuzzerBin,
   185  		Executor:  executorBin,
   186  		Name:      name,
   187  		OS:        mgr.cfg.TargetOS,
   188  		Arch:      mgr.cfg.TargetArch,
   189  		FwdAddr:   fwdAddr,
   190  		Sandbox:   mgr.cfg.Sandbox,
   191  		Procs:     1,
   192  		Verbosity: 0,
   193  		Cover:     mgr.cfg.Cover,
   194  		Debug:     mgr.debug,
   195  		Test:      false,
   196  		Optional: &instance.OptionalFuzzerArgs{
   197  			Slowdown:   mgr.cfg.Timeouts.Slowdown,
   198  			SandboxArg: mgr.cfg.SandboxArg,
   199  		},
   200  	}
   201  	cmd := instance.FuzzerCmd(args)
   202  	_, rep, err := inst.Run(time.Hour, mgr.reporter, cmd, vm.StopChan(mgr.vmStop))
   203  	if err != nil {
   204  		return nil, fmt.Errorf("failed to run fuzzer: %w", err)
   205  	}
   206  	return rep, nil
   207  }
   208  
   209  func (mgr *Manager) finishRequests(name string, rep *report.Report) error {
   210  	mgr.reqMu.Lock()
   211  	defer mgr.reqMu.Unlock()
   212  	for id := range mgr.pending[name] {
   213  		req := mgr.reqMap[id]
   214  		if req == nil {
   215  			return fmt.Errorf("vm crash: %v\n%s\n%s", rep.Title, rep.Report, rep.Output)
   216  		}
   217  		delete(mgr.reqMap, id)
   218  		output := rep.Report
   219  		if len(output) == 0 {
   220  			output = rep.Output
   221  		}
   222  		req.Done(&queue.Result{
   223  			Status: queue.Crashed,
   224  			Err:    fmt.Errorf("%v", rep.Title),
   225  			Output: slices.Clone(output),
   226  		})
   227  	}
   228  	delete(mgr.pending, name)
   229  	return nil
   230  }
   231  
   232  func (mgr *Manager) Connect(a *rpctype.ConnectArgs, r *rpctype.ConnectRes) error {
   233  	r.ReadFiles = append(mgr.checker.RequiredFiles(), mgr.checkFiles...)
   234  	r.ReadGlobs = mgr.cfg.Target.RequiredGlobs()
   235  	for feat := range flatrpc.EnumNamesFeature {
   236  		r.Features |= feat
   237  	}
   238  	return nil
   239  }
   240  
   241  func (mgr *Manager) Check(a *rpctype.CheckArgs, r *rpctype.CheckRes) error {
   242  	if a.Error != "" {
   243  		log.Fatalf("machine check: %v", a.Error)
   244  	}
   245  	select {
   246  	case mgr.checkResultC <- a:
   247  	default:
   248  	}
   249  	return nil
   250  }
   251  
   252  func (mgr *Manager) ExchangeInfo(a *rpctype.ExchangeInfoRequest, r *rpctype.ExchangeInfoReply) error {
   253  	mgr.reqMu.Lock()
   254  	defer mgr.reqMu.Unlock()
   255  	if mgr.pending[a.Name] == nil {
   256  		mgr.pending[a.Name] = make(map[int64]bool)
   257  	}
   258  	for i := range a.Results {
   259  		res := a.Results[i]
   260  		if !mgr.pending[a.Name][res.ID] {
   261  			log.Fatalf("runner %v wasn't executing request %v", a.Name, res.ID)
   262  		}
   263  		delete(mgr.pending[a.Name], res.ID)
   264  		req := mgr.reqMap[res.ID]
   265  		if req == nil {
   266  			log.Fatalf("request %v does not exist", res.ID)
   267  		}
   268  		delete(mgr.reqMap, res.ID)
   269  		if req == nil {
   270  			log.Fatalf("got done request for unknown id %v", res.ID)
   271  		}
   272  		result := &queue.Result{
   273  			Status: queue.Success,
   274  			Info:   &res.Info,
   275  			Output: res.Output,
   276  		}
   277  		if res.Error != "" {
   278  			result.Status = queue.ExecFailure
   279  			result.Err = errors.New(res.Error)
   280  		}
   281  		req.Done(result)
   282  	}
   283  	for i := 0; i < a.NeedProgs; i++ {
   284  		req := mgr.source.Next()
   285  		if req == nil {
   286  			break
   287  		}
   288  		mgr.reqSeq++
   289  		mgr.reqMap[mgr.reqSeq] = req
   290  		mgr.pending[a.Name][mgr.reqSeq] = true
   291  		var progData []byte
   292  		var err error
   293  		if req.BinaryFile != "" {
   294  			progData, err = os.ReadFile(req.BinaryFile)
   295  		} else {
   296  			progData, err = req.Prog.SerializeForExec()
   297  		}
   298  		if err != nil {
   299  			log.Fatal(err)
   300  		}
   301  		r.Requests = append(r.Requests, rpctype.ExecutionRequest{
   302  			ID:           mgr.reqSeq,
   303  			ProgData:     progData,
   304  			ExecOpts:     req.ExecOpts,
   305  			IsBinary:     req.BinaryFile != "",
   306  			ResetState:   req.BinaryFile == "",
   307  			ReturnOutput: true,
   308  			ReturnError:  true,
   309  			Repeat:       req.Repeat,
   310  		})
   311  	}
   312  	return nil
   313  }
   314  
   315  func (mgr *Manager) StartExecuting(a *rpctype.ExecutingRequest, r *int) error {
   316  	return nil
   317  }