github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/instance/instance.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  // Package instance provides helper functions for creation of temporal instances
     5  // used for testing of images, patches and bisection.
     6  package instance
     7  
     8  import (
     9  	"context"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"os"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/google/syzkaller/pkg/build"
    20  	"github.com/google/syzkaller/pkg/config"
    21  	"github.com/google/syzkaller/pkg/csource"
    22  	"github.com/google/syzkaller/pkg/log"
    23  	"github.com/google/syzkaller/pkg/mgrconfig"
    24  	"github.com/google/syzkaller/pkg/osutil"
    25  	"github.com/google/syzkaller/pkg/report"
    26  	"github.com/google/syzkaller/pkg/report/crash"
    27  	"github.com/google/syzkaller/pkg/tool"
    28  	"github.com/google/syzkaller/pkg/vcs"
    29  	"github.com/google/syzkaller/sys/targets"
    30  	"github.com/google/syzkaller/vm"
    31  )
    32  
    33  type Env interface {
    34  	BuildSyzkaller(string, string) (string, error)
    35  	CleanKernel(*BuildKernelConfig) error
    36  	BuildKernel(*BuildKernelConfig) (string, build.ImageDetails, error)
    37  	Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]EnvTestResult, error)
    38  }
    39  
    40  type env struct {
    41  	cfg           *mgrconfig.Config
    42  	optionalFlags bool
    43  	buildSem      *osutil.Semaphore
    44  	testSem       *osutil.Semaphore
    45  }
    46  
    47  type BuildKernelConfig struct {
    48  	MakeBin      string
    49  	CompilerBin  string
    50  	LinkerBin    string
    51  	CcacheBin    string
    52  	UserspaceDir string
    53  	CmdlineFile  string
    54  	SysctlFile   string
    55  	KernelConfig []byte
    56  	BuildCPUs    int
    57  }
    58  
    59  func NewEnv(cfg *mgrconfig.Config, buildSem, testSem *osutil.Semaphore) (Env, error) {
    60  	if !vm.AllowsOvercommit(cfg.Type) {
    61  		return nil, fmt.Errorf("test instances are not supported for %v VMs", cfg.Type)
    62  	}
    63  	if cfg.Workdir == "" {
    64  		return nil, fmt.Errorf("workdir path is empty")
    65  	}
    66  	if cfg.KernelSrc == "" {
    67  		return nil, fmt.Errorf("kernel src path is empty")
    68  	}
    69  	if cfg.Syzkaller == "" {
    70  		return nil, fmt.Errorf("syzkaller path is empty")
    71  	}
    72  	if err := osutil.MkdirAll(cfg.Workdir); err != nil {
    73  		return nil, fmt.Errorf("failed to create tmp dir: %w", err)
    74  	}
    75  	env := &env{
    76  		cfg:           cfg,
    77  		optionalFlags: true,
    78  		buildSem:      buildSem,
    79  		testSem:       testSem,
    80  	}
    81  	return env, nil
    82  }
    83  
    84  func (env *env) BuildSyzkaller(repoURL, commit string) (string, error) {
    85  	if env.buildSem != nil {
    86  		env.buildSem.Wait()
    87  		defer env.buildSem.Signal()
    88  	}
    89  	cfg := env.cfg
    90  	srcIndex := strings.LastIndex(cfg.Syzkaller, "/src/")
    91  	if srcIndex == -1 {
    92  		return "", fmt.Errorf("syzkaller path %q is not in GOPATH", cfg.Syzkaller)
    93  	}
    94  	repo := vcs.NewSyzkallerRepo(cfg.Syzkaller)
    95  	if _, err := repo.CheckoutCommit(repoURL, commit); err != nil {
    96  		return "", fmt.Errorf("failed to checkout syzkaller repo: %w", err)
    97  	}
    98  	// The following commit ("syz-fuzzer: support optional flags") adds support for optional flags
    99  	// in syz-execprog. This is required to invoke older binaries with newer flags
   100  	// without failing due to unknown flags.
   101  	optionalFlags, err := repo.Contains("64435345f0891706a7e0c7885f5f7487581e6005")
   102  	if err != nil {
   103  		return "", fmt.Errorf("optional flags check failed: %w", err)
   104  	}
   105  	env.optionalFlags = optionalFlags
   106  	cmd := osutil.Command(MakeBin, "target")
   107  	cmd.Dir = cfg.Syzkaller
   108  	goEnvOptions := []string{
   109  		"GOPATH=" + cfg.Syzkaller[:srcIndex],
   110  		"GO111MODULE=auto",
   111  	}
   112  	cmd.Env = append(append([]string{}, os.Environ()...), goEnvOptions...)
   113  	cmd.Env = append(cmd.Env,
   114  		"TARGETOS="+cfg.TargetOS,
   115  		"TARGETVMARCH="+cfg.TargetVMArch,
   116  		"TARGETARCH="+cfg.TargetArch,
   117  		// Since we can be building very old revisions for bisection here,
   118  		// make the build as permissive as possible.
   119  		// Newer compilers tend to produce more warnings also kernel headers may be broken, e.g.:
   120  		// ebtables.h:197:19: error: invalid conversion from ‘void*’ to ‘ebt_entry_target*’
   121  		"CFLAGS=-fpermissive -w",
   122  	)
   123  
   124  	// We collect the potentially useful debug info here unconditionally, because we will
   125  	// only figure out later whether we actually need it (e.g. if the patch testing fails).
   126  	goEnvCmd := osutil.Command("go", "env")
   127  	goEnvCmd.Dir = cfg.Syzkaller
   128  	goEnvCmd.Env = append(append([]string{}, os.Environ()...), goEnvOptions...)
   129  	goEnvOut, goEnvErr := osutil.Run(time.Hour, goEnvCmd)
   130  	gitStatusOut, gitStatusErr := osutil.RunCmd(time.Hour, cfg.Syzkaller, "git", "status")
   131  	// Compile syzkaller.
   132  	buildOutput, buildErr := osutil.Run(time.Hour, cmd)
   133  	buildLog := fmt.Sprintf("go env (err=%v)\n%s\ngit status (err=%v)\n%s\n\n%s",
   134  		goEnvErr, goEnvOut, gitStatusErr, gitStatusOut, buildOutput)
   135  	if buildErr != nil {
   136  		return buildLog, fmt.Errorf("syzkaller build failed: %w\n%s", buildErr, buildLog)
   137  	}
   138  	return buildLog, nil
   139  }
   140  
   141  func (env *env) buildParamsFromCfg(buildCfg *BuildKernelConfig) build.Params {
   142  	return build.Params{
   143  		TargetOS:     env.cfg.TargetOS,
   144  		TargetArch:   env.cfg.TargetVMArch,
   145  		VMType:       env.cfg.Type,
   146  		KernelDir:    env.cfg.KernelSrc,
   147  		OutputDir:    filepath.Join(env.cfg.Workdir, "image"),
   148  		Make:         buildCfg.MakeBin,
   149  		Compiler:     buildCfg.CompilerBin,
   150  		Linker:       buildCfg.LinkerBin,
   151  		Ccache:       buildCfg.CcacheBin,
   152  		UserspaceDir: buildCfg.UserspaceDir,
   153  		CmdlineFile:  buildCfg.CmdlineFile,
   154  		SysctlFile:   buildCfg.SysctlFile,
   155  		Config:       buildCfg.KernelConfig,
   156  		BuildCPUs:    buildCfg.BuildCPUs,
   157  	}
   158  }
   159  
   160  func (env *env) BuildKernel(buildCfg *BuildKernelConfig) (
   161  	string, build.ImageDetails, error) {
   162  	if env.buildSem != nil {
   163  		env.buildSem.Wait()
   164  		defer env.buildSem.Signal()
   165  	}
   166  	params := env.buildParamsFromCfg(buildCfg)
   167  	details, err := build.Image(params)
   168  	if err != nil {
   169  		return "", details, err
   170  	}
   171  	if err := SetConfigImage(env.cfg, params.OutputDir, true); err != nil {
   172  		return "", details, err
   173  	}
   174  	kernelConfigFile := filepath.Join(params.OutputDir, "kernel.config")
   175  	if !osutil.IsExist(kernelConfigFile) {
   176  		kernelConfigFile = ""
   177  	}
   178  	return kernelConfigFile, details, nil
   179  }
   180  
   181  func (env *env) CleanKernel(buildCfg *BuildKernelConfig) error {
   182  	if env.buildSem != nil {
   183  		env.buildSem.Wait()
   184  		defer env.buildSem.Signal()
   185  	}
   186  	params := env.buildParamsFromCfg(buildCfg)
   187  	return build.Clean(params)
   188  }
   189  
   190  func SetConfigImage(cfg *mgrconfig.Config, imageDir string, reliable bool) error {
   191  	cfg.KernelObj = filepath.Join(imageDir, "obj")
   192  	cfg.Image = filepath.Join(imageDir, "image")
   193  	if keyFile := filepath.Join(imageDir, "key"); osutil.IsExist(keyFile) {
   194  		cfg.SSHKey = keyFile
   195  	}
   196  	vmConfig := make(map[string]interface{})
   197  	if err := json.Unmarshal(cfg.VM, &vmConfig); err != nil {
   198  		return fmt.Errorf("failed to parse VM config: %w", err)
   199  	}
   200  	if cfg.Type == "qemu" || cfg.Type == "vmm" {
   201  		if kernel := filepath.Join(imageDir, "kernel"); osutil.IsExist(kernel) {
   202  			vmConfig["kernel"] = kernel
   203  		}
   204  		if initrd := filepath.Join(imageDir, "initrd"); osutil.IsExist(initrd) {
   205  			vmConfig["initrd"] = initrd
   206  		}
   207  	}
   208  	if cfg.Type == "gce" {
   209  		// Don't use preemptible VMs for image testing, patch testing and bisection.
   210  		vmConfig["preemptible"] = !reliable
   211  	}
   212  	vmCfg, err := json.Marshal(vmConfig)
   213  	if err != nil {
   214  		return fmt.Errorf("failed to serialize VM config: %w", err)
   215  	}
   216  	cfg.VM = vmCfg
   217  	return nil
   218  }
   219  
   220  func OverrideVMCount(cfg *mgrconfig.Config, n int) error {
   221  	vmConfig := make(map[string]interface{})
   222  	if err := json.Unmarshal(cfg.VM, &vmConfig); err != nil {
   223  		return fmt.Errorf("failed to parse VM config: %w", err)
   224  	}
   225  	if vmConfig["count"] == nil || !vm.AllowsOvercommit(cfg.Type) {
   226  		return nil
   227  	}
   228  	vmConfig["count"] = n
   229  	vmCfg, err := json.Marshal(vmConfig)
   230  	if err != nil {
   231  		return fmt.Errorf("failed to serialize VM config: %w", err)
   232  	}
   233  	cfg.VM = vmCfg
   234  	cfg.FuzzingVMs = min(cfg.FuzzingVMs, n)
   235  	return nil
   236  }
   237  
   238  type TestError struct {
   239  	Boot   bool // says if the error happened during booting or during instance testing
   240  	Infra  bool // whether the problem is related to some infrastructure problems
   241  	Title  string
   242  	Output []byte
   243  	Report *report.Report
   244  }
   245  
   246  func (err *TestError) Error() string {
   247  	return err.Title
   248  }
   249  
   250  type CrashError struct {
   251  	Report *report.Report
   252  }
   253  
   254  func (err *CrashError) Error() string {
   255  	return err.Report.Title
   256  }
   257  
   258  // Test boots numVMs VMs, tests basic kernel operation, and optionally tests the provided reproducer.
   259  // TestError is returned if there is a problem with kernel/image (crash, reboot loop, etc).
   260  // CrashError is returned if the reproducer crashes kernel.
   261  func (env *env) Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]EnvTestResult, error) {
   262  	if env.testSem != nil {
   263  		env.testSem.Wait()
   264  		defer env.testSem.Signal()
   265  	}
   266  	if err := mgrconfig.Complete(env.cfg); err != nil {
   267  		return nil, err
   268  	}
   269  	reporter, err := report.NewReporter(env.cfg)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  	vmPool, err := vm.Create(env.cfg, false)
   274  	if err != nil {
   275  		return nil, fmt.Errorf("failed to create VM pool: %w", err)
   276  	}
   277  	defer vmPool.Close()
   278  	numVMs = min(numVMs, vmPool.Count())
   279  	res := make(chan EnvTestResult, numVMs)
   280  	for i := 0; i < numVMs; i++ {
   281  		inst := &inst{
   282  			cfg:           env.cfg,
   283  			optionalFlags: env.optionalFlags,
   284  			reporter:      reporter,
   285  			vmPool:        vmPool,
   286  			vmIndex:       i,
   287  			reproSyz:      reproSyz,
   288  			reproOpts:     reproOpts,
   289  			reproC:        reproC,
   290  		}
   291  		go func() { res <- inst.test() }()
   292  	}
   293  	var ret []EnvTestResult
   294  	for i := 0; i < numVMs; i++ {
   295  		ret = append(ret, <-res)
   296  	}
   297  	return ret, nil
   298  }
   299  
   300  type inst struct {
   301  	cfg           *mgrconfig.Config
   302  	optionalFlags bool
   303  	reporter      *report.Reporter
   304  	vmPool        *vm.Pool
   305  	vm            *vm.Instance
   306  	vmIndex       int
   307  	reproSyz      []byte
   308  	reproOpts     []byte
   309  	reproC        []byte
   310  }
   311  
   312  type EnvTestResult struct {
   313  	Error     error
   314  	RawOutput []byte
   315  }
   316  
   317  func (inst *inst) test() EnvTestResult {
   318  	vmInst, err := inst.vmPool.Create(context.Background(), inst.vmIndex)
   319  	if err != nil {
   320  		testErr := &TestError{
   321  			Boot:  true,
   322  			Title: err.Error(),
   323  		}
   324  		ret := EnvTestResult{
   325  			Error: testErr,
   326  		}
   327  		var bootErr vm.BootErrorer
   328  		if errors.As(err, &bootErr) {
   329  			testErr.Title, testErr.Output = bootErr.BootError()
   330  			ret.RawOutput = testErr.Output
   331  			rep := inst.reporter.Parse(testErr.Output)
   332  			if rep != nil && rep.Type == crash.UnexpectedReboot {
   333  				// Avoid detecting any boot crash as "unexpected kernel reboot".
   334  				rep = inst.reporter.ParseFrom(testErr.Output, rep.SkipPos)
   335  			}
   336  			if rep == nil {
   337  				rep = &report.Report{
   338  					Title:  testErr.Title,
   339  					Output: testErr.Output,
   340  				}
   341  			}
   342  			if err := inst.reporter.Symbolize(rep); err != nil {
   343  				// TODO(dvyukov): send such errors to dashboard.
   344  				log.Logf(0, "failed to symbolize report: %v", err)
   345  			}
   346  			testErr.Report = rep
   347  			testErr.Title = rep.Title
   348  		} else {
   349  			testErr.Infra = true
   350  			var infraErr vm.InfraErrorer
   351  			if errors.As(err, &infraErr) {
   352  				// In case there's more info available.
   353  				testErr.Title, testErr.Output = infraErr.InfraError()
   354  			}
   355  		}
   356  		return ret
   357  	}
   358  	defer vmInst.Close()
   359  	inst.vm = vmInst
   360  	ret := EnvTestResult{}
   361  	if ret.Error = inst.testInstance(); ret.Error != nil {
   362  		return ret
   363  	}
   364  	if len(inst.reproSyz) != 0 || len(inst.reproC) != 0 {
   365  		ret.RawOutput, ret.Error = inst.testRepro()
   366  	}
   367  	return ret
   368  }
   369  
   370  // testInstance tests that the VM does not crash on a simple program.
   371  // TestError is returned if there is a problem with the kernel (e.g. crash).
   372  func (inst *inst) testInstance() error {
   373  	execProg, err := SetupExecProg(inst.vm, inst.cfg, inst.reporter, &OptionalConfig{
   374  		OldFlagsCompatMode: !inst.optionalFlags,
   375  	})
   376  	if err != nil {
   377  		return err
   378  	}
   379  	// Note: we create the test program on a newer syzkaller revision and pass it to the old execprog.
   380  	// We rely on the non-strict program parsing to parse it successfully.
   381  	testProg := inst.cfg.Target.DataMmapProg().Serialize()
   382  	// Use the same options as the target reproducer.
   383  	// E.g. if it does not use wifi, we won't test it, which reduces changes of unrelated kernel bugs.
   384  	// Note: we keep fault injection if it's enabled in the reproducer to test that fault injection works
   385  	// (does not produce some kernel oops when activated).
   386  	opts, err := inst.csourceOptions()
   387  	if err != nil {
   388  		return err
   389  	}
   390  	opts.Repeat = false
   391  	out, err := execProg.RunSyzProg(ExecParams{
   392  		SyzProg:        testProg,
   393  		Duration:       inst.cfg.Timeouts.NoOutputRunningTime,
   394  		Opts:           opts,
   395  		ExitConditions: vm.ExitNormal,
   396  	})
   397  	if err != nil {
   398  		return &TestError{Title: err.Error()}
   399  	}
   400  	if out.Report != nil {
   401  		return &TestError{Title: out.Report.Title, Report: out.Report}
   402  	}
   403  	return nil
   404  }
   405  
   406  func (inst *inst) testRepro() ([]byte, error) {
   407  	execProg, err := SetupExecProg(inst.vm, inst.cfg, inst.reporter, &OptionalConfig{
   408  		OldFlagsCompatMode: !inst.optionalFlags,
   409  	})
   410  	if err != nil {
   411  		return nil, err
   412  	}
   413  	transformError := func(res *RunResult, err error) ([]byte, error) {
   414  		if err != nil {
   415  			return nil, err
   416  		}
   417  		if res != nil && res.Report != nil {
   418  			return res.Output, &CrashError{Report: res.Report}
   419  		}
   420  		return res.Output, nil
   421  	}
   422  	out := []byte{}
   423  	if len(inst.reproSyz) > 0 {
   424  		opts, err := inst.csourceOptions()
   425  		if err != nil {
   426  			return nil, err
   427  		}
   428  		out, err = transformError(execProg.RunSyzProg(ExecParams{
   429  			SyzProg:  inst.reproSyz,
   430  			Duration: inst.cfg.Timeouts.NoOutputRunningTime,
   431  			Opts:     opts,
   432  		}))
   433  		if err != nil {
   434  			return out, err
   435  		}
   436  	}
   437  	if len(inst.reproC) > 0 {
   438  		// We should test for more than full "no output" timeout, but the problem is that C reproducers
   439  		// don't print anything, so we will get a false "no output" crash.
   440  		out, err = transformError(execProg.RunCProgRaw(inst.reproC, inst.cfg.Target,
   441  			inst.cfg.Timeouts.NoOutput/2))
   442  	}
   443  	return out, err
   444  }
   445  
   446  func (inst *inst) csourceOptions() (csource.Options, error) {
   447  	if len(inst.reproSyz) == 0 {
   448  		// If no syz repro is provided, the functionality is likely being used to test
   449  		// for the crashes that don't need a reproducer (e.g. kernel build/boot/test errors).
   450  		// Use the default options, that's the best we can do.
   451  		return csource.DefaultOpts(inst.cfg), nil
   452  	}
   453  	opts, err := csource.DeserializeOptions(inst.reproOpts)
   454  	if err != nil {
   455  		return opts, err
   456  	}
   457  	// Combine repro options and default options in a way that increases chances to reproduce the crash.
   458  	// We always enable threaded/collide as it should be [almost] strictly better.
   459  	opts.Repeat, opts.Threaded = true, true
   460  	return opts, nil
   461  }
   462  
   463  // nolint:revive
   464  func ExecprogCmd(execprog, executor, OS, arch, vmType string, opts csource.Options,
   465  	optionalFlags bool, slowdown int, progFile string) string {
   466  	repeatCount := 1
   467  	if opts.Repeat {
   468  		repeatCount = 0
   469  	}
   470  	sandbox := opts.Sandbox
   471  	if sandbox == "" {
   472  		// Executor does not support empty sandbox, so we use none instead.
   473  		sandbox = "none"
   474  	}
   475  	osArg := ""
   476  	if targets.Get(OS, arch).HostFuzzer {
   477  		osArg = " -os=" + OS
   478  	}
   479  	optionalArg := ""
   480  	if opts.Fault && opts.FaultCall >= 0 {
   481  		optionalArg = fmt.Sprintf(" -fault_call=%v -fault_nth=%v",
   482  			opts.FaultCall, opts.FaultNth)
   483  	}
   484  	if optionalFlags {
   485  		optionalArg += " " + tool.OptionalFlags([]tool.Flag{
   486  			{Name: "slowdown", Value: fmt.Sprint(slowdown)},
   487  			{Name: "sandbox_arg", Value: fmt.Sprint(opts.SandboxArg)},
   488  			{Name: "type", Value: fmt.Sprint(vmType)},
   489  		})
   490  	}
   491  	return fmt.Sprintf("%v -executor=%v -arch=%v%v -sandbox=%v"+
   492  		" -procs=%v -repeat=%v -threaded=%v -collide=%v -cover=0%v %v",
   493  		execprog, executor, arch, osArg, sandbox,
   494  		opts.Procs, repeatCount, opts.Threaded, opts.Collide,
   495  		optionalArg, progFile)
   496  }
   497  
   498  var MakeBin = func() string {
   499  	if runtime.GOOS == targets.FreeBSD || runtime.GOOS == targets.OpenBSD {
   500  		return "gmake"
   501  	}
   502  	return "make"
   503  }()
   504  
   505  // nolint:revive
   506  func RunnerCmd(prog, fwdAddr, os, arch string, poolIdx, vmIdx int, threaded, newEnv bool) string {
   507  	return fmt.Sprintf("%s -addr=%s -os=%s -arch=%s -pool=%d -vm=%d "+
   508  		"-threaded=%t -new-env=%t", prog, fwdAddr, os, arch, poolIdx, vmIdx, threaded, newEnv)
   509  }
   510  
   511  // RunSmokeTest executes syz-manager in the smoke test mode and returns two values:
   512  // The crash report, if the testing failed.
   513  // An error if there was a problem not related to testing the kernel.
   514  func RunSmokeTest(cfg *mgrconfig.Config) (*report.Report, error) {
   515  	if !vm.AllowsOvercommit(cfg.Type) {
   516  		return nil, nil // No support for creating machines out of thin air.
   517  	}
   518  	osutil.MkdirAll(cfg.Workdir)
   519  	configFile := filepath.Join(cfg.Workdir, "manager.cfg")
   520  	if err := config.SaveFile(configFile, cfg); err != nil {
   521  		return nil, err
   522  	}
   523  	timeout := 30 * time.Minute * cfg.Timeouts.Scale
   524  	bin := filepath.Join(cfg.Syzkaller, "bin", "syz-manager")
   525  	output, retErr := osutil.RunCmd(timeout, "", bin, "-config", configFile, "-mode=smoke-test")
   526  	if retErr == nil {
   527  		return nil, nil
   528  	}
   529  	// If there was a kernel bug, report it to dashboard.
   530  	// Otherwise just save the output in a temp file and log an error, unclear what else we can do.
   531  	reportData, err := os.ReadFile(filepath.Join(cfg.Workdir, "report.json"))
   532  	if err != nil {
   533  		if os.IsNotExist(err) {
   534  			var verboseErr *osutil.VerboseError
   535  			if errors.As(retErr, &verboseErr) {
   536  				// Include more details into the report.
   537  				prefix := fmt.Sprintf("%s, exit code %d\n\n", verboseErr, verboseErr.ExitCode)
   538  				output = append([]byte(prefix), output...)
   539  			}
   540  			rep := &report.Report{
   541  				Title:  "SYZFATAL: image testing failed w/o kernel bug",
   542  				Output: output,
   543  			}
   544  			return rep, nil
   545  		}
   546  		return nil, err
   547  	}
   548  	rep := new(report.Report)
   549  	if err := json.Unmarshal(reportData, rep); err != nil {
   550  		return nil, fmt.Errorf("failed to unmarshal smoke test report: %w", err)
   551  	}
   552  	return rep, nil
   553  }