github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/instance/execprog.go (about)

     1  // Copyright 2022 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 instance
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"time"
    11  
    12  	"github.com/google/syzkaller/pkg/csource"
    13  	"github.com/google/syzkaller/pkg/mgrconfig"
    14  	"github.com/google/syzkaller/pkg/osutil"
    15  	"github.com/google/syzkaller/pkg/report"
    16  	"github.com/google/syzkaller/prog"
    17  	"github.com/google/syzkaller/sys/targets"
    18  	"github.com/google/syzkaller/vm"
    19  )
    20  
    21  type ExecutorLogger func(int, string, ...interface{})
    22  
    23  type OptionalConfig struct {
    24  	Logf               ExecutorLogger
    25  	OldFlagsCompatMode bool
    26  	BeforeContextLen   int
    27  	StraceBin          string
    28  }
    29  
    30  type ExecProgInstance struct {
    31  	execprogBin string
    32  	executorBin string
    33  	reporter    *report.Reporter
    34  	mgrCfg      *mgrconfig.Config
    35  	VMInstance  *vm.Instance
    36  	OptionalConfig
    37  }
    38  
    39  type RunResult struct {
    40  	Output   []byte
    41  	Report   *report.Report
    42  	Duration time.Duration
    43  }
    44  
    45  const (
    46  	// It's reasonable to expect that tools/syz-execprog should not normally
    47  	// return a non-zero exit code.
    48  	SyzExitConditions = vm.ExitTimeout | vm.ExitNormal
    49  	binExitConditions = vm.ExitTimeout | vm.ExitNormal | vm.ExitError
    50  )
    51  
    52  func SetupExecProg(vmInst *vm.Instance, mgrCfg *mgrconfig.Config, reporter *report.Reporter,
    53  	opt *OptionalConfig) (*ExecProgInstance, error) {
    54  	var err error
    55  	execprogBin := mgrCfg.SysTarget.ExecprogBin
    56  	if execprogBin == "" {
    57  		execprogBin, err = vmInst.Copy(mgrCfg.ExecprogBin)
    58  		if err != nil {
    59  			return nil, &TestError{Title: fmt.Sprintf("failed to copy syz-execprog to VM: %v", err)}
    60  		}
    61  	}
    62  	executorBin := mgrCfg.SysTarget.ExecutorBin
    63  	if executorBin == "" {
    64  		executorBin, err = vmInst.Copy(mgrCfg.ExecutorBin)
    65  		if err != nil {
    66  			return nil, &TestError{Title: fmt.Sprintf("failed to copy syz-executor to VM: %v", err)}
    67  		}
    68  	}
    69  	ret := &ExecProgInstance{
    70  		execprogBin: execprogBin,
    71  		executorBin: executorBin,
    72  		reporter:    reporter,
    73  		mgrCfg:      mgrCfg,
    74  		VMInstance:  vmInst,
    75  	}
    76  	if opt != nil {
    77  		ret.OptionalConfig = *opt
    78  		if !mgrCfg.StraceBinOnTarget && ret.StraceBin != "" {
    79  			var err error
    80  			ret.StraceBin, err = vmInst.Copy(ret.StraceBin)
    81  			if err != nil {
    82  				return nil, &TestError{Title: fmt.Sprintf("failed to copy strace bin: %v", err)}
    83  			}
    84  		}
    85  	}
    86  	if ret.Logf == nil {
    87  		ret.Logf = func(int, string, ...interface{}) {}
    88  	}
    89  	return ret, nil
    90  }
    91  
    92  func CreateExecProgInstance(vmPool *vm.Pool, vmIndex int, mgrCfg *mgrconfig.Config,
    93  	reporter *report.Reporter, opt *OptionalConfig) (*ExecProgInstance, error) {
    94  	vmInst, err := vmPool.Create(context.Background(), vmIndex)
    95  	if err != nil {
    96  		return nil, fmt.Errorf("failed to create VM: %w", err)
    97  	}
    98  	ret, err := SetupExecProg(vmInst, mgrCfg, reporter, opt)
    99  	if err != nil {
   100  		vmInst.Close()
   101  		return nil, err
   102  	}
   103  	return ret, nil
   104  }
   105  
   106  func (inst *ExecProgInstance) runCommand(command string, duration time.Duration,
   107  	exitCondition vm.ExitCondition) (*RunResult, error) {
   108  	start := time.Now()
   109  
   110  	var prefixOutput []byte
   111  	if inst.StraceBin != "" {
   112  		filterCalls := ""
   113  		switch inst.mgrCfg.SysTarget.OS {
   114  		case targets.Linux:
   115  			// wait4 and nanosleep generate a lot of noise, especially when running syz-executor.
   116  			// We cut them on the VM side in order to decrease load on the network and to use
   117  			// the limited buffer size wisely.
   118  			filterCalls = ` -e \!wait4,clock_nanosleep,nanosleep`
   119  		}
   120  		command = inst.StraceBin + filterCalls + ` -s 100 -x -f ` + command
   121  		prefixOutput = []byte(fmt.Sprintf("%s\n\n<...>\n", command))
   122  	}
   123  	optionalBeforeContext := func(*vm.RunOptions) {}
   124  	if inst.BeforeContextLen != 0 {
   125  		optionalBeforeContext = vm.WithBeforeContext(inst.BeforeContextLen)
   126  	}
   127  	ctxTimeout, cancel := context.WithTimeout(context.Background(), duration)
   128  	defer cancel()
   129  	output, reps, err := inst.VMInstance.Run(ctxTimeout, inst.reporter, command,
   130  		vm.WithExitCondition(exitCondition),
   131  		optionalBeforeContext,
   132  	)
   133  	var rep *report.Report
   134  	if len(reps) > 0 {
   135  		rep = reps[0]
   136  	}
   137  	if err != nil {
   138  		return nil, fmt.Errorf("failed to run command in VM: %w", err)
   139  	}
   140  	if rep == nil {
   141  		inst.Logf(2, "program did not crash")
   142  	} else {
   143  		if err := inst.reporter.Symbolize(rep); err != nil {
   144  			inst.Logf(0, "failed to symbolize report: %v", err)
   145  		}
   146  		inst.Logf(2, "program crashed: %v", rep.Title)
   147  	}
   148  	return &RunResult{
   149  		Output:   append(prefixOutput, output...),
   150  		Report:   rep,
   151  		Duration: time.Since(start),
   152  	}, nil
   153  }
   154  
   155  func (inst *ExecProgInstance) runBinary(bin string, duration time.Duration) (*RunResult, error) {
   156  	bin, err := inst.VMInstance.Copy(bin)
   157  	if err != nil {
   158  		return nil, &TestError{Title: fmt.Sprintf("failed to copy binary to VM: %v", err)}
   159  	}
   160  	return inst.runCommand(bin, duration, binExitConditions)
   161  }
   162  
   163  type ExecParams struct {
   164  	// Only one of these will be used, depending on the function.
   165  	CProg   *prog.Prog
   166  	SyzProg []byte
   167  
   168  	Opts     csource.Options
   169  	Duration time.Duration
   170  	// If ExitConditions is empty, RunSyzProg() will assume instance.SyzExitConditions.
   171  	// RunCProg() always runs with binExitConditions.
   172  	ExitConditions vm.ExitCondition
   173  }
   174  
   175  func (inst *ExecProgInstance) RunCProg(params ExecParams) (*RunResult, error) {
   176  	src, err := csource.Write(params.CProg, params.Opts)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	inst.Logf(2, "testing compiled C program (duration=%v, %+v): %s",
   181  		params.Duration, params.Opts, params.CProg)
   182  	return inst.RunCProgRaw(src, params.CProg.Target, params.Duration)
   183  }
   184  
   185  func (inst *ExecProgInstance) RunCProgRaw(src []byte, target *prog.Target,
   186  	duration time.Duration) (*RunResult, error) {
   187  	bin, err := csource.BuildNoWarn(target, src)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	defer os.Remove(bin)
   192  	return inst.runBinary(bin, duration)
   193  }
   194  
   195  func (inst *ExecProgInstance) RunSyzProgFile(progFile string, duration time.Duration,
   196  	opts csource.Options, exitCondition vm.ExitCondition) (*RunResult, error) {
   197  	vmProgFile, err := inst.VMInstance.Copy(progFile)
   198  	if err != nil {
   199  		return nil, &TestError{Title: fmt.Sprintf("failed to copy prog to VM: %v", err)}
   200  	}
   201  	target := inst.mgrCfg.SysTarget
   202  	command := ExecprogCmd(inst.execprogBin, inst.executorBin, target.OS, target.Arch, inst.mgrCfg.Type, opts,
   203  		!inst.OldFlagsCompatMode, inst.mgrCfg.Timeouts.Slowdown, vmProgFile)
   204  	return inst.runCommand(command, duration, exitCondition)
   205  }
   206  
   207  func (inst *ExecProgInstance) RunSyzProg(params ExecParams) (*RunResult, error) {
   208  	progFile, err := osutil.WriteTempFile(params.SyzProg)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  	defer os.Remove(progFile)
   213  
   214  	if params.ExitConditions == 0 {
   215  		params.ExitConditions = SyzExitConditions
   216  	}
   217  	return inst.RunSyzProgFile(progFile, params.Duration, params.Opts, params.ExitConditions)
   218  }