1// Copyright 2021 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package fuzz 16 17// This file contains the common code for compiling C/C++ and Rust fuzzers for Android. 18 19import ( 20 "encoding/json" 21 "sort" 22 "strings" 23 24 "github.com/google/blueprint/proptools" 25 26 "android/soong/android" 27) 28 29type Lang string 30 31const ( 32 Cc Lang = "" 33 Rust Lang = "rust" 34 Java Lang = "java" 35) 36 37var BoolDefault = proptools.BoolDefault 38 39type FuzzModule struct { 40 android.ModuleBase 41 android.DefaultableModuleBase 42 android.ApexModuleBase 43} 44 45type FuzzPackager struct { 46 Packages android.Paths 47 FuzzTargets map[string]bool 48 SharedLibInstallStrings []string 49} 50 51type FileToZip struct { 52 SourceFilePath android.Path 53 DestinationPathPrefix string 54} 55 56type ArchOs struct { 57 HostOrTarget string 58 Arch string 59 Dir string 60} 61 62type FuzzConfig struct { 63 // Email address of people to CC on bugs or contact about this fuzz target. 64 Cc []string `json:"cc,omitempty"` 65 // Specify whether to enable continuous fuzzing on devices. Defaults to true. 66 Fuzz_on_haiku_device *bool `json:"fuzz_on_haiku_device,omitempty"` 67 // Specify whether to enable continuous fuzzing on host. Defaults to true. 68 Fuzz_on_haiku_host *bool `json:"fuzz_on_haiku_host,omitempty"` 69 // Component in Google's bug tracking system that bugs should be filed to. 70 Componentid *int64 `json:"componentid,omitempty"` 71 // Hotlists in Google's bug tracking system that bugs should be marked with. 72 Hotlists []string `json:"hotlists,omitempty"` 73 // Specify whether this fuzz target was submitted by a researcher. Defaults 74 // to false. 75 Researcher_submitted *bool `json:"researcher_submitted,omitempty"` 76 // Specify who should be acknowledged for CVEs in the Android Security 77 // Bulletin. 78 Acknowledgement []string `json:"acknowledgement,omitempty"` 79 // Additional options to be passed to libfuzzer when run in Haiku. 80 Libfuzzer_options []string `json:"libfuzzer_options,omitempty"` 81 // Additional options to be passed to HWASAN when running on-device in Haiku. 82 Hwasan_options []string `json:"hwasan_options,omitempty"` 83 // Additional options to be passed to HWASAN when running on host in Haiku. 84 Asan_options []string `json:"asan_options,omitempty"` 85} 86 87type FuzzProperties struct { 88 // Optional list of seed files to be installed to the fuzz target's output 89 // directory. 90 Corpus []string `android:"path"` 91 // Optional list of data files to be installed to the fuzz target's output 92 // directory. Directory structure relative to the module is preserved. 93 Data []string `android:"path"` 94 // Optional dictionary to be installed to the fuzz target's output directory. 95 Dictionary *string `android:"path"` 96 // Config for running the target on fuzzing infrastructure. 97 Fuzz_config *FuzzConfig 98} 99 100type FuzzPackagedModule struct { 101 FuzzProperties FuzzProperties 102 Dictionary android.Path 103 Corpus android.Paths 104 CorpusIntermediateDir android.Path 105 Config android.Path 106 Data android.Paths 107 DataIntermediateDir android.Path 108} 109 110func IsValid(fuzzModule FuzzModule) bool { 111 // Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of 112 // fuzz targets we're going to package anyway. 113 if !fuzzModule.Enabled() || fuzzModule.InRamdisk() || fuzzModule.InVendorRamdisk() || fuzzModule.InRecovery() { 114 return false 115 } 116 117 // Discard modules that are in an unavailable namespace. 118 if !fuzzModule.ExportedToMake() { 119 return false 120 } 121 122 return true 123} 124 125func (s *FuzzPackager) PackageArtifacts(ctx android.SingletonContext, module android.Module, fuzzModule FuzzPackagedModule, archDir android.OutputPath, builder *android.RuleBuilder) []FileToZip { 126 // Package the corpora into a zipfile. 127 var files []FileToZip 128 if fuzzModule.Corpus != nil { 129 corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip") 130 command := builder.Command().BuiltTool("soong_zip"). 131 Flag("-j"). 132 FlagWithOutput("-o ", corpusZip) 133 rspFile := corpusZip.ReplaceExtension(ctx, "rsp") 134 command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.Corpus) 135 files = append(files, FileToZip{corpusZip, ""}) 136 } 137 138 // Package the data into a zipfile. 139 if fuzzModule.Data != nil { 140 dataZip := archDir.Join(ctx, module.Name()+"_data.zip") 141 command := builder.Command().BuiltTool("soong_zip"). 142 FlagWithOutput("-o ", dataZip) 143 for _, f := range fuzzModule.Data { 144 intermediateDir := strings.TrimSuffix(f.String(), f.Rel()) 145 command.FlagWithArg("-C ", intermediateDir) 146 command.FlagWithInput("-f ", f) 147 } 148 files = append(files, FileToZip{dataZip, ""}) 149 } 150 151 // The dictionary. 152 if fuzzModule.Dictionary != nil { 153 files = append(files, FileToZip{fuzzModule.Dictionary, ""}) 154 } 155 156 // Additional fuzz config. 157 if fuzzModule.Config != nil { 158 files = append(files, FileToZip{fuzzModule.Config, ""}) 159 } 160 161 return files 162} 163 164func (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) { 165 fuzzZip := archDir.Join(ctx, module.Name()+".zip") 166 167 command := builder.Command().BuiltTool("soong_zip"). 168 Flag("-j"). 169 FlagWithOutput("-o ", fuzzZip) 170 171 for _, file := range files { 172 if file.DestinationPathPrefix != "" { 173 command.FlagWithArg("-P ", file.DestinationPathPrefix) 174 } else { 175 command.Flag("-P ''") 176 } 177 command.FlagWithInput("-f ", file.SourceFilePath) 178 } 179 180 builder.Build("create-"+fuzzZip.String(), 181 "Package "+module.Name()+" for "+archString+"-"+hostOrTargetString) 182 183 // Don't add modules to 'make haiku-rust' that are set to not be 184 // exported to the fuzzing infrastructure. 185 if config := fuzzModule.FuzzProperties.Fuzz_config; config != nil { 186 if strings.Contains(hostOrTargetString, "host") && !BoolDefault(config.Fuzz_on_haiku_host, true) { 187 return archDirs[archOs], false 188 } else if !BoolDefault(config.Fuzz_on_haiku_device, true) { 189 return archDirs[archOs], false 190 } 191 } 192 193 s.FuzzTargets[module.Name()] = true 194 archDirs[archOs] = append(archDirs[archOs], FileToZip{fuzzZip, ""}) 195 196 return archDirs[archOs], true 197} 198 199func (f *FuzzConfig) String() string { 200 b, err := json.Marshal(f) 201 if err != nil { 202 panic(err) 203 } 204 205 return string(b) 206} 207 208func (s *FuzzPackager) CreateFuzzPackage(ctx android.SingletonContext, archDirs map[ArchOs][]FileToZip, lang Lang, pctx android.PackageContext) { 209 var archOsList []ArchOs 210 for archOs := range archDirs { 211 archOsList = append(archOsList, archOs) 212 } 213 sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].Dir < archOsList[j].Dir }) 214 215 for _, archOs := range archOsList { 216 filesToZip := archDirs[archOs] 217 arch := archOs.Arch 218 hostOrTarget := archOs.HostOrTarget 219 builder := android.NewRuleBuilder(pctx, ctx) 220 zipFileName := "fuzz-" + hostOrTarget + "-" + arch + ".zip" 221 if lang == Rust { 222 zipFileName = "fuzz-rust-" + hostOrTarget + "-" + arch + ".zip" 223 } 224 if lang == Java { 225 zipFileName = "fuzz-java-" + hostOrTarget + "-" + arch + ".zip" 226 } 227 outputFile := android.PathForOutput(ctx, zipFileName) 228 229 s.Packages = append(s.Packages, outputFile) 230 231 command := builder.Command().BuiltTool("soong_zip"). 232 Flag("-j"). 233 FlagWithOutput("-o ", outputFile). 234 Flag("-L 0") // No need to try and re-compress the zipfiles. 235 236 for _, fileToZip := range filesToZip { 237 238 if fileToZip.DestinationPathPrefix != "" { 239 command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix) 240 } else { 241 command.Flag("-P ''") 242 } 243 command.FlagWithInput("-f ", fileToZip.SourceFilePath) 244 245 } 246 builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget, 247 "Create fuzz target packages for "+arch+"-"+hostOrTarget) 248 } 249} 250 251func (s *FuzzPackager) PreallocateSlice(ctx android.MakeVarsContext, targets string) { 252 fuzzTargets := make([]string, 0, len(s.FuzzTargets)) 253 for target, _ := range s.FuzzTargets { 254 fuzzTargets = append(fuzzTargets, target) 255 } 256 sort.Strings(fuzzTargets) 257 ctx.Strict(targets, strings.Join(fuzzTargets, " ")) 258} 259 260// CollectAllSharedDependencies performs a breadth-first search over the provided module's 261// dependencies using `visitDirectDeps` to enumerate all shared library 262// dependencies. We require breadth-first expansion, as otherwise we may 263// incorrectly use the core libraries (sanitizer runtimes, libc, libdl, etc.) 264// from a dependency. This may cause issues when dependencies have explicit 265// sanitizer tags, as we may get a dependency on an unsanitized libc, etc. 266func CollectAllSharedDependencies(ctx android.SingletonContext, module android.Module, unstrippedOutputFile func(module android.Module) android.Path, isValidSharedDependency func(dependency android.Module) bool) android.Paths { 267 var fringe []android.Module 268 269 seen := make(map[string]bool) 270 271 // Enumerate the first level of dependencies, as we discard all non-library 272 // modules in the BFS loop below. 273 ctx.VisitDirectDeps(module, func(dep android.Module) { 274 if isValidSharedDependency(dep) { 275 fringe = append(fringe, dep) 276 } 277 }) 278 279 var sharedLibraries android.Paths 280 281 for i := 0; i < len(fringe); i++ { 282 module := fringe[i] 283 if seen[module.Name()] { 284 continue 285 } 286 seen[module.Name()] = true 287 288 sharedLibraries = append(sharedLibraries, unstrippedOutputFile(module)) 289 ctx.VisitDirectDeps(module, func(dep android.Module) { 290 if isValidSharedDependency(dep) && !seen[dep.Name()] { 291 fringe = append(fringe, dep) 292 } 293 }) 294 } 295 296 return sharedLibraries 297} 298