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