| // Copyright 2021 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package fuzz |
| |
| // This file contains the common code for compiling C/C++ and Rust fuzzers for Android. |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "sort" |
| "strings" |
| |
| "github.com/google/blueprint/proptools" |
| |
| "android/soong/android" |
| ) |
| |
| type Lang string |
| |
| const ( |
| Cc Lang = "cc" |
| Rust Lang = "rust" |
| Java Lang = "java" |
| ) |
| |
| type Framework string |
| |
| const ( |
| AFL Framework = "afl" |
| LibFuzzer Framework = "libfuzzer" |
| Jazzer Framework = "jazzer" |
| UnknownFramework Framework = "unknownframework" |
| ) |
| |
| var BoolDefault = proptools.BoolDefault |
| |
| type FuzzModule struct { |
| android.ModuleBase |
| android.DefaultableModuleBase |
| android.ApexModuleBase |
| } |
| |
| type FuzzPackager struct { |
| Packages android.Paths |
| FuzzTargets map[string]bool |
| SharedLibInstallStrings []string |
| } |
| |
| type FileToZip struct { |
| SourceFilePath android.Path |
| DestinationPathPrefix string |
| } |
| |
| type ArchOs struct { |
| HostOrTarget string |
| Arch string |
| Dir string |
| } |
| |
| type Vector string |
| |
| const ( |
| unknown_access_vector Vector = "unknown_access_vector" |
| // The code being fuzzed is reachable from a remote source, or using data |
| // provided by a remote source. For example: media codecs process media files |
| // from the internet, SMS processing handles remote message data. |
| // See |
| // https://source.android.com/docs/security/overview/updates-resources#local-vs-remote |
| // for an explanation of what's considered "remote." |
| remote = "remote" |
| // The code being fuzzed can only be reached locally, such as from an |
| // installed app. As an example, if it's fuzzing a Binder interface, it's |
| // assumed that you'd need a local app to make arbitrary Binder calls. |
| // And the app that's calling the fuzzed code does not require any privileges; |
| // any 3rd party app could make these calls. |
| local_no_privileges_required = "local_no_privileges_required" |
| // The code being fuzzed can only be called locally, and the calling process |
| // requires additional permissions that prevent arbitrary 3rd party apps from |
| // calling the code. For instance: this requires a privileged or signature |
| // permission to reach, or SELinux restrictions prevent the untrusted_app |
| // domain from calling it. |
| local_privileges_required = "local_privileges_required" |
| // The code is only callable on a PC host, not on a production Android device. |
| // For instance, this is fuzzing code used during the build process, or |
| // tooling that does not exist on a user's actual Android device. |
| host_access = "host_access" |
| // The code being fuzzed is only reachable if the user has enabled Developer |
| // Options, or has enabled a persistent Developer Options setting. |
| local_with_developer_options = "local_with_developer_options" |
| ) |
| |
| func (vector Vector) isValidVector() bool { |
| switch vector { |
| case "", |
| unknown_access_vector, |
| remote, |
| local_no_privileges_required, |
| local_privileges_required, |
| host_access, |
| local_with_developer_options: |
| return true |
| } |
| return false |
| } |
| |
| type ServicePrivilege string |
| |
| const ( |
| unknown_service_privilege ServicePrivilege = "unknown_service_privilege" |
| // The code being fuzzed runs on a Secure Element. This has access to some |
| // of the most privileged data on the device, such as authentication keys. |
| // Not all devices have a Secure Element. |
| secure_element = "secure_element" |
| // The code being fuzzed runs in the TEE. The TEE is designed to be resistant |
| // to a compromised kernel, and stores sensitive data. |
| trusted_execution = "trusted_execution" |
| // The code being fuzzed has privileges beyond what arbitrary 3rd party apps |
| // have. For instance, it's running as the System UID, or it's in an SELinux |
| // domain that's able to perform calls that can't be made by 3rd party apps. |
| privileged = "privileged" |
| // The code being fuzzed is equivalent to a 3rd party app. It runs in the |
| // untrusted_app SELinux domain, or it only has privileges that are equivalent |
| // to what a 3rd party app could have. |
| unprivileged = "unprivileged" |
| // The code being fuzzed is significantly constrained, and even if it's |
| // compromised, it has significant restrictions that prevent it from |
| // performing most actions. This is significantly more restricted than |
| // UNPRIVILEGED. An example is the isolatedProcess=true setting in a 3rd |
| // party app. Or a process that's very restricted by SELinux, such as |
| // anything in the mediacodec SELinux domain. |
| constrained = "constrained" |
| // The code being fuzzed always has Negligible Security Impact. Even |
| // arbitrary out of bounds writes and full code execution would not be |
| // considered a security vulnerability. This typically only makes sense if |
| // FuzzedCodeUsage is set to FUTURE_VERSION or EXPERIMENTAL, and if |
| // AutomaticallyRouteTo is set to ALWAYS_NSI. |
| nsi = "nsi" |
| // The code being fuzzed only runs on a PC host, not on a production Android |
| // device. For instance, the fuzzer is fuzzing code used during the build |
| // process, or tooling that does not exist on a user's actual Android device. |
| host_only = "host_only" |
| ) |
| |
| func (service_privilege ServicePrivilege) isValidServicePrivilege() bool { |
| switch service_privilege { |
| case "", |
| unknown_service_privilege, |
| secure_element, |
| trusted_execution, |
| privileged, |
| unprivileged, |
| constrained, |
| nsi, |
| host_only: |
| return true |
| } |
| return false |
| } |
| |
| type UserData string |
| |
| const ( |
| unknown_user_data UserData = "unknown_user_data" |
| // The process being fuzzed only handles data from a single user, or from a |
| // single process or app. It's possible the process shuts down before |
| // handling data from another user/process/app, or it's possible the process |
| // only ever handles one user's/process's/app's data. As an example, some |
| // print spooler processes are started for a single document and terminate |
| // when done, so each instance only handles data from a single user/app. |
| single_user = "single_user" |
| // The process handles data from multiple users, or from multiple other apps |
| // or processes. Media processes, for instance, can handle media requests |
| // from multiple different apps without restarting. Wi-Fi and network |
| // processes handle data from multiple users, and processes, and apps. |
| multi_user = "multi_user" |
| ) |
| |
| func (user_data UserData) isValidUserData() bool { |
| switch user_data { |
| case "", |
| unknown_user_data, |
| single_user, |
| multi_user: |
| return true |
| } |
| return false |
| } |
| |
| type FuzzedCodeUsage string |
| |
| const ( |
| undefined FuzzedCodeUsage = "undefined" |
| unknown = "unknown" |
| // The code being fuzzed exists in a shipped version of Android and runs on |
| // devices in production. |
| shipped = "shipped" |
| // The code being fuzzed is not yet in a shipping version of Android, but it |
| // will be at some point in the future. |
| future_version = "future_version" |
| // The code being fuzzed is not in a shipping version of Android, and there |
| // are no plans to ship it in the future. |
| experimental = "experimental" |
| ) |
| |
| func (fuzzed_code_usage FuzzedCodeUsage) isValidFuzzedCodeUsage() bool { |
| switch fuzzed_code_usage { |
| case "", |
| undefined, |
| unknown, |
| shipped, |
| future_version, |
| experimental: |
| return true |
| } |
| return false |
| } |
| |
| type AutomaticallyRouteTo string |
| |
| const ( |
| undefined_routing AutomaticallyRouteTo = "undefined_routing" |
| // Automatically route this to the Android Automotive security team for |
| // assessment. |
| android_automotive = "android_automotive" |
| // This should not be used in fuzzer configurations. It is used internally |
| // by Severity Assigner to flag memory leak reports. |
| memory_leak = "memory_leak" |
| // Route this vulnerability to our Ittiam vendor team for assessment. |
| ittiam = "ittiam" |
| // Reports from this fuzzer are always NSI (see the NSI ServicePrivilegeEnum |
| // value for additional context). It is not possible for this code to ever |
| // have a security vulnerability. |
| always_nsi = "always_nsi" |
| // Route this vulnerability to AIDL team for assessment. |
| aidl = "aidl" |
| ) |
| |
| func (automatically_route_to AutomaticallyRouteTo) isValidAutomaticallyRouteTo() bool { |
| switch automatically_route_to { |
| case "", |
| undefined_routing, |
| android_automotive, |
| memory_leak, |
| ittiam, |
| always_nsi, |
| aidl: |
| return true |
| } |
| return false |
| } |
| |
| func IsValidConfig(fuzzModule FuzzPackagedModule, moduleName string) bool { |
| var config = fuzzModule.FuzzProperties.Fuzz_config |
| if config != nil { |
| if !config.Vector.isValidVector() { |
| panic(fmt.Errorf("Invalid vector in fuzz config in %s", moduleName)) |
| } |
| |
| if !config.Service_privilege.isValidServicePrivilege() { |
| panic(fmt.Errorf("Invalid service_privilege in fuzz config in %s", moduleName)) |
| } |
| |
| if !config.Users.isValidUserData() { |
| panic(fmt.Errorf("Invalid users (user_data) in fuzz config in %s", moduleName)) |
| } |
| |
| if !config.Fuzzed_code_usage.isValidFuzzedCodeUsage() { |
| panic(fmt.Errorf("Invalid fuzzed_code_usage in fuzz config in %s", moduleName)) |
| } |
| |
| if !config.Automatically_route_to.isValidAutomaticallyRouteTo() { |
| panic(fmt.Errorf("Invalid automatically_route_to in fuzz config in %s", moduleName)) |
| } |
| } |
| return true |
| } |
| |
| type FuzzConfig struct { |
| // Email address of people to CC on bugs or contact about this fuzz target. |
| Cc []string `json:"cc,omitempty"` |
| // A brief description of what the fuzzed code does. |
| Description string `json:"description,omitempty"` |
| // Whether the code being fuzzed is remotely accessible or requires privileges |
| // to access locally. |
| Vector Vector `json:"vector,omitempty"` |
| // How privileged the service being fuzzed is. |
| Service_privilege ServicePrivilege `json:"service_privilege,omitempty"` |
| // Whether the service being fuzzed handles data from multiple users or only |
| // a single one. |
| Users UserData `json:"users,omitempty"` |
| // Specifies the use state of the code being fuzzed. This state factors into |
| // how an issue is handled. |
| Fuzzed_code_usage FuzzedCodeUsage `json:"fuzzed_code_usage,omitempty"` |
| // Comment describing how we came to these settings for this fuzzer. |
| Config_comment string |
| // Which team to route this to, if it should be routed automatically. |
| Automatically_route_to AutomaticallyRouteTo `json:"automatically_route_to,omitempty"` |
| // Can third party/untrusted apps supply data to fuzzed code. |
| Untrusted_data *bool `json:"untrusted_data,omitempty"` |
| // When code was released or will be released. |
| Production_date string `json:"production_date,omitempty"` |
| // Prevents critical service functionality like phone calls, bluetooth, etc. |
| Critical *bool `json:"critical,omitempty"` |
| // Specify whether to enable continuous fuzzing on devices. Defaults to true. |
| Fuzz_on_haiku_device *bool `json:"fuzz_on_haiku_device,omitempty"` |
| // Specify whether to enable continuous fuzzing on host. Defaults to true. |
| Fuzz_on_haiku_host *bool `json:"fuzz_on_haiku_host,omitempty"` |
| // Component in Google's bug tracking system that bugs should be filed to. |
| Componentid *int64 `json:"componentid,omitempty"` |
| // Hotlist(s) in Google's bug tracking system that bugs should be marked with. |
| Hotlists []string `json:"hotlists,omitempty"` |
| // Specify whether this fuzz target was submitted by a researcher. Defaults |
| // to false. |
| Researcher_submitted *bool `json:"researcher_submitted,omitempty"` |
| // Specify who should be acknowledged for CVEs in the Android Security |
| // Bulletin. |
| Acknowledgement []string `json:"acknowledgement,omitempty"` |
| // Additional options to be passed to libfuzzer when run in Haiku. |
| Libfuzzer_options []string `json:"libfuzzer_options,omitempty"` |
| // Additional options to be passed to HWASAN when running on-device in Haiku. |
| Hwasan_options []string `json:"hwasan_options,omitempty"` |
| // Additional options to be passed to HWASAN when running on host in Haiku. |
| Asan_options []string `json:"asan_options,omitempty"` |
| // If there's a Java fuzzer with JNI, a different version of Jazzer would |
| // need to be added to the fuzzer package than one without JNI |
| IsJni *bool `json:"is_jni,omitempty"` |
| // List of modules for monitoring coverage drops in directories (e.g. "libicu") |
| Target_modules []string `json:"target_modules,omitempty"` |
| // Specifies a bug assignee to replace default ISE assignment |
| Triage_assignee string `json:"triage_assignee,omitempty"` |
| } |
| |
| type FuzzFrameworks struct { |
| Afl *bool |
| Libfuzzer *bool |
| Jazzer *bool |
| } |
| |
| type FuzzProperties struct { |
| // Optional list of seed files to be installed to the fuzz target's output |
| // directory. |
| Corpus []string `android:"path"` |
| // Optional list of data files to be installed to the fuzz target's output |
| // directory. Directory structure relative to the module is preserved. |
| Data []string `android:"path"` |
| // Optional dictionary to be installed to the fuzz target's output directory. |
| Dictionary *string `android:"path"` |
| // Define the fuzzing frameworks this fuzz target can be built for. If |
| // empty then the fuzz target will be available to be built for all fuzz |
| // frameworks available |
| Fuzzing_frameworks *FuzzFrameworks |
| // Config for running the target on fuzzing infrastructure. |
| Fuzz_config *FuzzConfig |
| } |
| |
| type FuzzPackagedModule struct { |
| FuzzProperties FuzzProperties |
| Dictionary android.Path |
| Corpus android.Paths |
| CorpusIntermediateDir android.Path |
| Config android.Path |
| Data android.Paths |
| DataIntermediateDir android.Path |
| } |
| |
| func GetFramework(ctx android.LoadHookContext, lang Lang) Framework { |
| framework := ctx.Config().Getenv("FUZZ_FRAMEWORK") |
| |
| if lang == Cc { |
| switch strings.ToLower(framework) { |
| case "": |
| return LibFuzzer |
| case "libfuzzer": |
| return LibFuzzer |
| case "afl": |
| return AFL |
| } |
| } else if lang == Rust { |
| return LibFuzzer |
| } else if lang == Java { |
| return Jazzer |
| } |
| |
| ctx.ModuleErrorf(fmt.Sprintf("%s is not a valid fuzzing framework for %s", framework, lang)) |
| return UnknownFramework |
| } |
| |
| func IsValidFrameworkForModule(targetFramework Framework, lang Lang, moduleFrameworks *FuzzFrameworks) bool { |
| if targetFramework == UnknownFramework { |
| return false |
| } |
| |
| if moduleFrameworks == nil { |
| return true |
| } |
| |
| switch targetFramework { |
| case LibFuzzer: |
| return proptools.BoolDefault(moduleFrameworks.Libfuzzer, true) |
| case AFL: |
| return proptools.BoolDefault(moduleFrameworks.Afl, true) |
| case Jazzer: |
| return proptools.BoolDefault(moduleFrameworks.Jazzer, true) |
| default: |
| panic("%s is not supported as a fuzz framework") |
| } |
| } |
| |
| func IsValid(fuzzModule FuzzModule) bool { |
| // Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of |
| // fuzz targets we're going to package anyway. |
| if !fuzzModule.Enabled() || fuzzModule.InRamdisk() || fuzzModule.InVendorRamdisk() || fuzzModule.InRecovery() { |
| return false |
| } |
| |
| // Discard modules that are in an unavailable namespace. |
| if !fuzzModule.ExportedToMake() { |
| return false |
| } |
| |
| return true |
| } |
| |
| func (s *FuzzPackager) PackageArtifacts(ctx android.SingletonContext, module android.Module, fuzzModule FuzzPackagedModule, archDir android.OutputPath, builder *android.RuleBuilder) []FileToZip { |
| // Package the corpora into a zipfile. |
| var files []FileToZip |
| if fuzzModule.Corpus != nil { |
| corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip") |
| command := builder.Command().BuiltTool("soong_zip"). |
| Flag("-j"). |
| FlagWithOutput("-o ", corpusZip) |
| rspFile := corpusZip.ReplaceExtension(ctx, "rsp") |
| command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.Corpus) |
| files = append(files, FileToZip{corpusZip, ""}) |
| } |
| |
| // Package the data into a zipfile. |
| if fuzzModule.Data != nil { |
| dataZip := archDir.Join(ctx, module.Name()+"_data.zip") |
| command := builder.Command().BuiltTool("soong_zip"). |
| FlagWithOutput("-o ", dataZip) |
| for _, f := range fuzzModule.Data { |
| intermediateDir := strings.TrimSuffix(f.String(), f.Rel()) |
| command.FlagWithArg("-C ", intermediateDir) |
| command.FlagWithInput("-f ", f) |
| } |
| files = append(files, FileToZip{dataZip, ""}) |
| } |
| |
| // The dictionary. |
| if fuzzModule.Dictionary != nil { |
| files = append(files, FileToZip{fuzzModule.Dictionary, ""}) |
| } |
| |
| // Additional fuzz config. |
| if fuzzModule.Config != nil && IsValidConfig(fuzzModule, module.Name()) { |
| files = append(files, FileToZip{fuzzModule.Config, ""}) |
| } |
| |
| return files |
| } |
| |
| func (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) { |
| fuzzZip := archDir.Join(ctx, module.Name()+".zip") |
| |
| command := builder.Command().BuiltTool("soong_zip"). |
| Flag("-j"). |
| FlagWithOutput("-o ", fuzzZip) |
| |
| for _, file := range files { |
| if file.DestinationPathPrefix != "" { |
| command.FlagWithArg("-P ", file.DestinationPathPrefix) |
| } else { |
| command.Flag("-P ''") |
| } |
| command.FlagWithInput("-f ", file.SourceFilePath) |
| } |
| |
| builder.Build("create-"+fuzzZip.String(), |
| "Package "+module.Name()+" for "+archString+"-"+hostOrTargetString) |
| |
| // Don't add modules to 'make haiku-rust' that are set to not be |
| // exported to the fuzzing infrastructure. |
| if config := fuzzModule.FuzzProperties.Fuzz_config; config != nil { |
| if strings.Contains(hostOrTargetString, "host") && !BoolDefault(config.Fuzz_on_haiku_host, true) { |
| return archDirs[archOs], false |
| } else if !BoolDefault(config.Fuzz_on_haiku_device, true) { |
| return archDirs[archOs], false |
| } |
| } |
| |
| s.FuzzTargets[module.Name()] = true |
| archDirs[archOs] = append(archDirs[archOs], FileToZip{fuzzZip, ""}) |
| |
| return archDirs[archOs], true |
| } |
| |
| func (f *FuzzConfig) String() string { |
| b, err := json.Marshal(f) |
| if err != nil { |
| panic(err) |
| } |
| |
| return string(b) |
| } |
| |
| func (s *FuzzPackager) CreateFuzzPackage(ctx android.SingletonContext, archDirs map[ArchOs][]FileToZip, fuzzType Lang, pctx android.PackageContext) { |
| var archOsList []ArchOs |
| for archOs := range archDirs { |
| archOsList = append(archOsList, archOs) |
| } |
| sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].Dir < archOsList[j].Dir }) |
| |
| for _, archOs := range archOsList { |
| filesToZip := archDirs[archOs] |
| arch := archOs.Arch |
| hostOrTarget := archOs.HostOrTarget |
| builder := android.NewRuleBuilder(pctx, ctx) |
| zipFileName := "fuzz-" + hostOrTarget + "-" + arch + ".zip" |
| if fuzzType == Rust { |
| zipFileName = "fuzz-rust-" + hostOrTarget + "-" + arch + ".zip" |
| } |
| if fuzzType == Java { |
| zipFileName = "fuzz-java-" + hostOrTarget + "-" + arch + ".zip" |
| } |
| |
| outputFile := android.PathForOutput(ctx, zipFileName) |
| |
| s.Packages = append(s.Packages, outputFile) |
| |
| command := builder.Command().BuiltTool("soong_zip"). |
| Flag("-j"). |
| FlagWithOutput("-o ", outputFile). |
| Flag("-L 0") // No need to try and re-compress the zipfiles. |
| |
| for _, fileToZip := range filesToZip { |
| if fileToZip.DestinationPathPrefix != "" { |
| command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix) |
| } else { |
| command.Flag("-P ''") |
| } |
| command.FlagWithInput("-f ", fileToZip.SourceFilePath) |
| |
| } |
| builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget, |
| "Create fuzz target packages for "+arch+"-"+hostOrTarget) |
| } |
| } |
| |
| func (s *FuzzPackager) PreallocateSlice(ctx android.MakeVarsContext, targets string) { |
| fuzzTargets := make([]string, 0, len(s.FuzzTargets)) |
| for target, _ := range s.FuzzTargets { |
| fuzzTargets = append(fuzzTargets, target) |
| } |
| |
| sort.Strings(fuzzTargets) |
| ctx.Strict(targets, strings.Join(fuzzTargets, " ")) |
| } |