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