github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-verifier/verifier.go (about)

     1  // Copyright 2021 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  // TODO: switch syz-verifier to use syz-fuzzer.
     5  
     6  //go:build ignore
     7  
     8  package main
     9  
    10  import (
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"math/rand"
    15  	"os"
    16  	"os/signal"
    17  	"path/filepath"
    18  	"strings"
    19  	"sync"
    20  	"time"
    21  
    22  	"github.com/google/syzkaller/pkg/instance"
    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/prog"
    27  	"github.com/google/syzkaller/vm"
    28  )
    29  
    30  // Verifier TODO.
    31  type Verifier struct {
    32  	pools  map[int]*poolInfo
    33  	vmStop chan bool
    34  	// Location of a working directory for all VMs for the syz-verifier process.
    35  	// Outputs here include:
    36  	// - <workdir>/crashes/<OS-Arch>/*: crash output files grouped by OS/Arch
    37  	// - <workdir>/corpus.db: corpus with interesting programs
    38  	// - <workdir>/<OS-Arch>/instance-x: per VM instance temporary files
    39  	// grouped by OS/Arch
    40  	workdir           string
    41  	crashdir          string
    42  	resultsdir        string
    43  	target            *prog.Target
    44  	runnerBin         string
    45  	executorBin       string
    46  	progGeneratorInit sync.WaitGroup
    47  	choiceTable       *prog.ChoiceTable
    48  	progIdx           int
    49  	addr              string
    50  	srv               *RPCServer
    51  	calls             map[*prog.Syscall]bool
    52  	reasons           map[*prog.Syscall]string
    53  	reportReasons     bool
    54  	stats             *Stats
    55  	statsWrite        io.Writer
    56  	newEnv            bool
    57  	reruns            int
    58  
    59  	// We use single queue for every kernel environment.
    60  	tasksMutex     sync.Mutex
    61  	onTaskAdded    *sync.Cond
    62  	kernelEnvTasks [][]*ExecTaskQueue
    63  	taskFactory    *ExecTaskFactory
    64  }
    65  
    66  func (vrf *Verifier) Init() {
    67  	vrf.progGeneratorInit.Add(1)
    68  
    69  	vrf.onTaskAdded = sync.NewCond(&vrf.tasksMutex)
    70  
    71  	vrf.kernelEnvTasks = make([][]*ExecTaskQueue, len(vrf.pools))
    72  	for i := range vrf.kernelEnvTasks {
    73  		vrf.kernelEnvTasks[i] = make([]*ExecTaskQueue, EnvironmentsCount)
    74  		for j := range vrf.kernelEnvTasks[i] {
    75  			vrf.kernelEnvTasks[i][j] = MakeExecTaskQueue()
    76  		}
    77  	}
    78  
    79  	srv, err := startRPCServer(vrf)
    80  	if err != nil {
    81  		log.Fatalf("failed to initialise RPC server: %v", err)
    82  	}
    83  	vrf.srv = srv
    84  
    85  	vrf.taskFactory = MakeExecTaskFactory()
    86  }
    87  
    88  func (vrf *Verifier) StartProgramsAnalysis() {
    89  	go func() {
    90  		vrf.progGeneratorInit.Wait()
    91  
    92  		type AnalysisResult struct {
    93  			Diff []*ExecResult
    94  			Prog *prog.Prog
    95  		}
    96  
    97  		results := make(chan *AnalysisResult)
    98  		go func() {
    99  			for result := range results {
   100  				if result.Diff != nil {
   101  					vrf.SaveDiffResults(result.Diff, result.Prog)
   102  				}
   103  			}
   104  		}()
   105  
   106  		for i := 0; i < 100; i++ {
   107  			go func() {
   108  				for {
   109  					prog := vrf.generate()
   110  					results <- &AnalysisResult{
   111  						vrf.TestProgram(prog),
   112  						prog,
   113  					}
   114  				}
   115  			}()
   116  		}
   117  	}()
   118  }
   119  
   120  func (vrf *Verifier) GetRunnerTask(kernel int, existing EnvDescr) *rpctype.ExecTask {
   121  	vrf.tasksMutex.Lock()
   122  	defer vrf.tasksMutex.Unlock()
   123  
   124  	for {
   125  		for env := existing; env >= AnyEnvironment; env-- {
   126  			if task, ok := vrf.kernelEnvTasks[kernel][env].PopTask(); ok {
   127  				return task.ToRPC()
   128  			}
   129  		}
   130  
   131  		vrf.onTaskAdded.Wait()
   132  	}
   133  }
   134  
   135  func (vrf *Verifier) PutExecResult(result *ExecResult) {
   136  	c := vrf.taskFactory.GetExecResultChan(result.ExecTaskID)
   137  	c <- result
   138  }
   139  
   140  // TestProgram return the results slice if some exec diff was found.
   141  func (vrf *Verifier) TestProgram(prog *prog.Prog) (result []*ExecResult) {
   142  	steps := []EnvDescr{
   143  		NewEnvironment,
   144  		NewEnvironment,
   145  	}
   146  
   147  	defer vrf.stats.TotalProgs.Inc()
   148  
   149  	for i, env := range steps {
   150  		stepRes, err := vrf.Run(prog, env)
   151  		if err != nil {
   152  			vrf.stats.ExecErrorProgs.Inc()
   153  			return
   154  		}
   155  		vrf.AddCallsExecutionStat(stepRes, prog)
   156  		if stepRes[0].IsEqual(stepRes[1]) {
   157  			if i != 0 {
   158  				vrf.stats.FlakyProgs.Inc()
   159  			}
   160  			return
   161  		}
   162  		if i == len(steps)-1 {
   163  			vrf.stats.MismatchingProgs.Inc()
   164  			return stepRes
   165  		}
   166  	}
   167  	return
   168  }
   169  
   170  // Run sends the program for verification to execution queues and return
   171  // result once it's ready.
   172  // In case of time-out, return (nil, error).
   173  func (vrf *Verifier) Run(prog *prog.Prog, env EnvDescr) (result []*ExecResult, err error) {
   174  	totalKernels := len(vrf.kernelEnvTasks)
   175  	result = make([]*ExecResult, totalKernels)
   176  
   177  	wg := sync.WaitGroup{}
   178  	wg.Add(totalKernels)
   179  	for i := 0; i < totalKernels; i++ {
   180  		q := vrf.kernelEnvTasks[i][env]
   181  
   182  		go func() {
   183  			defer wg.Done()
   184  			task := vrf.taskFactory.MakeExecTask(prog)
   185  			defer vrf.taskFactory.DeleteExecTask(task)
   186  
   187  			vrf.tasksMutex.Lock()
   188  			q.PushTask(task)
   189  			vrf.onTaskAdded.Signal()
   190  			vrf.tasksMutex.Unlock()
   191  
   192  			result[i] = <-task.ExecResultChan
   193  		}()
   194  	}
   195  	wg.Wait()
   196  
   197  	for _, item := range result {
   198  		if item == nil {
   199  			err = errors.New("something went wrong and we exit w/o results")
   200  			return nil, err
   201  		}
   202  		if item.Error != nil {
   203  			err = item.Error
   204  			return nil, err
   205  		}
   206  	}
   207  
   208  	return result, nil
   209  }
   210  
   211  // SetPrintStatAtSIGINT asks Stats object to report verification
   212  // statistics when an os.Interrupt occurs and Exit().
   213  func (vrf *Verifier) SetPrintStatAtSIGINT() error {
   214  	if vrf.stats == nil {
   215  		return errors.New("verifier.stats is nil")
   216  	}
   217  
   218  	osSignalChannel := make(chan os.Signal, 1)
   219  	signal.Notify(osSignalChannel, os.Interrupt)
   220  
   221  	go func() {
   222  		<-osSignalChannel
   223  		defer os.Exit(0)
   224  
   225  		totalExecutionTime := time.Since(vrf.stats.StartTime.Get()).Minutes()
   226  		if !vrf.stats.MismatchesFound() {
   227  			fmt.Fprint(vrf.statsWrite, "No mismatches occurred until syz-verifier was stopped.")
   228  		} else {
   229  			fmt.Fprintf(vrf.statsWrite, "%s", vrf.stats.GetTextDescription(totalExecutionTime))
   230  		}
   231  	}()
   232  
   233  	return nil
   234  }
   235  
   236  func (vrf *Verifier) startInstances() {
   237  	for poolID, pi := range vrf.pools {
   238  		totalInstances := pi.pool.Count()
   239  		for vmID := 0; vmID < totalInstances; vmID++ {
   240  			go func(pi *poolInfo, poolID, vmID int) {
   241  				for {
   242  					vrf.createAndManageInstance(pi, poolID, vmID)
   243  				}
   244  			}(pi, poolID, vmID)
   245  		}
   246  	}
   247  }
   248  
   249  func (vrf *Verifier) createAndManageInstance(pi *poolInfo, poolID, vmID int) {
   250  	inst, err := pi.pool.Create(vmID)
   251  	if err != nil {
   252  		log.Fatalf("failed to create instance: %v", err)
   253  	}
   254  	defer inst.Close()
   255  	defer vrf.srv.cleanup(poolID, vmID)
   256  
   257  	fwdAddr, err := inst.Forward(vrf.srv.port)
   258  	if err != nil {
   259  		log.Fatalf("failed to set up port forwarding: %v", err)
   260  	}
   261  
   262  	runnerBin, err := inst.Copy(vrf.runnerBin)
   263  	if err != nil {
   264  		log.Fatalf(" failed to copy runner binary: %v", err)
   265  	}
   266  	_, err = inst.Copy(vrf.executorBin)
   267  	if err != nil {
   268  		log.Fatalf("failed to copy executor binary: %v", err)
   269  	}
   270  
   271  	cmd := instance.RunnerCmd(runnerBin, fwdAddr, vrf.target.OS, vrf.target.Arch, poolID, 0, false, vrf.newEnv)
   272  	_, _, err = inst.Run(pi.cfg.Timeouts.VMRunningTime, pi.Reporter, cmd, vm.ExitTimeout, vm.StopChan(vrf.vmStop))
   273  	if err != nil {
   274  		log.Fatalf("failed to start runner: %v", err)
   275  	}
   276  	log.Logf(0, "reboot the VM in pool %d", poolID)
   277  }
   278  
   279  // finalizeCallSet removes the system calls that are not supported from the set
   280  // of enabled system calls and reports the reason to the io.Writer (either
   281  // because the call is not supported by one of the kernels or because the call
   282  // is missing some transitive dependencies). The resulting set of system calls
   283  // will be used to build the prog.ChoiceTable.
   284  func (vrf *Verifier) finalizeCallSet(w io.Writer) {
   285  	for c := range vrf.reasons {
   286  		delete(vrf.calls, c)
   287  	}
   288  
   289  	// Find and report to the user all the system calls that need to be
   290  	// disabled due to missing dependencies.
   291  	_, disabled := vrf.target.TransitivelyEnabledCalls(vrf.calls)
   292  	for c, reason := range disabled {
   293  		vrf.reasons[c] = reason
   294  		delete(vrf.calls, c)
   295  	}
   296  
   297  	if len(vrf.calls) == 0 {
   298  		log.Logf(0, "All enabled system calls are missing dependencies or not"+
   299  			" supported by some kernels, exiting syz-verifier.")
   300  	}
   301  
   302  	if !vrf.reportReasons {
   303  		return
   304  	}
   305  
   306  	fmt.Fprintln(w, "The following calls have been disabled:")
   307  	for c, reason := range vrf.reasons {
   308  		fmt.Fprintf(w, "\t%v: %v\n", c.Name, reason)
   309  	}
   310  }
   311  
   312  // AddCallsExecutionStat ignore all the calls after the first mismatch.
   313  func (vrf *Verifier) AddCallsExecutionStat(results []*ExecResult, program *prog.Prog) {
   314  	rr := CompareResults(results, program)
   315  	for _, cr := range rr.Reports {
   316  		vrf.stats.Calls.IncCallOccurrenceCount(cr.Call)
   317  	}
   318  
   319  	for _, cr := range rr.Reports {
   320  		if !cr.Mismatch {
   321  			continue
   322  		}
   323  		vrf.stats.IncCallMismatches(cr.Call)
   324  		for _, state := range cr.States {
   325  			if state0 := cr.States[0]; state0 != state {
   326  				vrf.stats.Calls.AddState(cr.Call, state)
   327  				vrf.stats.Calls.AddState(cr.Call, state0)
   328  			}
   329  		}
   330  		break
   331  	}
   332  }
   333  
   334  // SaveDiffResults extract diff and save result on the persistent storage.
   335  func (vrf *Verifier) SaveDiffResults(results []*ExecResult, program *prog.Prog) bool {
   336  	rr := CompareResults(results, program)
   337  
   338  	oldest := 0
   339  	var oldestTime time.Time
   340  	for i := 0; i < maxResultReports; i++ {
   341  		info, err := os.Stat(filepath.Join(vrf.resultsdir, fmt.Sprintf("result-%d", i)))
   342  		if err != nil {
   343  			// There are only i-1 report files so the i-th one
   344  			// can be created.
   345  			oldest = i
   346  			break
   347  		}
   348  
   349  		// Otherwise, search for the oldest report file to
   350  		// overwrite as newer result reports are more useful.
   351  		if oldestTime.IsZero() || info.ModTime().Before(oldestTime) {
   352  			oldest = i
   353  			oldestTime = info.ModTime()
   354  		}
   355  	}
   356  
   357  	err := osutil.WriteFile(filepath.Join(vrf.resultsdir,
   358  		fmt.Sprintf("result-%d", oldest)), createReport(rr, len(vrf.pools)))
   359  	if err != nil {
   360  		log.Logf(0, "failed to write result-%d file, err %v", oldest, err)
   361  	}
   362  
   363  	log.Logf(0, "result-%d written successfully", oldest)
   364  	return true
   365  }
   366  
   367  // generate returns a newly generated program or error.
   368  func (vrf *Verifier) generate() *prog.Prog {
   369  	vrf.progGeneratorInit.Wait()
   370  
   371  	rnd := rand.New(rand.NewSource(time.Now().UnixNano() + 1e12))
   372  	return vrf.target.Generate(rnd, prog.RecommendedCalls, vrf.choiceTable)
   373  }
   374  
   375  func createReport(rr *ResultReport, pools int) []byte {
   376  	calls := strings.Split(rr.Prog, "\n")
   377  	calls = calls[:len(calls)-1]
   378  
   379  	data := "ERRNO mismatches found for program:\n\n"
   380  	for idx, cr := range rr.Reports {
   381  		tick := "[=]"
   382  		if cr.Mismatch {
   383  			tick = "[!]"
   384  		}
   385  		data += fmt.Sprintf("%s %s\n", tick, calls[idx])
   386  
   387  		// Ensure results are ordered by pool index.
   388  		for i := 0; i < pools; i++ {
   389  			state := cr.States[i]
   390  			data += fmt.Sprintf("\t↳ Pool: %d, %s\n", i, state)
   391  		}
   392  
   393  		data += "\n"
   394  	}
   395  
   396  	return []byte(data)
   397  }