github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/build/gvisor.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 build
     5  
     6  import (
     7  	"fmt"
     8  	"path/filepath"
     9  	"regexp"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/google/syzkaller/pkg/log"
    14  	"github.com/google/syzkaller/pkg/osutil"
    15  	"github.com/google/syzkaller/sys/targets"
    16  )
    17  
    18  type gvisor struct{}
    19  
    20  var bazelTargetPath = regexp.MustCompile(`(?sm:.*^)\s*Outputs: \[(.*)\](?sm:$.*)`)
    21  
    22  func (gvisor gvisor) build(params Params) (ImageDetails, error) {
    23  	if params.Compiler == "" {
    24  		params.Compiler = "bazel"
    25  	}
    26  
    27  	// Bring down bazel daemon right away. We don't need it running and consuming memory.
    28  	defer osutil.RunCmd(10*time.Minute, params.KernelDir, params.Compiler, "shutdown")
    29  
    30  	config, err := parseGVisorConfig(params.Config)
    31  	if err != nil {
    32  		return ImageDetails{}, fmt.Errorf("cannot parse gVisor configuration: %w", err)
    33  	}
    34  	args := []string{}
    35  
    36  	target := "//runsc:runsc"
    37  	if config.Race {
    38  		args = append(args, "--config=race")
    39  		target = "//runsc:runsc-race"
    40  	}
    41  	if config.Coverage {
    42  		coverageFiles := "//pkg/..."
    43  		exclusions := []string{
    44  			"//pkg/sentry/platform", "//pkg/ring0", // Breaks kvm.
    45  			"//pkg/coverage:coverage", // Too slow.
    46  		}
    47  		if config.Race {
    48  			// These targets use go:norace, which is not
    49  			// respected by coverage instrumentation. Race builds
    50  			// will be instrumented with atomic coverage (using
    51  			// sync/atomic.AddInt32), which will not work.
    52  			exclusions = append(exclusions, []string{
    53  				"//pkg/sleep:sleep",
    54  				"//pkg/sync:sync",
    55  				"//pkg/syncevent:syncevent",
    56  			}...)
    57  		}
    58  		for _, f := range exclusions {
    59  			coverageFiles += ",-" + f
    60  		}
    61  		args = append(args, []string{
    62  			"--collect_code_coverage",
    63  			"--instrumentation_filter=" + coverageFiles}...)
    64  	}
    65  	buildArgs := []string{"build", "--verbose_failures"}
    66  	buildArgs = append(buildArgs, args...)
    67  	buildArgs = append(buildArgs, target)
    68  	log.Logf(0, "bazel: %v", buildArgs)
    69  	// The 1 hour timeout is quite high. But we've seen false positives with 20 mins
    70  	// on the first build after bazel/deps update. Also other gvisor instances running
    71  	// on the same machine contribute to longer build times.
    72  	if _, err := osutil.RunCmd(60*time.Minute, params.KernelDir, params.Compiler, buildArgs...); err != nil {
    73  		return ImageDetails{}, err
    74  	}
    75  
    76  	// Find out a path to the runsc binary.
    77  	aqueryArgs := append([]string{"aquery"}, args...)
    78  	aqueryArgs = append(aqueryArgs, fmt.Sprintf("mnemonic(\"GoLink\", %s)", target))
    79  	log.Logf(0, "bazel: %v", aqueryArgs)
    80  	out, err := osutil.RunCmd(10*time.Minute, params.KernelDir, params.Compiler, aqueryArgs...)
    81  	if err != nil {
    82  		return ImageDetails{}, err
    83  	}
    84  
    85  	match := bazelTargetPath.FindSubmatch(out)
    86  	if match == nil {
    87  		return ImageDetails{}, fmt.Errorf("failed to find the runsc binary")
    88  	}
    89  	outBinary := filepath.Join(params.KernelDir, filepath.FromSlash(string(match[1])))
    90  
    91  	if err := osutil.CopyFile(outBinary, filepath.Join(params.OutputDir, "image")); err != nil {
    92  		return ImageDetails{}, err
    93  	}
    94  	sysTarget := targets.Get(params.TargetOS, params.TargetArch)
    95  	return ImageDetails{}, osutil.CopyFile(outBinary, filepath.Join(params.OutputDir, "obj", sysTarget.KernelObject))
    96  }
    97  
    98  func (gvisor) clean(kernelDir, targetArch string) error {
    99  	// Let's assume that bazel always properly handles build without cleaning (until proven otherwise).
   100  	return nil
   101  }
   102  
   103  // Known gVisor configuration flags.
   104  const (
   105  	gvisorFlagCover = "-cover"
   106  	gvisorFlagRace  = "-race"
   107  )
   108  
   109  // gvisorConfig is a gVisor configuration.
   110  type gvisorConfig struct {
   111  	// Coverage represents whether code coverage is enabled.
   112  	Coverage bool
   113  
   114  	// Race represents whether race condition detection is enabled.
   115  	Race bool
   116  }
   117  
   118  // parseGVisorConfig parses a set of flags into a `gvisorConfig`.
   119  func parseGVisorConfig(config []byte) (gvisorConfig, error) {
   120  	var cfg gvisorConfig
   121  	for _, flag := range strings.Fields(string(config)) {
   122  		switch flag {
   123  		case gvisorFlagCover:
   124  			cfg.Coverage = true
   125  		case gvisorFlagRace:
   126  			cfg.Race = true
   127  		default:
   128  			return cfg, fmt.Errorf("unknown gVisor configuration flag: %q", flag)
   129  		}
   130  	}
   131  	return cfg, nil
   132  }