1// Copyright 2016 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 cc 16 17import ( 18 "encoding/json" 19 "path/filepath" 20 "sort" 21 "strings" 22 23 "android/soong/android" 24 "android/soong/cc/config" 25) 26 27type FuzzConfig struct { 28 // Email address of people to CC on bugs or contact about this fuzz target. 29 Cc []string `json:"cc,omitempty"` 30 // Specify whether to enable continuous fuzzing on devices. Defaults to true. 31 Fuzz_on_haiku_device *bool `json:"fuzz_on_haiku_device,omitempty"` 32 // Specify whether to enable continuous fuzzing on host. Defaults to true. 33 Fuzz_on_haiku_host *bool `json:"fuzz_on_haiku_host,omitempty"` 34 // Component in Google's bug tracking system that bugs should be filed to. 35 Componentid *int64 `json:"componentid,omitempty"` 36 // Hotlists in Google's bug tracking system that bugs should be marked with. 37 Hotlists []string `json:"hotlists,omitempty"` 38} 39 40func (f *FuzzConfig) String() string { 41 b, err := json.Marshal(f) 42 if err != nil { 43 panic(err) 44 } 45 46 return string(b) 47} 48 49type FuzzProperties struct { 50 // Optional list of seed files to be installed to the fuzz target's output 51 // directory. 52 Corpus []string `android:"path"` 53 // Optional list of data files to be installed to the fuzz target's output 54 // directory. Directory structure relative to the module is preserved. 55 Data []string `android:"path"` 56 // Optional dictionary to be installed to the fuzz target's output directory. 57 Dictionary *string `android:"path"` 58 // Config for running the target on fuzzing infrastructure. 59 Fuzz_config *FuzzConfig 60} 61 62func init() { 63 android.RegisterModuleType("cc_fuzz", FuzzFactory) 64 android.RegisterSingletonType("cc_fuzz_packaging", fuzzPackagingFactory) 65} 66 67// cc_fuzz creates a host/device fuzzer binary. Host binaries can be found at 68// $ANDROID_HOST_OUT/fuzz/, and device binaries can be found at /data/fuzz on 69// your device, or $ANDROID_PRODUCT_OUT/data/fuzz in your build tree. 70func FuzzFactory() android.Module { 71 module := NewFuzz(android.HostAndDeviceSupported) 72 return module.Init() 73} 74 75func NewFuzzInstaller() *baseInstaller { 76 return NewBaseInstaller("fuzz", "fuzz", InstallInData) 77} 78 79type fuzzBinary struct { 80 *binaryDecorator 81 *baseCompiler 82 83 Properties FuzzProperties 84 dictionary android.Path 85 corpus android.Paths 86 corpusIntermediateDir android.Path 87 config android.Path 88 data android.Paths 89 dataIntermediateDir android.Path 90 installedSharedDeps []string 91} 92 93func (fuzz *fuzzBinary) linkerProps() []interface{} { 94 props := fuzz.binaryDecorator.linkerProps() 95 props = append(props, &fuzz.Properties) 96 return props 97} 98 99func (fuzz *fuzzBinary) linkerInit(ctx BaseModuleContext) { 100 fuzz.binaryDecorator.linkerInit(ctx) 101} 102 103func (fuzz *fuzzBinary) linkerDeps(ctx DepsContext, deps Deps) Deps { 104 deps.StaticLibs = append(deps.StaticLibs, 105 config.LibFuzzerRuntimeLibrary(ctx.toolchain())) 106 deps = fuzz.binaryDecorator.linkerDeps(ctx, deps) 107 return deps 108} 109 110func (fuzz *fuzzBinary) linkerFlags(ctx ModuleContext, flags Flags) Flags { 111 flags = fuzz.binaryDecorator.linkerFlags(ctx, flags) 112 // RunPaths on devices isn't instantiated by the base linker. `../lib` for 113 // installed fuzz targets (both host and device), and `./lib` for fuzz 114 // target packages. 115 flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN/../lib`) 116 flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN/lib`) 117 return flags 118} 119 120// This function performs a breadth-first search over the provided module's 121// dependencies using `visitDirectDeps` to enumerate all shared library 122// dependencies. We require breadth-first expansion, as otherwise we may 123// incorrectly use the core libraries (sanitizer runtimes, libc, libdl, etc.) 124// from a dependency. This may cause issues when dependencies have explicit 125// sanitizer tags, as we may get a dependency on an unsanitized libc, etc. 126func collectAllSharedDependencies(ctx android.SingletonContext, module android.Module) android.Paths { 127 var fringe []android.Module 128 129 seen := make(map[android.Module]bool) 130 131 // Enumerate the first level of dependencies, as we discard all non-library 132 // modules in the BFS loop below. 133 ctx.VisitDirectDeps(module, func(dep android.Module) { 134 if isValidSharedDependency(dep) { 135 fringe = append(fringe, dep) 136 } 137 }) 138 139 var sharedLibraries android.Paths 140 141 for i := 0; i < len(fringe); i++ { 142 module := fringe[i] 143 if seen[module] { 144 continue 145 } 146 seen[module] = true 147 148 ccModule := module.(*Module) 149 sharedLibraries = append(sharedLibraries, ccModule.UnstrippedOutputFile()) 150 ctx.VisitDirectDeps(module, func(dep android.Module) { 151 if isValidSharedDependency(dep) && !seen[dep] { 152 fringe = append(fringe, dep) 153 } 154 }) 155 } 156 157 return sharedLibraries 158} 159 160// This function takes a module and determines if it is a unique shared library 161// that should be installed in the fuzz target output directories. This function 162// returns true, unless: 163// - The module is not a shared library, or 164// - The module is a header, stub, or vendor-linked library. 165func isValidSharedDependency(dependency android.Module) bool { 166 // TODO(b/144090547): We should be parsing these modules using 167 // ModuleDependencyTag instead of the current brute-force checking. 168 169 if linkable, ok := dependency.(LinkableInterface); !ok || // Discard non-linkables. 170 !linkable.CcLibraryInterface() || !linkable.Shared() || // Discard static libs. 171 linkable.UseVndk() || // Discard vendor linked libraries. 172 // Discard stubs libs (only CCLibrary variants). Prebuilt libraries should not 173 // be excluded on the basis of they're not CCLibrary()'s. 174 (linkable.CcLibrary() && linkable.BuildStubs()) { 175 return false 176 } 177 178 // We discarded module stubs libraries above, but the LLNDK prebuilts stubs 179 // libraries must be handled differently - by looking for the stubDecorator. 180 // Discard LLNDK prebuilts stubs as well. 181 if ccLibrary, isCcLibrary := dependency.(*Module); isCcLibrary { 182 if _, isLLndkStubLibrary := ccLibrary.linker.(*stubDecorator); isLLndkStubLibrary { 183 return false 184 } 185 } 186 187 return true 188} 189 190func sharedLibraryInstallLocation( 191 libraryPath android.Path, isHost bool, archString string) string { 192 installLocation := "$(PRODUCT_OUT)/data" 193 if isHost { 194 installLocation = "$(HOST_OUT)" 195 } 196 installLocation = filepath.Join( 197 installLocation, "fuzz", archString, "lib", libraryPath.Base()) 198 return installLocation 199} 200 201func (fuzz *fuzzBinary) install(ctx ModuleContext, file android.Path) { 202 fuzz.binaryDecorator.baseInstaller.dir = filepath.Join( 203 "fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName()) 204 fuzz.binaryDecorator.baseInstaller.dir64 = filepath.Join( 205 "fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName()) 206 fuzz.binaryDecorator.baseInstaller.install(ctx, file) 207 208 fuzz.corpus = android.PathsForModuleSrc(ctx, fuzz.Properties.Corpus) 209 builder := android.NewRuleBuilder() 210 intermediateDir := android.PathForModuleOut(ctx, "corpus") 211 for _, entry := range fuzz.corpus { 212 builder.Command().Text("cp"). 213 Input(entry). 214 Output(intermediateDir.Join(ctx, entry.Base())) 215 } 216 builder.Build(pctx, ctx, "copy_corpus", "copy corpus") 217 fuzz.corpusIntermediateDir = intermediateDir 218 219 fuzz.data = android.PathsForModuleSrc(ctx, fuzz.Properties.Data) 220 builder = android.NewRuleBuilder() 221 intermediateDir = android.PathForModuleOut(ctx, "data") 222 for _, entry := range fuzz.data { 223 builder.Command().Text("cp"). 224 Input(entry). 225 Output(intermediateDir.Join(ctx, entry.Rel())) 226 } 227 builder.Build(pctx, ctx, "copy_data", "copy data") 228 fuzz.dataIntermediateDir = intermediateDir 229 230 if fuzz.Properties.Dictionary != nil { 231 fuzz.dictionary = android.PathForModuleSrc(ctx, *fuzz.Properties.Dictionary) 232 if fuzz.dictionary.Ext() != ".dict" { 233 ctx.PropertyErrorf("dictionary", 234 "Fuzzer dictionary %q does not have '.dict' extension", 235 fuzz.dictionary.String()) 236 } 237 } 238 239 if fuzz.Properties.Fuzz_config != nil { 240 configPath := android.PathForModuleOut(ctx, "config").Join(ctx, "config.json") 241 ctx.Build(pctx, android.BuildParams{ 242 Rule: android.WriteFile, 243 Description: "fuzzer infrastructure configuration", 244 Output: configPath, 245 Args: map[string]string{ 246 "content": fuzz.Properties.Fuzz_config.String(), 247 }, 248 }) 249 fuzz.config = configPath 250 } 251 252 // Grab the list of required shared libraries. 253 seen := make(map[android.Module]bool) 254 var sharedLibraries android.Paths 255 ctx.WalkDeps(func(child, parent android.Module) bool { 256 if seen[child] { 257 return false 258 } 259 seen[child] = true 260 261 if isValidSharedDependency(child) { 262 sharedLibraries = append(sharedLibraries, child.(*Module).UnstrippedOutputFile()) 263 return true 264 } 265 return false 266 }) 267 268 for _, lib := range sharedLibraries { 269 fuzz.installedSharedDeps = append(fuzz.installedSharedDeps, 270 sharedLibraryInstallLocation( 271 lib, ctx.Host(), ctx.Arch().ArchType.String())) 272 } 273} 274 275func NewFuzz(hod android.HostOrDeviceSupported) *Module { 276 module, binary := NewBinary(hod) 277 278 binary.baseInstaller = NewFuzzInstaller() 279 module.sanitize.SetSanitizer(fuzzer, true) 280 281 fuzz := &fuzzBinary{ 282 binaryDecorator: binary, 283 baseCompiler: NewBaseCompiler(), 284 } 285 module.compiler = fuzz 286 module.linker = fuzz 287 module.installer = fuzz 288 289 // The fuzzer runtime is not present for darwin host modules, disable cc_fuzz modules when targeting darwin. 290 android.AddLoadHook(module, func(ctx android.LoadHookContext) { 291 disableDarwinAndLinuxBionic := struct { 292 Target struct { 293 Darwin struct { 294 Enabled *bool 295 } 296 Linux_bionic struct { 297 Enabled *bool 298 } 299 } 300 }{} 301 disableDarwinAndLinuxBionic.Target.Darwin.Enabled = BoolPtr(false) 302 disableDarwinAndLinuxBionic.Target.Linux_bionic.Enabled = BoolPtr(false) 303 ctx.AppendProperties(&disableDarwinAndLinuxBionic) 304 }) 305 306 return module 307} 308 309// Responsible for generating GNU Make rules that package fuzz targets into 310// their architecture & target/host specific zip file. 311type fuzzPackager struct { 312 packages android.Paths 313 sharedLibInstallStrings []string 314 fuzzTargets map[string]bool 315} 316 317func fuzzPackagingFactory() android.Singleton { 318 return &fuzzPackager{} 319} 320 321type fileToZip struct { 322 SourceFilePath android.Path 323 DestinationPathPrefix string 324} 325 326type archOs struct { 327 hostOrTarget string 328 arch string 329 dir string 330} 331 332func (s *fuzzPackager) GenerateBuildActions(ctx android.SingletonContext) { 333 // Map between each architecture + host/device combination, and the files that 334 // need to be packaged (in the tuple of {source file, destination folder in 335 // archive}). 336 archDirs := make(map[archOs][]fileToZip) 337 338 // Map tracking whether each shared library has an install rule to avoid duplicate install rules from 339 // multiple fuzzers that depend on the same shared library. 340 sharedLibraryInstalled := make(map[string]bool) 341 342 // List of individual fuzz targets, so that 'make fuzz' also installs the targets 343 // to the correct output directories as well. 344 s.fuzzTargets = make(map[string]bool) 345 346 ctx.VisitAllModules(func(module android.Module) { 347 // Discard non-fuzz targets. 348 ccModule, ok := module.(*Module) 349 if !ok { 350 return 351 } 352 353 fuzzModule, ok := ccModule.compiler.(*fuzzBinary) 354 if !ok { 355 return 356 } 357 358 // Discard vendor-NDK-linked + ramdisk + recovery modules, they're duplicates of 359 // fuzz targets we're going to package anyway. 360 if !ccModule.Enabled() || ccModule.Properties.PreventInstall || 361 ccModule.UseVndk() || ccModule.InRamdisk() || ccModule.InRecovery() { 362 return 363 } 364 365 // Discard modules that are in an unavailable namespace. 366 if !ccModule.ExportedToMake() { 367 return 368 } 369 370 s.fuzzTargets[module.Name()] = true 371 372 hostOrTargetString := "target" 373 if ccModule.Host() { 374 hostOrTargetString = "host" 375 } 376 377 archString := ccModule.Arch().ArchType.String() 378 archDir := android.PathForIntermediates(ctx, "fuzz", hostOrTargetString, archString) 379 archOs := archOs{hostOrTarget: hostOrTargetString, arch: archString, dir: archDir.String()} 380 381 // Grab the list of required shared libraries. 382 sharedLibraries := collectAllSharedDependencies(ctx, module) 383 384 var files []fileToZip 385 builder := android.NewRuleBuilder() 386 387 // Package the corpora into a zipfile. 388 if fuzzModule.corpus != nil { 389 corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip") 390 command := builder.Command().BuiltTool(ctx, "soong_zip"). 391 Flag("-j"). 392 FlagWithOutput("-o ", corpusZip) 393 command.FlagWithRspFileInputList("-l ", fuzzModule.corpus) 394 files = append(files, fileToZip{corpusZip, ""}) 395 } 396 397 // Package the data into a zipfile. 398 if fuzzModule.data != nil { 399 dataZip := archDir.Join(ctx, module.Name()+"_data.zip") 400 command := builder.Command().BuiltTool(ctx, "soong_zip"). 401 FlagWithOutput("-o ", dataZip) 402 for _, f := range fuzzModule.data { 403 intermediateDir := strings.TrimSuffix(f.String(), f.Rel()) 404 command.FlagWithArg("-C ", intermediateDir) 405 command.FlagWithInput("-f ", f) 406 } 407 files = append(files, fileToZip{dataZip, ""}) 408 } 409 410 // Find and mark all the transiently-dependent shared libraries for 411 // packaging. 412 for _, library := range sharedLibraries { 413 files = append(files, fileToZip{library, "lib"}) 414 415 // For each architecture-specific shared library dependency, we need to 416 // install it to the output directory. Setup the install destination here, 417 // which will be used by $(copy-many-files) in the Make backend. 418 installDestination := sharedLibraryInstallLocation( 419 library, ccModule.Host(), archString) 420 if sharedLibraryInstalled[installDestination] { 421 continue 422 } 423 sharedLibraryInstalled[installDestination] = true 424 // Escape all the variables, as the install destination here will be called 425 // via. $(eval) in Make. 426 installDestination = strings.ReplaceAll( 427 installDestination, "$", "$$") 428 s.sharedLibInstallStrings = append(s.sharedLibInstallStrings, 429 library.String()+":"+installDestination) 430 } 431 432 // The executable. 433 files = append(files, fileToZip{ccModule.UnstrippedOutputFile(), ""}) 434 435 // The dictionary. 436 if fuzzModule.dictionary != nil { 437 files = append(files, fileToZip{fuzzModule.dictionary, ""}) 438 } 439 440 // Additional fuzz config. 441 if fuzzModule.config != nil { 442 files = append(files, fileToZip{fuzzModule.config, ""}) 443 } 444 445 fuzzZip := archDir.Join(ctx, module.Name()+".zip") 446 command := builder.Command().BuiltTool(ctx, "soong_zip"). 447 Flag("-j"). 448 FlagWithOutput("-o ", fuzzZip) 449 for _, file := range files { 450 if file.DestinationPathPrefix != "" { 451 command.FlagWithArg("-P ", file.DestinationPathPrefix) 452 } else { 453 command.Flag("-P ''") 454 } 455 command.FlagWithInput("-f ", file.SourceFilePath) 456 } 457 458 builder.Build(pctx, ctx, "create-"+fuzzZip.String(), 459 "Package "+module.Name()+" for "+archString+"-"+hostOrTargetString) 460 461 archDirs[archOs] = append(archDirs[archOs], fileToZip{fuzzZip, ""}) 462 }) 463 464 var archOsList []archOs 465 for archOs := range archDirs { 466 archOsList = append(archOsList, archOs) 467 } 468 sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].dir < archOsList[j].dir }) 469 470 for _, archOs := range archOsList { 471 filesToZip := archDirs[archOs] 472 arch := archOs.arch 473 hostOrTarget := archOs.hostOrTarget 474 builder := android.NewRuleBuilder() 475 outputFile := android.PathForOutput(ctx, "fuzz-"+hostOrTarget+"-"+arch+".zip") 476 s.packages = append(s.packages, outputFile) 477 478 command := builder.Command().BuiltTool(ctx, "soong_zip"). 479 Flag("-j"). 480 FlagWithOutput("-o ", outputFile). 481 Flag("-L 0") // No need to try and re-compress the zipfiles. 482 483 for _, fileToZip := range filesToZip { 484 if fileToZip.DestinationPathPrefix != "" { 485 command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix) 486 } else { 487 command.Flag("-P ''") 488 } 489 command.FlagWithInput("-f ", fileToZip.SourceFilePath) 490 } 491 492 builder.Build(pctx, ctx, "create-fuzz-package-"+arch+"-"+hostOrTarget, 493 "Create fuzz target packages for "+arch+"-"+hostOrTarget) 494 } 495} 496 497func (s *fuzzPackager) MakeVars(ctx android.MakeVarsContext) { 498 packages := s.packages.Strings() 499 sort.Strings(packages) 500 sort.Strings(s.sharedLibInstallStrings) 501 // TODO(mitchp): Migrate this to use MakeVarsContext::DistForGoal() when it's 502 // ready to handle phony targets created in Soong. In the meantime, this 503 // exports the phony 'fuzz' target and dependencies on packages to 504 // core/main.mk so that we can use dist-for-goals. 505 ctx.Strict("SOONG_FUZZ_PACKAGING_ARCH_MODULES", strings.Join(packages, " ")) 506 ctx.Strict("FUZZ_TARGET_SHARED_DEPS_INSTALL_PAIRS", 507 strings.Join(s.sharedLibInstallStrings, " ")) 508 509 // Preallocate the slice of fuzz targets to minimise memory allocations. 510 fuzzTargets := make([]string, 0, len(s.fuzzTargets)) 511 for target, _ := range s.fuzzTargets { 512 fuzzTargets = append(fuzzTargets, target) 513 } 514 sort.Strings(fuzzTargets) 515 ctx.Strict("ALL_FUZZ_TARGETS", strings.Join(fuzzTargets, " ")) 516} 517