code-intelligence.com/cifuzz@v0.40.0/internal/bundler/libfuzzer_bundler.go (about)

     1  package bundler
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"runtime"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/otiai10/copy"
    15  	"github.com/pkg/errors"
    16  	"github.com/spf13/viper"
    17  	"golang.org/x/exp/maps"
    18  
    19  	"code-intelligence.com/cifuzz/internal/build"
    20  	"code-intelligence.com/cifuzz/internal/build/bazel"
    21  	"code-intelligence.com/cifuzz/internal/build/cmake"
    22  	"code-intelligence.com/cifuzz/internal/build/other"
    23  	"code-intelligence.com/cifuzz/internal/bundler/archive"
    24  	"code-intelligence.com/cifuzz/internal/cmdutils"
    25  	"code-intelligence.com/cifuzz/internal/cmdutils/logging"
    26  	"code-intelligence.com/cifuzz/internal/config"
    27  	"code-intelligence.com/cifuzz/pkg/dependencies"
    28  	"code-intelligence.com/cifuzz/pkg/log"
    29  	"code-intelligence.com/cifuzz/util/envutil"
    30  	"code-intelligence.com/cifuzz/util/fileutil"
    31  	"code-intelligence.com/cifuzz/util/sliceutil"
    32  )
    33  
    34  type configureVariant struct {
    35  	Sanitizers []string
    36  }
    37  
    38  // System library dependencies that are so common that we shouldn't emit a warning for them - they will be contained in
    39  // any reasonable Docker image.
    40  var wellKnownSystemLibraries = map[string][]*regexp.Regexp{
    41  	"linux": {
    42  		versionedLibraryRegexp("ld-linux-x86-64.so"),
    43  		versionedLibraryRegexp("libc.so"),
    44  		versionedLibraryRegexp("libgcc_s.so"),
    45  		versionedLibraryRegexp("libm.so"),
    46  		versionedLibraryRegexp("libstdc++.so"),
    47  	},
    48  }
    49  
    50  func versionedLibraryRegexp(unversionedBasename string) *regexp.Regexp {
    51  	return regexp.MustCompile(".*/" + regexp.QuoteMeta(unversionedBasename) + "[.0-9]*")
    52  }
    53  
    54  type libfuzzerBundler struct {
    55  	opts          *Opts
    56  	archiveWriter archive.ArchiveWriter
    57  }
    58  
    59  func newLibfuzzerBundler(opts *Opts, archiveWriter archive.ArchiveWriter) *libfuzzerBundler {
    60  	return &libfuzzerBundler{opts, archiveWriter}
    61  }
    62  
    63  func (b *libfuzzerBundler) bundle() ([]*archive.Fuzzer, error) {
    64  	err := b.checkDependencies()
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	buildResults, err := b.buildAllVariants()
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	// Add all fuzz test artifacts to the archive. There will be one "Fuzzer" metadata object for each pair of fuzz test
    75  	// and Builder instance.
    76  	var fuzzers []*archive.Fuzzer
    77  	deduplicatedSystemDeps := make(map[string]struct{})
    78  	for _, buildResult := range buildResults {
    79  		fuzzTestFuzzers, systemDeps, err := b.assembleArtifacts(buildResult)
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  		fuzzers = append(fuzzers, fuzzTestFuzzers...)
    84  		for _, systemDep := range systemDeps {
    85  			deduplicatedSystemDeps[systemDep] = struct{}{}
    86  		}
    87  	}
    88  
    89  	systemDeps := maps.Keys(deduplicatedSystemDeps)
    90  	sort.Strings(systemDeps)
    91  	if len(systemDeps) != 0 {
    92  		log.Warnf(`The following system libraries are not part of the artifact and have to be provided by the Docker image %q:
    93        %s`, b.opts.DockerImage, strings.Join(systemDeps, "\n  "))
    94  	}
    95  
    96  	return fuzzers, nil
    97  }
    98  
    99  func (b *libfuzzerBundler) buildAllVariants() ([]*build.Result, error) {
   100  	fuzzingVariant := configureVariant{
   101  		// TODO: Do not hardcode these values.
   102  		Sanitizers: []string{"address"},
   103  	}
   104  	// UBSan is not supported by MSVb.
   105  	// TODO: Not needed anymore when sanitizers are configurable,
   106  	//       then we do want to fail if the user explicitly asked for
   107  	//       UBSan.
   108  	if runtime.GOOS != "windows" {
   109  		fuzzingVariant.Sanitizers = append(fuzzingVariant.Sanitizers, "undefined")
   110  	}
   111  	configureVariants := []configureVariant{fuzzingVariant}
   112  
   113  	// Coverage builds are not supported by MSVb.
   114  	if runtime.GOOS != "windows" {
   115  		coverageVariant := configureVariant{
   116  			Sanitizers: []string{"coverage"},
   117  		}
   118  		configureVariants = append(configureVariants, coverageVariant)
   119  	}
   120  
   121  	switch b.opts.BuildSystem {
   122  	case config.BuildSystemBazel:
   123  		return b.buildAllVariantsBazel(configureVariants)
   124  	case config.BuildSystemCMake:
   125  		return b.buildAllVariantsCMake(configureVariants)
   126  	case config.BuildSystemOther:
   127  		return b.buildAllVariantsOther(configureVariants)
   128  	default:
   129  		// We panic here instead of returning an error because it's a
   130  		// programming error if this function was called with an
   131  		// unsupported build system, that case should have been handled
   132  		// in the Opts.Validate function.
   133  		panic(fmt.Sprintf("Unsupported build system: %v", b.opts.BuildSystem))
   134  	}
   135  }
   136  
   137  func (b *libfuzzerBundler) buildAllVariantsBazel(configureVariants []configureVariant) ([]*build.Result, error) {
   138  	var allResults []*build.Result
   139  	for i, variant := range configureVariants {
   140  		builder, err := bazel.NewBuilder(&bazel.BuilderOptions{
   141  			ProjectDir: b.opts.ProjectDir,
   142  			Args:       b.opts.BuildSystemArgs,
   143  			NumJobs:    b.opts.NumBuildJobs,
   144  			Stdout:     b.opts.BuildStdout,
   145  			Stderr:     b.opts.BuildStderr,
   146  			TempDir:    b.opts.tempDir,
   147  			Verbose:    viper.GetBool("verbose"),
   148  		})
   149  		if err != nil {
   150  			return nil, err
   151  		}
   152  
   153  		b.printBuildingMsg(variant, i)
   154  
   155  		if len(b.opts.FuzzTests) == 0 {
   156  			// We panic here instead of returning an error because it's a
   157  			// programming error if this function was called without any
   158  			// fuzz tests, that case should have been handled in the
   159  			// Opts.Validate function.
   160  			panic("No fuzz tests specified")
   161  		}
   162  
   163  		results, err := builder.BuildForBundle(variant.Sanitizers, b.opts.FuzzTests)
   164  		if err != nil {
   165  			return nil, err
   166  		}
   167  		allResults = append(allResults, results...)
   168  	}
   169  
   170  	return allResults, nil
   171  }
   172  
   173  func (b *libfuzzerBundler) buildAllVariantsCMake(configureVariants []configureVariant) ([]*build.Result, error) {
   174  	var allResults []*build.Result
   175  	for i, variant := range configureVariants {
   176  		builder, err := cmake.NewBuilder(&cmake.BuilderOptions{
   177  			ProjectDir: b.opts.ProjectDir,
   178  			Args:       b.opts.BuildSystemArgs,
   179  			Sanitizers: variant.Sanitizers,
   180  			Parallel: cmake.ParallelOptions{
   181  				Enabled: viper.IsSet("build-jobs"),
   182  				NumJobs: b.opts.NumBuildJobs,
   183  			},
   184  			Stdout:          b.opts.BuildStdout,
   185  			Stderr:          b.opts.BuildStderr,
   186  			FindRuntimeDeps: true,
   187  		})
   188  		if err != nil {
   189  			return nil, err
   190  		}
   191  
   192  		b.printBuildingMsg(variant, i)
   193  
   194  		err = builder.Configure()
   195  		if err != nil {
   196  			return nil, err
   197  		}
   198  
   199  		var fuzzTests []string
   200  		if len(b.opts.FuzzTests) == 0 {
   201  			fuzzTests, err = builder.ListFuzzTests()
   202  			if err != nil {
   203  				return nil, err
   204  			}
   205  		} else {
   206  			fuzzTests = b.opts.FuzzTests
   207  		}
   208  
   209  		// The fuzz tests passed to builder.Build must not contain
   210  		// duplicates, which is ensured by builder.ListFuzzTests()
   211  		// and the Opts.Validate() function.
   212  		results, err := builder.Build(fuzzTests)
   213  		if err != nil {
   214  			return nil, err
   215  		}
   216  		allResults = append(allResults, results...)
   217  	}
   218  
   219  	return allResults, nil
   220  }
   221  
   222  func (b *libfuzzerBundler) printBuildingMsg(variant configureVariant, i int) {
   223  	var typeDisplayString string
   224  	if isCoverageBuild(variant.Sanitizers) {
   225  		typeDisplayString = "coverage"
   226  	} else {
   227  		typeDisplayString = "fuzzing"
   228  	}
   229  
   230  	// Print a newline to separate the build logs unless this is the
   231  
   232  	// first variant build
   233  	if i > 0 && !logging.ShouldLogBuildToFile() {
   234  		log.Print()
   235  	}
   236  
   237  	log.Infof("Building for %s...", typeDisplayString)
   238  }
   239  
   240  func (b *libfuzzerBundler) buildAllVariantsOther(configureVariants []configureVariant) ([]*build.Result, error) {
   241  	if len(b.opts.BuildSystemArgs) > 0 {
   242  		log.Warnf("Passing additional arguments is not supported for build system type \"other\".\n"+
   243  			"These arguments are ignored: %s", strings.Join(b.opts.BuildSystemArgs, " "))
   244  	}
   245  
   246  	var results []*build.Result
   247  	for i, variant := range configureVariants {
   248  		builder, err := other.NewBuilder(&other.BuilderOptions{
   249  			ProjectDir:   b.opts.ProjectDir,
   250  			BuildCommand: b.opts.BuildCommand,
   251  			CleanCommand: b.opts.CleanCommand,
   252  			Sanitizers:   variant.Sanitizers,
   253  			Stdout:       b.opts.BuildStdout,
   254  			Stderr:       b.opts.BuildStderr,
   255  		})
   256  		if err != nil {
   257  			return nil, err
   258  		}
   259  
   260  		b.printBuildingMsg(variant, i)
   261  
   262  		if len(b.opts.FuzzTests) == 0 {
   263  			// We panic here instead of returning an error because it's a
   264  			// programming error if this function was called without any
   265  			// fuzz tests, that case should have been handled in the
   266  			// Opts.Validate function.
   267  			panic("No fuzz tests specified")
   268  		}
   269  
   270  		if err := builder.Clean(); err != nil {
   271  			return nil, err
   272  		}
   273  
   274  		for _, fuzzTest := range b.opts.FuzzTests {
   275  			result, err := builder.Build(fuzzTest)
   276  			if err != nil {
   277  				return nil, err
   278  			}
   279  
   280  			// To avoid that subsequent builds overwrite the artifacts
   281  			// from this build, we copy them to a temporary directory
   282  			// and adjust the paths in the build.Result struct
   283  			tempDir := filepath.Join(b.opts.tempDir, fuzzTestPrefix(result))
   284  			err = b.copyArtifactsToTempdir(result, tempDir)
   285  			if err != nil {
   286  				return nil, err
   287  			}
   288  
   289  			results = append(results, result)
   290  		}
   291  	}
   292  
   293  	return results, nil
   294  }
   295  
   296  func (b *libfuzzerBundler) copyArtifactsToTempdir(buildResult *build.Result, tempDir string) error {
   297  	fuzzTestExecutableAbsPath := buildResult.Executable
   298  	isBelow, err := fileutil.IsBelow(fuzzTestExecutableAbsPath, buildResult.BuildDir)
   299  	if err != nil {
   300  		return err
   301  	}
   302  	if isBelow {
   303  		relPath, err := filepath.Rel(buildResult.BuildDir, fuzzTestExecutableAbsPath)
   304  		if err != nil {
   305  			return errors.WithStack(err)
   306  		}
   307  		newExecutablePath := filepath.Join(tempDir, relPath)
   308  		err = copy.Copy(buildResult.Executable, newExecutablePath)
   309  		if err != nil {
   310  			return errors.WithStack(err)
   311  		}
   312  		buildResult.Executable = newExecutablePath
   313  	}
   314  
   315  	// Try to copy the regular files first before copying the corresponding symlinks.
   316  	// Failing to do so results in errors that target of the symlink does not exist
   317  	// in the temp directory.
   318  	sort.Slice(buildResult.RuntimeDeps, func(i, j int) bool {
   319  		return !fileutil.IsSymlink(buildResult.RuntimeDeps[i])
   320  	})
   321  
   322  	for i, dep := range buildResult.RuntimeDeps {
   323  		isBelow, err = fileutil.IsBelow(dep, buildResult.BuildDir)
   324  		if err != nil {
   325  			return err
   326  		}
   327  		var topDir string
   328  		if isBelow {
   329  			topDir = buildResult.BuildDir
   330  		} else {
   331  			topDir = "/"
   332  		}
   333  
   334  		relPath, err := filepath.Rel(topDir, dep)
   335  		if err != nil {
   336  			return errors.WithStack(err)
   337  		}
   338  		newDepPath := filepath.Join(tempDir, relPath)
   339  
   340  		// When dealing with symlinks, resolve the path so that we copy the actual file
   341  		// to the temporary directory. This ensures that the dynamic dependencies resolved
   342  		// by ldd and added into the bundle are valid files.
   343  		resolvedPath, err := filepath.EvalSymlinks(dep)
   344  		if err != nil {
   345  			return errors.WithStack(err)
   346  		}
   347  		err = copy.Copy(resolvedPath, newDepPath)
   348  		if err != nil {
   349  			return errors.WithStack(err)
   350  		}
   351  
   352  		buildResult.RuntimeDeps[i] = newDepPath
   353  	}
   354  	buildResult.BuildDir = tempDir
   355  
   356  	return nil
   357  }
   358  
   359  func (b *libfuzzerBundler) checkDependencies() error {
   360  	var deps []dependencies.Key
   361  	switch b.opts.BuildSystem {
   362  	case config.BuildSystemCMake:
   363  		deps = []dependencies.Key{dependencies.Clang, dependencies.CMake}
   364  	case config.BuildSystemOther:
   365  		deps = []dependencies.Key{dependencies.Clang}
   366  	}
   367  	err := dependencies.Check(deps, b.opts.ProjectDir)
   368  	if err != nil {
   369  		log.Error(err)
   370  		return cmdutils.WrapSilentError(err)
   371  	}
   372  	return nil
   373  }
   374  
   375  //nolint:nonamedreturns
   376  func (b *libfuzzerBundler) assembleArtifacts(buildResult *build.Result) (
   377  	fuzzers []*archive.Fuzzer,
   378  	systemDeps []string,
   379  	err error,
   380  ) {
   381  	fuzzTestExecutableAbsPath := buildResult.Executable
   382  
   383  	// Add all build artifacts under a subdirectory of the fuzz test base path so that these files don't clash with
   384  	// seeds and dictionaries.
   385  	buildArtifactsPrefix := filepath.Join(fuzzTestPrefix(buildResult), "bin")
   386  
   387  	// Add the fuzz test executable.
   388  	ok, err := fileutil.IsBelow(fuzzTestExecutableAbsPath, buildResult.BuildDir)
   389  	if err != nil {
   390  		return
   391  	}
   392  	if !ok {
   393  		err = errors.Errorf("fuzz test executable (%s) is not below build directory (%s)", fuzzTestExecutableAbsPath, buildResult.BuildDir)
   394  		return
   395  	}
   396  	fuzzTestExecutableRelPath, err := filepath.Rel(buildResult.BuildDir, fuzzTestExecutableAbsPath)
   397  	if err != nil {
   398  		err = errors.WithStack(err)
   399  		return
   400  	}
   401  	fuzzTestArchivePath := filepath.Join(buildArtifactsPrefix, fuzzTestExecutableRelPath)
   402  	err = b.archiveWriter.WriteFile(fuzzTestArchivePath, fuzzTestExecutableAbsPath)
   403  	if err != nil {
   404  		return
   405  	}
   406  
   407  	// On macOS, debug information is collected in a separate .dSYM file. We bundle it in to get source locations
   408  	// resolved in stack traces.
   409  	fuzzTestDsymAbsPath := fuzzTestExecutableAbsPath + ".dSYM"
   410  	dsymExists, err := fileutil.Exists(fuzzTestDsymAbsPath)
   411  	if err != nil {
   412  		err = errors.WithStack(err)
   413  		return
   414  	}
   415  	if dsymExists {
   416  		fuzzTestDsymArchivePath := fuzzTestArchivePath + ".dSYM"
   417  		err = b.archiveWriter.WriteDir(fuzzTestDsymArchivePath, fuzzTestDsymAbsPath)
   418  		if err != nil {
   419  			return
   420  		}
   421  	}
   422  
   423  	var libraryPaths []string
   424  	// Add the runtime dependencies of the fuzz test executable.
   425  	externalLibrariesPrefix := ""
   426  depsLoop:
   427  	for _, dep := range buildResult.RuntimeDeps {
   428  		var isBelowBuildDir bool
   429  		isBelowBuildDir, err = fileutil.IsBelow(dep, buildResult.BuildDir)
   430  		if err != nil {
   431  			return
   432  		}
   433  		if isBelowBuildDir {
   434  			var buildDirRelPath string
   435  			buildDirRelPath, err = filepath.Rel(buildResult.BuildDir, dep)
   436  			if err != nil {
   437  				err = errors.WithStack(err)
   438  				return
   439  			}
   440  
   441  			if b.opts.BuildSystem == config.BuildSystemOther {
   442  				libraryPath := filepath.Join(buildArtifactsPrefix, filepath.Dir(buildDirRelPath))
   443  				if !sliceutil.Contains(libraryPaths, libraryPath) {
   444  					libraryPaths = append(libraryPaths, libraryPath)
   445  				}
   446  			}
   447  
   448  			var hash string
   449  			hash, err = sha256sum(dep)
   450  			if err != nil {
   451  				return
   452  			}
   453  			casPath := filepath.Join("cas", hash[:2], hash[2:], filepath.Base(dep))
   454  			if !b.archiveWriter.HasFileEntry(casPath) {
   455  				err = b.archiveWriter.WriteFile(casPath, dep)
   456  				if err != nil {
   457  					return
   458  				}
   459  			}
   460  			err = b.archiveWriter.WriteHardLink(casPath, filepath.Join(buildArtifactsPrefix, buildDirRelPath))
   461  			if err != nil {
   462  				return
   463  			}
   464  			continue
   465  		}
   466  
   467  		// The runtime dependency is not built as part of the current project. It will be of one of the following types:
   468  		// 1. A standard system library that is available in all reasonable Docker images.
   469  		// 2. A more uncommon system library that may require additional packages to be installed (e.g. X11), but still
   470  		//    lives in a standard system library directory (e.g. /usr/lib). Such dependencies are expected to be
   471  		//    provided by the Docker image used as the run environment.
   472  		// 3. Any other external dependency (e.g. a CMake target imported from another CMake project with a separate
   473  		//    build directory). These are not expected to be part of the Docker image and thus added to the archive
   474  		//    in a special directory that is added to the library search path at runtime.
   475  
   476  		// 1. is handled by ignoring these runtime dependencies.
   477  		for _, wellKnownSystemLibrary := range wellKnownSystemLibraries[runtime.GOOS] {
   478  			if wellKnownSystemLibrary.MatchString(dep) {
   479  				continue depsLoop
   480  			}
   481  		}
   482  
   483  		// 2. is handled by returning a list of these libraries that is shown to the user as a warning about the
   484  		// required contents of the Docker image specified as the run environment.
   485  		if fileutil.IsSystemLibrary(dep) {
   486  			systemDeps = append(systemDeps, dep)
   487  			continue depsLoop
   488  		}
   489  
   490  		// 3. is handled by staging the dependency in a special external library directory in the archive that is added
   491  		// to the library search path in the run environment.
   492  		// Note: Since all libraries are placed in a single directory, we have to ensure that basenames of external
   493  		// libraries are unique. If they aren't, we report a conflict.
   494  		externalLibrariesPrefix = filepath.Join(fuzzTestPrefix(buildResult), "external_libs")
   495  		archivePath := filepath.Join(externalLibrariesPrefix, filepath.Base(dep))
   496  		if b.archiveWriter.HasFileEntry(archivePath) {
   497  			err = errors.Errorf(
   498  				"fuzz test %q has conflicting runtime dependencies: %s and %s",
   499  				buildResult.Name,
   500  				dep,
   501  				b.archiveWriter.GetSourcePath(archivePath),
   502  			)
   503  			return
   504  		}
   505  		err = b.archiveWriter.WriteFile(archivePath, dep)
   506  		if err != nil {
   507  			return
   508  		}
   509  	}
   510  
   511  	// Add dictionary to archive
   512  	var archiveDict string
   513  	if b.opts.Dictionary != "" {
   514  		archiveDict = filepath.Join(fuzzTestPrefix(buildResult), "dict")
   515  		err = b.archiveWriter.WriteFile(archiveDict, b.opts.Dictionary)
   516  		if err != nil {
   517  			return
   518  		}
   519  	}
   520  
   521  	// Add seeds from user-specified seed corpus dirs (if any) and the
   522  	// default seed corpus (if it exists) to the seeds directory in the
   523  	// archive
   524  	seedCorpusDirs := b.opts.SeedCorpusDirs
   525  	exists, err := fileutil.Exists(buildResult.SeedCorpus)
   526  	if err != nil {
   527  		return
   528  	}
   529  	if exists {
   530  		seedCorpusDirs = append([]string{buildResult.SeedCorpus}, seedCorpusDirs...)
   531  	}
   532  	var archiveSeedsDir string
   533  	if len(seedCorpusDirs) > 0 {
   534  		archiveSeedsDir = filepath.Join(fuzzTestPrefix(buildResult), "seeds")
   535  
   536  		err = prepareSeeds(seedCorpusDirs, archiveSeedsDir, b.archiveWriter)
   537  		if err != nil {
   538  			return
   539  		}
   540  	}
   541  
   542  	// Set NO_CIFUZZ=1 to avoid that remotely executed fuzz tests try
   543  	// to start cifuzz
   544  	env, err := envutil.Setenv(b.opts.Env, "NO_CIFUZZ", "1")
   545  	if err != nil {
   546  		return
   547  	}
   548  
   549  	baseFuzzerInfo := archive.Fuzzer{
   550  		Target:     buildResult.Name,
   551  		Path:       fuzzTestArchivePath,
   552  		ProjectDir: buildResult.ProjectDir,
   553  		Dictionary: archiveDict,
   554  		Seeds:      archiveSeedsDir,
   555  		EngineOptions: archive.EngineOptions{
   556  			Env:   env,
   557  			Flags: b.opts.EngineArgs,
   558  		},
   559  		MaxRunTime: uint(b.opts.Timeout.Seconds()),
   560  	}
   561  
   562  	if externalLibrariesPrefix != "" {
   563  		libraryPaths = append(libraryPaths, externalLibrariesPrefix)
   564  	}
   565  	baseFuzzerInfo.LibraryPaths = libraryPaths
   566  
   567  	if isCoverageBuild(buildResult.Sanitizers) {
   568  		fuzzer := baseFuzzerInfo
   569  		fuzzer.Engine = "LLVM_COV"
   570  		// We use libFuzzer's crash-resistant merge mode. The first positional argument has to be an empty directory,
   571  		// for which we use the working directory (empty at the beginning of a job as we include an empty work_dir in
   572  		// the bundle). The second positional argument is the corpus directory passed in by the backend.
   573  		// Since most libFuzzer options are not useful or potentially disruptive for coverage runs, we do not include
   574  		// flags passed in via `--engine_args`.
   575  		fuzzer.EngineOptions.Flags = []string{"-merge=1", "."}
   576  		fuzzers = []*archive.Fuzzer{&fuzzer}
   577  		// Coverage builds are separate from sanitizer builds, so we don't have any other fuzzers to add.
   578  		return
   579  	}
   580  
   581  	for _, sanitizer := range buildResult.Sanitizers {
   582  		if sanitizer == "undefined" {
   583  			// The artifact archive spec does not support UBSan as a standalone sanitizer.
   584  			continue
   585  		}
   586  		fuzzer := baseFuzzerInfo
   587  		fuzzer.Engine = "LIBFUZZER"
   588  		fuzzer.Sanitizer = strings.ToUpper(sanitizer)
   589  		fuzzers = append(fuzzers, &fuzzer)
   590  	}
   591  
   592  	return
   593  }
   594  
   595  // fuzzTestPrefix returns the path in the resulting artifact archive under which fuzz test specific files should be
   596  // added.
   597  func fuzzTestPrefix(buildResult *build.Result) string {
   598  	sanitizerSegment := strings.Join(buildResult.Sanitizers, "+")
   599  	if sanitizerSegment == "" {
   600  		sanitizerSegment = "none"
   601  	}
   602  	engine := "libfuzzer"
   603  	if isCoverageBuild(buildResult.Sanitizers) {
   604  		// The backend currently only passes the corpus directory (rather than the files contained in it) as
   605  		// an argument to the coverage binary if it finds the substring "replayer/coverage" in the fuzz test archive
   606  		// path.
   607  		// FIXME: Remove this workaround as soon as the artifact spec provides a way to specify compatibility with
   608  		//  directory arguments.
   609  		engine = "replayer"
   610  	}
   611  	return filepath.Join(engine, sanitizerSegment, buildResult.Name)
   612  }
   613  
   614  func isCoverageBuild(sanitizers []string) bool {
   615  	return len(sanitizers) == 1 && sanitizers[0] == "coverage"
   616  }
   617  
   618  func sha256sum(filename string) (string, error) {
   619  	f, err := os.Open(filename)
   620  	if err != nil {
   621  		return "", errors.WithStack(err)
   622  	}
   623  	defer f.Close()
   624  
   625  	h := sha256.New()
   626  	_, err = io.Copy(h, f)
   627  	if err != nil {
   628  		return "", errors.WithStack(err)
   629  	}
   630  
   631  	return fmt.Sprintf("%x", h.Sum(nil)), nil
   632  }