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

     1  // Copyright 2019 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  // syz-testbuild tests kernel build/boot on releases as it will be done by pkg/bisect.
     5  // This allows to ensure that, for example, a change to kernel config won't break
     6  // build/boot on older releases and consequently won't break bisection process.
     7  // The binary needs to run under root because it creates images.
     8  // The kernel checkout given to the tool will be cleaned and used for in-tree builds.
     9  // Example invocation:
    10  //
    11  //	sudo syz-testbuild -kernel_src $LINUX_CHECKOUT \
    12  //		-config dashboard/config/upstream-kasan.config \
    13  //		-sysctl dashboard/config/upstream.sysctl \
    14  //		-cmdline dashboard/config/upstream-apparmor.cmdline \
    15  //		-userspace $WHEEZY_USERSPACE \
    16  //		-bisect_bin $BISECT_BIN
    17  //
    18  // A suitable wheezy userspace can be downloaded from:
    19  // https://storage.googleapis.com/syzkaller/wheezy.tar.gz
    20  // A set of binaries required for bisection (older compilers) can be downloaded from:
    21  // https://storage.googleapis.com/syzkaller/bisect_bin.tar.gz
    22  package main
    23  
    24  import (
    25  	"encoding/json"
    26  	"errors"
    27  	"flag"
    28  	"fmt"
    29  	"log"
    30  	"os"
    31  	"runtime"
    32  
    33  	"github.com/google/syzkaller/pkg/instance"
    34  	"github.com/google/syzkaller/pkg/mgrconfig"
    35  	"github.com/google/syzkaller/pkg/osutil"
    36  	"github.com/google/syzkaller/pkg/tool"
    37  	"github.com/google/syzkaller/pkg/vcs"
    38  )
    39  
    40  var (
    41  	flagOS            = flag.String("os", runtime.GOOS, "OS to test")
    42  	flagArch          = flag.String("arch", runtime.GOARCH, "arch to test")
    43  	flagKernelSrc     = flag.String("kernel_src", "", "path to kernel checkout")
    44  	flagKernelConfig  = flag.String("config", "", "kernel config")
    45  	flagKernelSysctl  = flag.String("sysctl", "", "kernel sysctl file")
    46  	flagKernelCmdline = flag.String("cmdline", "", "kernel cmdline file")
    47  	flagUserspace     = flag.String("userspace", "", "path to userspace for build")
    48  	flagBisectBin     = flag.String("bisect_bin", "", "path to bisection binaries")
    49  	flagSyzkaller     = flag.String("syzkaller", ".", "path to built syzkaller")
    50  	flagSandbox       = flag.String("sandbox", "namespace", "sandbox to use for testing")
    51  	flagSandboxArg    = flag.Int("sandbox_arg", 0, "an argument for sandbox runner")
    52  	flagCompiler      = flag.String("compiler", "clang", "compiler to use")
    53  	flagCompilerType  = flag.String("compiler_type", "clang", "compiler to use")
    54  	flagLinker        = flag.String("linker", "ld.lld", "linker to use")
    55  )
    56  
    57  const (
    58  	vmType   = "qemu"
    59  	numTests = 5
    60  )
    61  
    62  func main() {
    63  	flag.Parse()
    64  	if os.Getuid() != 0 {
    65  		tool.Failf("image build will fail, run under root")
    66  	}
    67  	os.Setenv("SYZ_DISABLE_SANDBOXING", "yes")
    68  	dir, err := os.MkdirTemp("", "syz-testbuild")
    69  	if err != nil {
    70  		tool.Fail(err)
    71  	}
    72  	defer os.RemoveAll(dir)
    73  	cfg := &mgrconfig.Config{
    74  		RawTarget:  *flagOS + "/" + *flagArch,
    75  		HTTP:       ":0",
    76  		Workdir:    dir,
    77  		KernelSrc:  *flagKernelSrc,
    78  		KernelObj:  *flagKernelSrc,
    79  		Syzkaller:  *flagSyzkaller,
    80  		Sandbox:    *flagSandbox,
    81  		SandboxArg: int64(*flagSandboxArg),
    82  		SSHUser:    "root",
    83  		Procs:      1,
    84  		Cover:      false,
    85  		Type:       vmType,
    86  		VM: json.RawMessage([]byte(fmt.Sprintf(`{"count": %v,
    87  			"cpu": 2,
    88  			"mem": 2048,
    89  			"cmdline": "root=/dev/sda1"}`, numTests))),
    90  		Derived: mgrconfig.Derived{
    91  			TargetOS:     *flagOS,
    92  			TargetArch:   *flagArch,
    93  			TargetVMArch: *flagArch,
    94  		},
    95  		Experimental: mgrconfig.Experimental{
    96  			DescriptionsMode: "manual",
    97  		},
    98  	}
    99  	if err := mgrconfig.SetTargets(cfg); err != nil {
   100  		tool.Fail(err)
   101  	}
   102  	if err := mgrconfig.Complete(cfg); err != nil {
   103  		tool.Fail(err)
   104  	}
   105  	repo, err := vcs.NewRepo(*flagOS, vmType, *flagKernelSrc)
   106  	if err != nil {
   107  		tool.Fail(err)
   108  	}
   109  	bisecter := repo.(vcs.Bisecter)
   110  	head, err := repo.Commit(vcs.HEAD)
   111  	if err != nil {
   112  		tool.Fail(err)
   113  	}
   114  	log.Printf("HEAD is on %v %v", head.Hash, head.Title)
   115  	tags, err := bisecter.PreviousReleaseTags(head.Hash, "gcc")
   116  	if err != nil {
   117  		tool.Fail(err)
   118  	}
   119  	log.Printf("tags: %v", tags)
   120  	kernelConfig, err := os.ReadFile(*flagKernelConfig)
   121  	if err != nil {
   122  		tool.Fail(err)
   123  	}
   124  	env, err := instance.NewEnv(cfg, nil, nil)
   125  	if err != nil {
   126  		tool.Fail(err)
   127  	}
   128  	test(repo, bisecter, kernelConfig, env, head)
   129  	for _, tag := range tags {
   130  		com, err := repo.SwitchCommit(tag)
   131  		if err != nil {
   132  			tool.Fail(err)
   133  		}
   134  		test(repo, bisecter, kernelConfig, env, com)
   135  	}
   136  }
   137  
   138  func test(repo vcs.Repo, bisecter vcs.Bisecter, kernelConfig []byte, env instance.Env, com *vcs.Commit) {
   139  	bisectEnv, err := bisecter.EnvForCommit(*flagCompiler, *flagCompilerType, *flagBisectBin, com.Hash, kernelConfig, nil)
   140  	if err != nil {
   141  		tool.Fail(err)
   142  	}
   143  	log.Printf("testing: %v %v using %v", com.Hash, com.Title, bisectEnv.Compiler)
   144  	buildCfg := &instance.BuildKernelConfig{
   145  		CompilerBin:  bisectEnv.Compiler,
   146  		LinkerBin:    *flagLinker,
   147  		CcacheBin:    "",
   148  		UserspaceDir: *flagUserspace,
   149  		CmdlineFile:  *flagKernelCmdline,
   150  		SysctlFile:   *flagKernelSysctl,
   151  		KernelConfig: bisectEnv.KernelConfig,
   152  	}
   153  	if err := env.CleanKernel(buildCfg); err != nil {
   154  		tool.Fail(err)
   155  	}
   156  	_, _, err = env.BuildKernel(buildCfg)
   157  	if err != nil {
   158  		var verr *osutil.VerboseError
   159  		if errors.As(err, &verr) {
   160  			log.Printf("BUILD BROKEN: %s", verr)
   161  			saveLog(com.Hash, 0, verr.Output)
   162  		} else {
   163  			log.Printf("BUILD BROKEN: %v", err)
   164  		}
   165  		return
   166  	}
   167  	log.Printf("build OK")
   168  	results, err := env.Test(numTests, nil, nil, nil)
   169  	if err != nil {
   170  		tool.Fail(err)
   171  	}
   172  	var verdicts []string
   173  	for i, res := range results {
   174  		if res.Error == nil {
   175  			verdicts = append(verdicts, "OK")
   176  			continue
   177  		}
   178  
   179  		var testError *instance.TestError
   180  		var crashError *instance.CrashError
   181  		switch {
   182  		case errors.As(res.Error, &testError):
   183  			if testError.Boot {
   184  				verdicts = append(verdicts, fmt.Sprintf("boot failed: %v", testError))
   185  			} else {
   186  				verdicts = append(verdicts, fmt.Sprintf("basic kernel testing failed: %v", testError))
   187  			}
   188  			output := testError.Output
   189  			if testError.Report != nil {
   190  				output = testError.Report.Output
   191  			}
   192  			saveLog(com.Hash, i, output)
   193  		case errors.As(res.Error, &crashError):
   194  			verdicts = append(verdicts, fmt.Sprintf("crashed: %v", crashError))
   195  			output := crashError.Report.Report
   196  			if len(output) == 0 {
   197  				output = crashError.Report.Output
   198  			}
   199  			saveLog(com.Hash, i, output)
   200  		default:
   201  			verdicts = append(verdicts, fmt.Sprintf("failed: %v", err))
   202  		}
   203  	}
   204  	unique := make(map[string]bool)
   205  	for _, verdict := range verdicts {
   206  		unique[verdict] = true
   207  	}
   208  	if len(unique) == 1 {
   209  		log.Printf("all runs: %v", verdicts[0])
   210  	} else {
   211  		for i, verdict := range verdicts {
   212  			log.Printf("run #%v: %v", i, verdict)
   213  		}
   214  	}
   215  }
   216  
   217  func saveLog(hash string, idx int, data []byte) {
   218  	if len(data) == 0 {
   219  		return
   220  	}
   221  	osutil.WriteFile(fmt.Sprintf("%v.%v", hash, idx), data)
   222  }