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