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 "fmt" 22 "sort" 23 "strings" 24 25 "github.com/google/blueprint/proptools" 26 27 "android/soong/android" 28) 29 30type Lang string 31 32const ( 33 Cc Lang = "cc" 34 Rust Lang = "rust" 35 Java Lang = "java" 36) 37 38type Framework string 39 40const ( 41 AFL Framework = "afl" 42 LibFuzzer Framework = "libfuzzer" 43 Jazzer Framework = "jazzer" 44 UnknownFramework Framework = "unknownframework" 45) 46 47func (f Framework) Variant() string { 48 switch f { 49 case AFL: 50 return "afl" 51 case LibFuzzer: 52 return "libfuzzer" 53 case Jazzer: 54 return "jazzer" 55 default: 56 panic(fmt.Errorf("unknown fuzzer %q when getting variant", f)) 57 } 58} 59 60func FrameworkFromVariant(v string) Framework { 61 switch v { 62 case "afl": 63 return AFL 64 case "libfuzzer": 65 return LibFuzzer 66 case "jazzer": 67 return Jazzer 68 default: 69 panic(fmt.Errorf("unknown variant %q when getting fuzzer", v)) 70 } 71} 72 73var BoolDefault = proptools.BoolDefault 74 75type FuzzModule struct { 76 android.ModuleBase 77 android.DefaultableModuleBase 78 android.ApexModuleBase 79} 80 81type FuzzPackager struct { 82 Packages android.Paths 83 FuzzTargets map[string]bool 84 SharedLibInstallStrings []string 85} 86 87type FileToZip struct { 88 SourceFilePath android.Path 89 DestinationPathPrefix string 90 DestinationPath string 91} 92 93type ArchOs struct { 94 HostOrTarget string 95 Arch string 96 Dir string 97} 98 99type Vector string 100 101const ( 102 unknown_access_vector Vector = "unknown_access_vector" 103 // The code being fuzzed is reachable from a remote source, or using data 104 // provided by a remote source. For example: media codecs process media files 105 // from the internet, SMS processing handles remote message data. 106 // See 107 // https://source.android.com/docs/security/overview/updates-resources#local-vs-remote 108 // for an explanation of what's considered "remote." 109 remote = "remote" 110 // The code being fuzzed can only be reached locally, such as from an 111 // installed app. As an example, if it's fuzzing a Binder interface, it's 112 // assumed that you'd need a local app to make arbitrary Binder calls. 113 // And the app that's calling the fuzzed code does not require any privileges; 114 // any 3rd party app could make these calls. 115 local_no_privileges_required = "local_no_privileges_required" 116 // The code being fuzzed can only be called locally, and the calling process 117 // requires additional permissions that prevent arbitrary 3rd party apps from 118 // calling the code. For instance: this requires a privileged or signature 119 // permission to reach, or SELinux restrictions prevent the untrusted_app 120 // domain from calling it. 121 local_privileges_required = "local_privileges_required" 122 // The code is only callable on a PC host, not on a production Android device. 123 // For instance, this is fuzzing code used during the build process, or 124 // tooling that does not exist on a user's actual Android device. 125 host_access = "host_access" 126 // The code being fuzzed is only reachable if the user has enabled Developer 127 // Options, or has enabled a persistent Developer Options setting. 128 local_with_developer_options = "local_with_developer_options" 129) 130 131func (vector Vector) isValidVector() bool { 132 switch vector { 133 case "", 134 unknown_access_vector, 135 remote, 136 local_no_privileges_required, 137 local_privileges_required, 138 host_access, 139 local_with_developer_options: 140 return true 141 } 142 return false 143} 144 145type ServicePrivilege string 146 147const ( 148 unknown_service_privilege ServicePrivilege = "unknown_service_privilege" 149 // The code being fuzzed runs on a Secure Element. This has access to some 150 // of the most privileged data on the device, such as authentication keys. 151 // Not all devices have a Secure Element. 152 secure_element = "secure_element" 153 // The code being fuzzed runs in the TEE. The TEE is designed to be resistant 154 // to a compromised kernel, and stores sensitive data. 155 trusted_execution = "trusted_execution" 156 // The code being fuzzed has privileges beyond what arbitrary 3rd party apps 157 // have. For instance, it's running as the System UID, or it's in an SELinux 158 // domain that's able to perform calls that can't be made by 3rd party apps. 159 privileged = "privileged" 160 // The code being fuzzed is equivalent to a 3rd party app. It runs in the 161 // untrusted_app SELinux domain, or it only has privileges that are equivalent 162 // to what a 3rd party app could have. 163 unprivileged = "unprivileged" 164 // The code being fuzzed is significantly constrained, and even if it's 165 // compromised, it has significant restrictions that prevent it from 166 // performing most actions. This is significantly more restricted than 167 // UNPRIVILEGED. An example is the isolatedProcess=true setting in a 3rd 168 // party app. Or a process that's very restricted by SELinux, such as 169 // anything in the mediacodec SELinux domain. 170 constrained = "constrained" 171 // The code being fuzzed always has Negligible Security Impact. Even 172 // arbitrary out of bounds writes and full code execution would not be 173 // considered a security vulnerability. This typically only makes sense if 174 // FuzzedCodeUsage is set to FUTURE_VERSION or EXPERIMENTAL, and if 175 // AutomaticallyRouteTo is set to ALWAYS_NSI. 176 nsi = "nsi" 177 // The code being fuzzed only runs on a PC host, not on a production Android 178 // device. For instance, the fuzzer is fuzzing code used during the build 179 // process, or tooling that does not exist on a user's actual Android device. 180 host_only = "host_only" 181) 182 183func (service_privilege ServicePrivilege) isValidServicePrivilege() bool { 184 switch service_privilege { 185 case "", 186 unknown_service_privilege, 187 secure_element, 188 trusted_execution, 189 privileged, 190 unprivileged, 191 constrained, 192 nsi, 193 host_only: 194 return true 195 } 196 return false 197} 198 199type UsePlatformLibs string 200 201const ( 202 unknown_use_platform_libs UsePlatformLibs = "unknown_use_platform_libs" 203 // Use the native libraries on the device, typically in /system directory 204 use_platform_libs = "use_platform_libs" 205 // Do not use any native libraries (ART will not be initialized) 206 use_none = "use_none" 207) 208 209func (use_platform_libs UsePlatformLibs) isValidUsePlatformLibs() bool { 210 switch use_platform_libs { 211 case "", 212 unknown_use_platform_libs, 213 use_platform_libs, 214 use_none: 215 return true 216 } 217 return false 218} 219 220type UserData string 221 222const ( 223 unknown_user_data UserData = "unknown_user_data" 224 // The process being fuzzed only handles data from a single user, or from a 225 // single process or app. It's possible the process shuts down before 226 // handling data from another user/process/app, or it's possible the process 227 // only ever handles one user's/process's/app's data. As an example, some 228 // print spooler processes are started for a single document and terminate 229 // when done, so each instance only handles data from a single user/app. 230 single_user = "single_user" 231 // The process handles data from multiple users, or from multiple other apps 232 // or processes. Media processes, for instance, can handle media requests 233 // from multiple different apps without restarting. Wi-Fi and network 234 // processes handle data from multiple users, and processes, and apps. 235 multi_user = "multi_user" 236) 237 238func (user_data UserData) isValidUserData() bool { 239 switch user_data { 240 case "", 241 unknown_user_data, 242 single_user, 243 multi_user: 244 return true 245 } 246 return false 247} 248 249type FuzzedCodeUsage string 250 251const ( 252 undefined FuzzedCodeUsage = "undefined" 253 unknown = "unknown" 254 // The code being fuzzed exists in a shipped version of Android and runs on 255 // devices in production. 256 shipped = "shipped" 257 // The code being fuzzed is not yet in a shipping version of Android, but it 258 // will be at some point in the future. 259 future_version = "future_version" 260 // The code being fuzzed is not in a shipping version of Android, and there 261 // are no plans to ship it in the future. 262 experimental = "experimental" 263) 264 265func (fuzzed_code_usage FuzzedCodeUsage) isValidFuzzedCodeUsage() bool { 266 switch fuzzed_code_usage { 267 case "", 268 undefined, 269 unknown, 270 shipped, 271 future_version, 272 experimental: 273 return true 274 } 275 return false 276} 277 278type AutomaticallyRouteTo string 279 280const ( 281 undefined_routing AutomaticallyRouteTo = "undefined_routing" 282 // Automatically route this to the Android Automotive security team for 283 // assessment. 284 android_automotive = "android_automotive" 285 // This should not be used in fuzzer configurations. It is used internally 286 // by Severity Assigner to flag memory leak reports. 287 memory_leak = "memory_leak" 288 // Route this vulnerability to our Ittiam vendor team for assessment. 289 ittiam = "ittiam" 290 // Reports from this fuzzer are always NSI (see the NSI ServicePrivilegeEnum 291 // value for additional context). It is not possible for this code to ever 292 // have a security vulnerability. 293 always_nsi = "always_nsi" 294 // Route this vulnerability to AIDL team for assessment. 295 aidl = "aidl" 296) 297 298func (automatically_route_to AutomaticallyRouteTo) isValidAutomaticallyRouteTo() bool { 299 switch automatically_route_to { 300 case "", 301 undefined_routing, 302 android_automotive, 303 memory_leak, 304 ittiam, 305 always_nsi, 306 aidl: 307 return true 308 } 309 return false 310} 311 312func IsValidConfig(fuzzModule FuzzPackagedModule, moduleName string) bool { 313 var config = fuzzModule.FuzzProperties.Fuzz_config 314 if config != nil { 315 if !config.Vector.isValidVector() { 316 panic(fmt.Errorf("Invalid vector in fuzz config in %s", moduleName)) 317 } 318 319 if !config.Service_privilege.isValidServicePrivilege() { 320 panic(fmt.Errorf("Invalid service_privilege in fuzz config in %s", moduleName)) 321 } 322 323 if !config.Users.isValidUserData() { 324 panic(fmt.Errorf("Invalid users (user_data) in fuzz config in %s", moduleName)) 325 } 326 327 if !config.Fuzzed_code_usage.isValidFuzzedCodeUsage() { 328 panic(fmt.Errorf("Invalid fuzzed_code_usage in fuzz config in %s", moduleName)) 329 } 330 331 if !config.Automatically_route_to.isValidAutomaticallyRouteTo() { 332 panic(fmt.Errorf("Invalid automatically_route_to in fuzz config in %s", moduleName)) 333 } 334 335 if !config.Use_platform_libs.isValidUsePlatformLibs() { 336 panic(fmt.Errorf("Invalid use_platform_libs in fuzz config in %s", moduleName)) 337 } 338 } 339 return true 340} 341 342type FuzzConfig struct { 343 // Email address of people to CC on bugs or contact about this fuzz target. 344 Cc []string `json:"cc,omitempty"` 345 // A brief description of what the fuzzed code does. 346 Description string `json:"description,omitempty"` 347 // Whether the code being fuzzed is remotely accessible or requires privileges 348 // to access locally. 349 Vector Vector `json:"vector,omitempty"` 350 // How privileged the service being fuzzed is. 351 Service_privilege ServicePrivilege `json:"service_privilege,omitempty"` 352 // Whether the service being fuzzed handles data from multiple users or only 353 // a single one. 354 Users UserData `json:"users,omitempty"` 355 // Specifies the use state of the code being fuzzed. This state factors into 356 // how an issue is handled. 357 Fuzzed_code_usage FuzzedCodeUsage `json:"fuzzed_code_usage,omitempty"` 358 // Comment describing how we came to these settings for this fuzzer. 359 Config_comment string 360 // Which team to route this to, if it should be routed automatically. 361 Automatically_route_to AutomaticallyRouteTo `json:"automatically_route_to,omitempty"` 362 // Can third party/untrusted apps supply data to fuzzed code. 363 Untrusted_data *bool `json:"untrusted_data,omitempty"` 364 // When code was released or will be released. 365 Production_date string `json:"production_date,omitempty"` 366 // Prevents critical service functionality like phone calls, bluetooth, etc. 367 Critical *bool `json:"critical,omitempty"` 368 // Specify whether to enable continuous fuzzing on devices. Defaults to true. 369 Fuzz_on_haiku_device *bool `json:"fuzz_on_haiku_device,omitempty"` 370 // Specify whether to enable continuous fuzzing on host. Defaults to true. 371 Fuzz_on_haiku_host *bool `json:"fuzz_on_haiku_host,omitempty"` 372 // Component in Google's bug tracking system that bugs should be filed to. 373 Componentid *int64 `json:"componentid,omitempty"` 374 // Hotlist(s) in Google's bug tracking system that bugs should be marked with. 375 Hotlists []string `json:"hotlists,omitempty"` 376 // Specify whether this fuzz target was submitted by a researcher. Defaults 377 // to false. 378 Researcher_submitted *bool `json:"researcher_submitted,omitempty"` 379 // Specify who should be acknowledged for CVEs in the Android Security 380 // Bulletin. 381 Acknowledgement []string `json:"acknowledgement,omitempty"` 382 // Additional options to be passed to libfuzzer when run in Haiku. 383 Libfuzzer_options []string `json:"libfuzzer_options,omitempty"` 384 // Additional options to be passed to HWASAN when running on-device in Haiku. 385 Hwasan_options []string `json:"hwasan_options,omitempty"` 386 // Additional options to be passed to HWASAN when running on host in Haiku. 387 Asan_options []string `json:"asan_options,omitempty"` 388 // If there's a Java fuzzer with JNI, a different version of Jazzer would 389 // need to be added to the fuzzer package than one without JNI 390 IsJni *bool `json:"is_jni,omitempty"` 391 // List of modules for monitoring coverage drops in directories (e.g. "libicu") 392 Target_modules []string `json:"target_modules,omitempty"` 393 // Specifies a bug assignee to replace default ISE assignment 394 Triage_assignee string `json:"triage_assignee,omitempty"` 395 // Specifies libs used to initialize ART (java only, 'use_none' for no initialization) 396 Use_platform_libs UsePlatformLibs `json:"use_platform_libs,omitempty"` 397 // Specifies whether fuzz target should check presubmitted code changes for crashes. 398 // Defaults to false. 399 Use_for_presubmit *bool `json:"use_for_presubmit,omitempty"` 400 // Specify which paths to exclude from fuzzing coverage reports 401 Exclude_paths_from_reports []string `json:"exclude_paths_from_reports,omitempty"` 402} 403 404type FuzzFrameworks struct { 405 Afl *bool 406 Libfuzzer *bool 407 Jazzer *bool 408} 409 410type FuzzProperties struct { 411 // Optional list of seed files to be installed to the fuzz target's output 412 // directory. 413 Corpus []string `android:"path"` 414 415 // Same as corpus, but adds dependencies on module references using the device's os variant 416 // and the common arch variant. 417 Device_common_corpus []string `android:"path_device_common"` 418 419 // Optional list of data files to be installed to the fuzz target's output 420 // directory. Directory structure relative to the module is preserved. 421 Data []string `android:"path"` 422 // Optional dictionary to be installed to the fuzz target's output directory. 423 Dictionary *string `android:"path"` 424 // Define the fuzzing frameworks this fuzz target can be built for. If 425 // empty then the fuzz target will be available to be built for all fuzz 426 // frameworks available 427 Fuzzing_frameworks *FuzzFrameworks 428 // Config for running the target on fuzzing infrastructure. 429 Fuzz_config *FuzzConfig 430} 431 432type FuzzPackagedModule struct { 433 FuzzProperties FuzzProperties 434 Dictionary android.Path 435 Corpus android.Paths 436 Config android.Path 437 Data android.Paths 438} 439 440func GetFramework(ctx android.LoadHookContext, lang Lang) Framework { 441 framework := ctx.Config().Getenv("FUZZ_FRAMEWORK") 442 443 if lang == Cc { 444 switch strings.ToLower(framework) { 445 case "": 446 return LibFuzzer 447 case "libfuzzer": 448 return LibFuzzer 449 case "afl": 450 return AFL 451 } 452 } else if lang == Rust { 453 return LibFuzzer 454 } else if lang == Java { 455 return Jazzer 456 } 457 458 ctx.ModuleErrorf(fmt.Sprintf("%s is not a valid fuzzing framework for %s", framework, lang)) 459 return UnknownFramework 460} 461 462func IsValidFrameworkForModule(targetFramework Framework, lang Lang, moduleFrameworks *FuzzFrameworks) bool { 463 if targetFramework == UnknownFramework { 464 return false 465 } 466 467 if moduleFrameworks == nil { 468 return true 469 } 470 471 switch targetFramework { 472 case LibFuzzer: 473 return proptools.BoolDefault(moduleFrameworks.Libfuzzer, true) 474 case AFL: 475 return proptools.BoolDefault(moduleFrameworks.Afl, true) 476 case Jazzer: 477 return proptools.BoolDefault(moduleFrameworks.Jazzer, true) 478 default: 479 panic("%s is not supported as a fuzz framework") 480 } 481} 482 483func IsValid(ctx android.ConfigurableEvaluatorContext, fuzzModule FuzzModule) bool { 484 // Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of 485 // fuzz targets we're going to package anyway. 486 if !fuzzModule.Enabled(ctx) || fuzzModule.InRamdisk() || fuzzModule.InVendorRamdisk() || fuzzModule.InRecovery() { 487 return false 488 } 489 490 // Discard modules that are in an unavailable namespace. 491 if !fuzzModule.ExportedToMake() { 492 return false 493 } 494 495 return true 496} 497 498func (s *FuzzPackager) PackageArtifacts(ctx android.SingletonContext, module android.Module, fuzzModule FuzzPackagedModule, archDir android.OutputPath, builder *android.RuleBuilder) []FileToZip { 499 // Package the corpora into a zipfile. 500 var files []FileToZip 501 if fuzzModule.Corpus != nil { 502 corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip") 503 command := builder.Command().BuiltTool("soong_zip"). 504 Flag("-j"). 505 FlagWithOutput("-o ", corpusZip) 506 rspFile := corpusZip.ReplaceExtension(ctx, "rsp") 507 command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.Corpus) 508 files = append(files, FileToZip{SourceFilePath: corpusZip}) 509 } 510 511 // Package the data into a zipfile. 512 if fuzzModule.Data != nil { 513 dataZip := archDir.Join(ctx, module.Name()+"_data.zip") 514 command := builder.Command().BuiltTool("soong_zip"). 515 FlagWithOutput("-o ", dataZip) 516 for _, f := range fuzzModule.Data { 517 intermediateDir := strings.TrimSuffix(f.String(), f.Rel()) 518 command.FlagWithArg("-C ", intermediateDir) 519 command.FlagWithInput("-f ", f) 520 } 521 files = append(files, FileToZip{SourceFilePath: dataZip}) 522 } 523 524 // The dictionary. 525 if fuzzModule.Dictionary != nil { 526 files = append(files, FileToZip{SourceFilePath: fuzzModule.Dictionary}) 527 } 528 529 // Additional fuzz config. 530 if fuzzModule.Config != nil && IsValidConfig(fuzzModule, module.Name()) { 531 files = append(files, FileToZip{SourceFilePath: fuzzModule.Config}) 532 } 533 534 return files 535} 536 537func (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) { 538 fuzzZip := archDir.Join(ctx, module.Name()+".zip") 539 540 command := builder.Command().BuiltTool("soong_zip"). 541 Flag("-j"). 542 FlagWithOutput("-o ", fuzzZip) 543 544 for _, file := range files { 545 if file.DestinationPathPrefix != "" { 546 command.FlagWithArg("-P ", file.DestinationPathPrefix) 547 } else { 548 command.Flag("-P ''") 549 } 550 if file.DestinationPath != "" { 551 command.FlagWithArg("-e ", file.DestinationPath) 552 } 553 command.FlagWithInput("-f ", file.SourceFilePath) 554 } 555 556 builder.Build("create-"+fuzzZip.String(), 557 "Package "+module.Name()+" for "+archString+"-"+hostOrTargetString) 558 559 if config := fuzzModule.FuzzProperties.Fuzz_config; config != nil { 560 if strings.Contains(hostOrTargetString, "host") && !BoolDefault(config.Fuzz_on_haiku_host, true) { 561 return archDirs[archOs], false 562 } else if !strings.Contains(hostOrTargetString, "host") && !BoolDefault(config.Fuzz_on_haiku_device, true) { 563 return archDirs[archOs], false 564 } 565 } 566 567 s.FuzzTargets[module.Name()] = true 568 archDirs[archOs] = append(archDirs[archOs], FileToZip{SourceFilePath: fuzzZip}) 569 570 return archDirs[archOs], true 571} 572 573func (f *FuzzConfig) String() string { 574 b, err := json.Marshal(f) 575 if err != nil { 576 panic(err) 577 } 578 579 return string(b) 580} 581 582func (s *FuzzPackager) CreateFuzzPackage(ctx android.SingletonContext, archDirs map[ArchOs][]FileToZip, fuzzType Lang, pctx android.PackageContext) { 583 var archOsList []ArchOs 584 for archOs := range archDirs { 585 archOsList = append(archOsList, archOs) 586 } 587 sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].Dir < archOsList[j].Dir }) 588 589 for _, archOs := range archOsList { 590 filesToZip := archDirs[archOs] 591 arch := archOs.Arch 592 hostOrTarget := archOs.HostOrTarget 593 builder := android.NewRuleBuilder(pctx, ctx) 594 zipFileName := "fuzz-" + hostOrTarget + "-" + arch + ".zip" 595 if fuzzType == Rust { 596 zipFileName = "fuzz-rust-" + hostOrTarget + "-" + arch + ".zip" 597 } 598 if fuzzType == Java { 599 zipFileName = "fuzz-java-" + hostOrTarget + "-" + arch + ".zip" 600 } 601 602 outputFile := android.PathForOutput(ctx, zipFileName) 603 604 s.Packages = append(s.Packages, outputFile) 605 606 command := builder.Command().BuiltTool("soong_zip"). 607 Flag("-j"). 608 FlagWithOutput("-o ", outputFile). 609 Flag("-L 0") // No need to try and re-compress the zipfiles. 610 611 for _, fileToZip := range filesToZip { 612 if fileToZip.DestinationPathPrefix != "" { 613 command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix) 614 } else { 615 command.Flag("-P ''") 616 } 617 command.FlagWithInput("-f ", fileToZip.SourceFilePath) 618 619 } 620 builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget, 621 "Create fuzz target packages for "+arch+"-"+hostOrTarget) 622 } 623} 624 625func (s *FuzzPackager) PreallocateSlice(ctx android.MakeVarsContext, targets string) { 626 fuzzTargets := make([]string, 0, len(s.FuzzTargets)) 627 for target, _ := range s.FuzzTargets { 628 fuzzTargets = append(fuzzTargets, target) 629 } 630 631 sort.Strings(fuzzTargets) 632 ctx.Strict(targets, strings.Join(fuzzTargets, " ")) 633} 634