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