// Copyright 2021 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fuzz // This file contains the common code for compiling C/C++ and Rust fuzzers for Android. import ( "encoding/json" "sort" "strings" "github.com/google/blueprint/proptools" "android/soong/android" ) type Lang string const ( Cc Lang = "" Rust Lang = "rust" ) var BoolDefault = proptools.BoolDefault type FuzzModule struct { android.ModuleBase android.DefaultableModuleBase android.ApexModuleBase } type FuzzPackager struct { Packages android.Paths FuzzTargets map[string]bool SharedLibInstallStrings []string } type FileToZip struct { SourceFilePath android.Path DestinationPathPrefix string } type ArchOs struct { HostOrTarget string Arch string Dir string } type FuzzConfig struct { // Email address of people to CC on bugs or contact about this fuzz target. Cc []string `json:"cc,omitempty"` // Specify whether to enable continuous fuzzing on devices. Defaults to true. Fuzz_on_haiku_device *bool `json:"fuzz_on_haiku_device,omitempty"` // Specify whether to enable continuous fuzzing on host. Defaults to true. Fuzz_on_haiku_host *bool `json:"fuzz_on_haiku_host,omitempty"` // Component in Google's bug tracking system that bugs should be filed to. Componentid *int64 `json:"componentid,omitempty"` // Hotlists in Google's bug tracking system that bugs should be marked with. Hotlists []string `json:"hotlists,omitempty"` // Specify whether this fuzz target was submitted by a researcher. Defaults // to false. Researcher_submitted *bool `json:"researcher_submitted,omitempty"` // Specify who should be acknowledged for CVEs in the Android Security // Bulletin. Acknowledgement []string `json:"acknowledgement,omitempty"` // Additional options to be passed to libfuzzer when run in Haiku. Libfuzzer_options []string `json:"libfuzzer_options,omitempty"` // Additional options to be passed to HWASAN when running on-device in Haiku. Hwasan_options []string `json:"hwasan_options,omitempty"` // Additional options to be passed to HWASAN when running on host in Haiku. Asan_options []string `json:"asan_options,omitempty"` } type FuzzProperties struct { // Optional list of seed files to be installed to the fuzz target's output // directory. Corpus []string `android:"path"` // Optional list of data files to be installed to the fuzz target's output // directory. Directory structure relative to the module is preserved. Data []string `android:"path"` // Optional dictionary to be installed to the fuzz target's output directory. Dictionary *string `android:"path"` // Config for running the target on fuzzing infrastructure. Fuzz_config *FuzzConfig } type FuzzPackagedModule struct { FuzzProperties FuzzProperties Dictionary android.Path Corpus android.Paths CorpusIntermediateDir android.Path Config android.Path Data android.Paths DataIntermediateDir android.Path } func IsValid(fuzzModule FuzzModule) bool { // Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of // fuzz targets we're going to package anyway. if !fuzzModule.Enabled() || fuzzModule.InRamdisk() || fuzzModule.InVendorRamdisk() || fuzzModule.InRecovery() { return false } // Discard modules that are in an unavailable namespace. if !fuzzModule.ExportedToMake() { return false } return true } func (s *FuzzPackager) PackageArtifacts(ctx android.SingletonContext, module android.Module, fuzzModule FuzzPackagedModule, archDir android.OutputPath, builder *android.RuleBuilder) []FileToZip { // Package the corpora into a zipfile. var files []FileToZip if fuzzModule.Corpus != nil { corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip") command := builder.Command().BuiltTool("soong_zip"). Flag("-j"). FlagWithOutput("-o ", corpusZip) rspFile := corpusZip.ReplaceExtension(ctx, "rsp") command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.Corpus) files = append(files, FileToZip{corpusZip, ""}) } // Package the data into a zipfile. if fuzzModule.Data != nil { dataZip := archDir.Join(ctx, module.Name()+"_data.zip") command := builder.Command().BuiltTool("soong_zip"). FlagWithOutput("-o ", dataZip) for _, f := range fuzzModule.Data { intermediateDir := strings.TrimSuffix(f.String(), f.Rel()) command.FlagWithArg("-C ", intermediateDir) command.FlagWithInput("-f ", f) } files = append(files, FileToZip{dataZip, ""}) } // The dictionary. if fuzzModule.Dictionary != nil { files = append(files, FileToZip{fuzzModule.Dictionary, ""}) } // Additional fuzz config. if fuzzModule.Config != nil { files = append(files, FileToZip{fuzzModule.Config, ""}) } return files } func (s *FuzzPackager) BuildZipFile(ctx android.SingletonContext, module android.Module, fuzzModule FuzzPackagedModule, files []FileToZip, builder *android.RuleBuilder, archDir android.OutputPath, archString string, hostOrTargetString string, archOs ArchOs, archDirs map[ArchOs][]FileToZip) ([]FileToZip, bool) { fuzzZip := archDir.Join(ctx, module.Name()+".zip") command := builder.Command().BuiltTool("soong_zip"). Flag("-j"). FlagWithOutput("-o ", fuzzZip) for _, file := range files { if file.DestinationPathPrefix != "" { command.FlagWithArg("-P ", file.DestinationPathPrefix) } else { command.Flag("-P ''") } command.FlagWithInput("-f ", file.SourceFilePath) } builder.Build("create-"+fuzzZip.String(), "Package "+module.Name()+" for "+archString+"-"+hostOrTargetString) // Don't add modules to 'make haiku-rust' that are set to not be // exported to the fuzzing infrastructure. if config := fuzzModule.FuzzProperties.Fuzz_config; config != nil { if strings.Contains(hostOrTargetString, "host") && !BoolDefault(config.Fuzz_on_haiku_host, true) { return archDirs[archOs], false } else if !BoolDefault(config.Fuzz_on_haiku_device, true) { return archDirs[archOs], false } } s.FuzzTargets[module.Name()] = true archDirs[archOs] = append(archDirs[archOs], FileToZip{fuzzZip, ""}) return archDirs[archOs], true } func (f *FuzzConfig) String() string { b, err := json.Marshal(f) if err != nil { panic(err) } return string(b) } func (s *FuzzPackager) CreateFuzzPackage(ctx android.SingletonContext, archDirs map[ArchOs][]FileToZip, lang Lang, pctx android.PackageContext) { var archOsList []ArchOs for archOs := range archDirs { archOsList = append(archOsList, archOs) } sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].Dir < archOsList[j].Dir }) for _, archOs := range archOsList { filesToZip := archDirs[archOs] arch := archOs.Arch hostOrTarget := archOs.HostOrTarget builder := android.NewRuleBuilder(pctx, ctx) zipFileName := "fuzz-" + hostOrTarget + "-" + arch + ".zip" if lang == Rust { zipFileName = "fuzz-rust-" + hostOrTarget + "-" + arch + ".zip" } outputFile := android.PathForOutput(ctx, zipFileName) s.Packages = append(s.Packages, outputFile) command := builder.Command().BuiltTool("soong_zip"). Flag("-j"). FlagWithOutput("-o ", outputFile). Flag("-L 0") // No need to try and re-compress the zipfiles. for _, fileToZip := range filesToZip { if fileToZip.DestinationPathPrefix != "" { command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix) } else { command.Flag("-P ''") } command.FlagWithInput("-f ", fileToZip.SourceFilePath) } builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget, "Create fuzz target packages for "+arch+"-"+hostOrTarget) } } func (s *FuzzPackager) PreallocateSlice(ctx android.MakeVarsContext, targets string) { fuzzTargets := make([]string, 0, len(s.FuzzTargets)) for target, _ := range s.FuzzTargets { fuzzTargets = append(fuzzTargets, target) } sort.Strings(fuzzTargets) ctx.Strict(targets, strings.Join(fuzzTargets, " ")) } // CollectAllSharedDependencies performs a breadth-first search over the provided module's // dependencies using `visitDirectDeps` to enumerate all shared library // dependencies. We require breadth-first expansion, as otherwise we may // incorrectly use the core libraries (sanitizer runtimes, libc, libdl, etc.) // from a dependency. This may cause issues when dependencies have explicit // sanitizer tags, as we may get a dependency on an unsanitized libc, etc. func CollectAllSharedDependencies(ctx android.SingletonContext, module android.Module, unstrippedOutputFile func(module android.Module) android.Path, isValidSharedDependency func(dependency android.Module) bool) android.Paths { var fringe []android.Module seen := make(map[string]bool) // Enumerate the first level of dependencies, as we discard all non-library // modules in the BFS loop below. ctx.VisitDirectDeps(module, func(dep android.Module) { if isValidSharedDependency(dep) { fringe = append(fringe, dep) } }) var sharedLibraries android.Paths for i := 0; i < len(fringe); i++ { module := fringe[i] if seen[module.Name()] { continue } seen[module.Name()] = true sharedLibraries = append(sharedLibraries, unstrippedOutputFile(module)) ctx.VisitDirectDeps(module, func(dep android.Module) { if isValidSharedDependency(dep) && !seen[dep.Name()] { fringe = append(fringe, dep) } }) } return sharedLibraries }