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

     1  // Copyright 2015 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  package main
     5  
     6  import (
     7  	"flag"
     8  	"fmt"
     9  	"net/http"
    10  	_ "net/http/pprof"
    11  	"os"
    12  	"path/filepath"
    13  	"runtime"
    14  	"runtime/debug"
    15  	"sync"
    16  	"sync/atomic"
    17  	"time"
    18  
    19  	"github.com/google/syzkaller/pkg/flatrpc"
    20  	"github.com/google/syzkaller/pkg/host"
    21  	"github.com/google/syzkaller/pkg/ipc"
    22  	"github.com/google/syzkaller/pkg/ipc/ipcconfig"
    23  	"github.com/google/syzkaller/pkg/log"
    24  	"github.com/google/syzkaller/pkg/osutil"
    25  	"github.com/google/syzkaller/pkg/rpctype"
    26  	"github.com/google/syzkaller/pkg/signal"
    27  	"github.com/google/syzkaller/pkg/tool"
    28  	"github.com/google/syzkaller/prog"
    29  	_ "github.com/google/syzkaller/sys"
    30  	"github.com/google/syzkaller/sys/targets"
    31  )
    32  
    33  type FuzzerTool struct {
    34  	name     string
    35  	executor string
    36  	gate     *ipc.Gate
    37  	manager  *rpctype.RPCClient
    38  	// TODO: repair triagedCandidates logic, it's broken now.
    39  	triagedCandidates uint32
    40  	timeouts          targets.Timeouts
    41  	leakFrames        []string
    42  
    43  	noExecRequests atomic.Uint64
    44  	noExecDuration atomic.Uint64
    45  
    46  	requests  chan rpctype.ExecutionRequest
    47  	results   chan executionResult
    48  	signalMu  sync.RWMutex
    49  	maxSignal signal.Signal
    50  }
    51  
    52  // executionResult offloads some computations from the proc loop
    53  // to the communication thread.
    54  type executionResult struct {
    55  	rpctype.ExecutionRequest
    56  	procID int
    57  	try    int
    58  	info   *ipc.ProgInfo
    59  	output []byte
    60  	err    string
    61  }
    62  
    63  // Gate size controls how deep in the log the last executed by every proc
    64  // program may be. The intent is to make sure that, given the output log,
    65  // we always understand what was happening.
    66  // Judging by the logs collected on syzbot, 32 should be a reasonable figure.
    67  // It coincides with prog.MaxPids.
    68  const gateSize = prog.MaxPids
    69  
    70  // TODO: split into smaller methods.
    71  // nolint: funlen, gocyclo
    72  func main() {
    73  	debug.SetGCPercent(50)
    74  
    75  	var (
    76  		flagName      = flag.String("name", "test", "unique name for manager")
    77  		flagOS        = flag.String("os", runtime.GOOS, "target OS")
    78  		flagArch      = flag.String("arch", runtime.GOARCH, "target arch")
    79  		flagManager   = flag.String("manager", "", "manager rpc address")
    80  		flagProcs     = flag.Int("procs", 1, "number of parallel test processes")
    81  		flagTest      = flag.Bool("test", false, "enable image testing mode") // used by syz-ci
    82  		flagPprofPort = flag.Int("pprof_port", 0, "HTTP port for the pprof endpoint (disabled if 0)")
    83  	)
    84  	defer tool.Init()()
    85  	log.Logf(0, "fuzzer started")
    86  
    87  	target, err := prog.GetTarget(*flagOS, *flagArch)
    88  	if err != nil {
    89  		log.SyzFatal(err)
    90  	}
    91  
    92  	config, execOpts, err := ipcconfig.Default(target)
    93  	if err != nil {
    94  		log.SyzFatalf("failed to create default ipc config: %v", err)
    95  	}
    96  	timeouts := config.Timeouts
    97  	executor := config.Executor
    98  	shutdown := make(chan struct{})
    99  	osutil.HandleInterrupts(shutdown)
   100  	go func() {
   101  		// Handles graceful preemption on GCE.
   102  		<-shutdown
   103  		log.Logf(0, "SYZ-FUZZER: PREEMPTED")
   104  		os.Exit(1)
   105  	}()
   106  
   107  	if *flagPprofPort != 0 {
   108  		setupPprofHandler(*flagPprofPort)
   109  	}
   110  
   111  	if *flagTest {
   112  		checkArgs := &checkArgs{
   113  			target:         target,
   114  			sandbox:        ipc.FlagsToSandbox(execOpts.EnvFlags),
   115  			ipcConfig:      config,
   116  			ipcExecOpts:    execOpts,
   117  			gitRevision:    prog.GitRevision,
   118  			targetRevision: target.Revision,
   119  		}
   120  		testImage(*flagManager, checkArgs)
   121  		return
   122  	}
   123  
   124  	log.Logf(0, "dialing manager at %v", *flagManager)
   125  	manager, err := rpctype.NewRPCClient(*flagManager)
   126  	if err != nil {
   127  		log.SyzFatalf("failed to create an RPC client: %v ", err)
   128  	}
   129  
   130  	log.Logf(1, "connecting to manager...")
   131  	a := &rpctype.ConnectArgs{
   132  		Name:        *flagName,
   133  		GitRevision: prog.GitRevision,
   134  		SyzRevision: target.Revision,
   135  	}
   136  	a.ExecutorArch, a.ExecutorSyzRevision, a.ExecutorGitRevision, err = executorVersion(executor)
   137  	if err != nil {
   138  		log.SyzFatalf("failed to run executor version: %v ", err)
   139  	}
   140  	r := &rpctype.ConnectRes{}
   141  	if err := manager.Call("Manager.Connect", a, r); err != nil {
   142  		log.SyzFatalf("failed to call Manager.Connect(): %v ", err)
   143  	}
   144  	checkReq := &rpctype.CheckArgs{
   145  		Name:  *flagName,
   146  		Files: host.ReadFiles(r.ReadFiles),
   147  		Globs: make(map[string][]string),
   148  	}
   149  	features, err := host.SetupFeatures(target, executor, r.Features, nil)
   150  	if err != nil {
   151  		log.SyzFatalf("failed to setup features: %v ", err)
   152  	}
   153  	checkReq.Features = features
   154  	for _, glob := range r.ReadGlobs {
   155  		files, err := filepath.Glob(filepath.FromSlash(glob))
   156  		if err != nil && checkReq.Error == "" {
   157  			checkReq.Error = fmt.Sprintf("failed to read glob %q: %v", glob, err)
   158  		}
   159  		checkReq.Globs[glob] = files
   160  	}
   161  	checkRes := new(rpctype.CheckRes)
   162  	if err := manager.Call("Manager.Check", checkReq, checkRes); err != nil {
   163  		log.SyzFatalf("Manager.Check call failed: %v", err)
   164  	}
   165  	if checkReq.Error != "" {
   166  		log.SyzFatalf("%v", checkReq.Error)
   167  	}
   168  
   169  	if checkRes.CoverFilterBitmap != nil {
   170  		if err := osutil.WriteFile("syz-cover-bitmap", checkRes.CoverFilterBitmap); err != nil {
   171  			log.SyzFatalf("failed to write syz-cover-bitmap: %v", err)
   172  		}
   173  	}
   174  
   175  	inputsCount := *flagProcs * 2
   176  	fuzzerTool := &FuzzerTool{
   177  		name:       *flagName,
   178  		executor:   executor,
   179  		manager:    manager,
   180  		timeouts:   timeouts,
   181  		leakFrames: r.MemoryLeakFrames,
   182  
   183  		requests: make(chan rpctype.ExecutionRequest, 2*inputsCount),
   184  		results:  make(chan executionResult, 2*inputsCount),
   185  	}
   186  	fuzzerTool.filterDataRaceFrames(r.DataRaceFrames)
   187  	var gateCallback func()
   188  	for _, feat := range features {
   189  		if feat.Id == flatrpc.FeatureLeak && feat.Reason == "" {
   190  			gateCallback = fuzzerTool.leakGateCallback
   191  		}
   192  	}
   193  	fuzzerTool.gate = ipc.NewGate(gateSize, gateCallback)
   194  
   195  	log.Logf(0, "starting %v executor processes", *flagProcs)
   196  	for pid := 0; pid < *flagProcs; pid++ {
   197  		startProc(fuzzerTool, pid, config)
   198  	}
   199  
   200  	// Query enough inputs at the beginning.
   201  	fuzzerTool.exchangeDataCall(nil, 0)
   202  	go fuzzerTool.exchangeDataWorker()
   203  	fuzzerTool.exchangeDataWorker()
   204  }
   205  
   206  func (tool *FuzzerTool) leakGateCallback() {
   207  	// Leak checking is very slow so we don't do it while triaging the corpus
   208  	// (otherwise it takes infinity). When we have presumably triaged the corpus
   209  	// (triagedCandidates == 1), we run leak checking bug ignore the result
   210  	// to flush any previous leaks. After that (triagedCandidates == 2)
   211  	// we do actual leak checking and report leaks.
   212  	triagedCandidates := atomic.LoadUint32(&tool.triagedCandidates)
   213  	if triagedCandidates == 0 {
   214  		return
   215  	}
   216  	args := append([]string{"leak"}, tool.leakFrames...)
   217  	timeout := tool.timeouts.NoOutput * 9 / 10
   218  	output, err := osutil.RunCmd(timeout, "", tool.executor, args...)
   219  	if err != nil && triagedCandidates == 2 {
   220  		// If we exit right away, dying executors will dump lots of garbage to console.
   221  		os.Stdout.Write(output)
   222  		fmt.Printf("BUG: leak checking failed\n")
   223  		time.Sleep(time.Hour)
   224  		os.Exit(1)
   225  	}
   226  	if triagedCandidates == 1 {
   227  		atomic.StoreUint32(&tool.triagedCandidates, 2)
   228  	}
   229  }
   230  
   231  func (tool *FuzzerTool) filterDataRaceFrames(frames []string) {
   232  	if len(frames) == 0 {
   233  		return
   234  	}
   235  	args := append([]string{"setup_kcsan_filterlist"}, frames...)
   236  	timeout := time.Minute * tool.timeouts.Scale
   237  	output, err := osutil.RunCmd(timeout, "", tool.executor, args...)
   238  	if err != nil {
   239  		log.SyzFatalf("failed to set KCSAN filterlist: %v", err)
   240  	}
   241  	log.Logf(0, "%s", output)
   242  }
   243  
   244  func (tool *FuzzerTool) startExecutingCall(progID int64, pid, try int) {
   245  	tool.manager.AsyncCall("Manager.StartExecuting", &rpctype.ExecutingRequest{
   246  		Name:   tool.name,
   247  		ID:     progID,
   248  		ProcID: pid,
   249  		Try:    try,
   250  	})
   251  }
   252  
   253  func (tool *FuzzerTool) exchangeDataCall(results []rpctype.ExecutionResult, latency time.Duration) time.Duration {
   254  	needProgs := max(0, cap(tool.requests)/2-len(tool.requests))
   255  	a := &rpctype.ExchangeInfoRequest{
   256  		Name:       tool.name,
   257  		NeedProgs:  needProgs,
   258  		Results:    results,
   259  		StatsDelta: tool.grabStats(),
   260  		Latency:    latency,
   261  	}
   262  	r := &rpctype.ExchangeInfoReply{}
   263  	start := osutil.MonotonicNano()
   264  	if err := tool.manager.Call("Manager.ExchangeInfo", a, r); err != nil {
   265  		log.SyzFatalf("Manager.ExchangeInfo call failed: %v", err)
   266  	}
   267  	latency = osutil.MonotonicNano() - start
   268  	tool.updateMaxSignal(r.NewMaxSignal, r.DropMaxSignal)
   269  	if len(r.Requests) == 0 {
   270  		// This is possible during initial checking stage, backoff a bit.
   271  		time.Sleep(100 * time.Millisecond)
   272  	}
   273  	for _, req := range r.Requests {
   274  		tool.requests <- req
   275  	}
   276  	return latency
   277  }
   278  
   279  func (tool *FuzzerTool) exchangeDataWorker() {
   280  	var latency time.Duration
   281  	ticker := time.NewTicker(3 * time.Second * tool.timeouts.Scale).C
   282  	for {
   283  		var results []rpctype.ExecutionResult
   284  		select {
   285  		case res := <-tool.results:
   286  			results = append(results, tool.convertExecutionResult(res))
   287  		case <-ticker:
   288  			// This is not expected to happen a lot,
   289  			// but this is required to resolve potential deadlock
   290  			// during initial checking stage when we may get
   291  			// no test requests from the host in some requests.
   292  		}
   293  		// Grab other finished calls, just in case there are any.
   294  	loop:
   295  		for {
   296  			select {
   297  			case res := <-tool.results:
   298  				results = append(results, tool.convertExecutionResult(res))
   299  			default:
   300  				break loop
   301  			}
   302  		}
   303  		// Replenish exactly the finished requests.
   304  		latency = tool.exchangeDataCall(results, latency)
   305  	}
   306  }
   307  
   308  func (tool *FuzzerTool) convertExecutionResult(res executionResult) rpctype.ExecutionResult {
   309  	ret := rpctype.ExecutionResult{
   310  		ID:     res.ID,
   311  		ProcID: res.procID,
   312  		Try:    res.try,
   313  		Output: res.output,
   314  		Error:  res.err,
   315  	}
   316  	if res.info != nil {
   317  		if res.NewSignal {
   318  			tool.diffMaxSignal(res.info, res.SignalFilter, res.SignalFilterCall)
   319  		}
   320  		ret.Info = *res.info
   321  	}
   322  	return ret
   323  }
   324  
   325  func (tool *FuzzerTool) grabStats() map[string]uint64 {
   326  	return map[string]uint64{
   327  		"no exec requests": tool.noExecRequests.Swap(0),
   328  		"no exec duration": tool.noExecDuration.Swap(0),
   329  	}
   330  }
   331  
   332  func (tool *FuzzerTool) diffMaxSignal(info *ipc.ProgInfo, mask signal.Signal, maskCall int) {
   333  	tool.signalMu.RLock()
   334  	defer tool.signalMu.RUnlock()
   335  	diffMaxSignal(info, tool.maxSignal, mask, maskCall)
   336  }
   337  
   338  func diffMaxSignal(info *ipc.ProgInfo, max, mask signal.Signal, maskCall int) {
   339  	info.Extra.Signal = diffCallSignal(info.Extra.Signal, max, mask, -1, maskCall)
   340  	for i := 0; i < len(info.Calls); i++ {
   341  		info.Calls[i].Signal = diffCallSignal(info.Calls[i].Signal, max, mask, i, maskCall)
   342  	}
   343  }
   344  
   345  func diffCallSignal(raw []uint32, max, mask signal.Signal, call, maskCall int) []uint32 {
   346  	if mask != nil && call == maskCall {
   347  		return signal.FilterRaw(raw, max, mask)
   348  	}
   349  	return max.DiffFromRaw(raw)
   350  }
   351  
   352  func (tool *FuzzerTool) updateMaxSignal(add, drop []uint32) {
   353  	tool.signalMu.Lock()
   354  	defer tool.signalMu.Unlock()
   355  	tool.maxSignal.Subtract(signal.FromRaw(drop, 0))
   356  	tool.maxSignal.Merge(signal.FromRaw(add, 0))
   357  }
   358  
   359  func setupPprofHandler(port int) {
   360  	// Necessary for pprof handlers.
   361  	go func() {
   362  		err := http.ListenAndServe(fmt.Sprintf("0.0.0.0:%v", port), nil)
   363  		if err != nil {
   364  			log.SyzFatalf("failed to setup a server: %v", err)
   365  		}
   366  	}()
   367  }