// 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 DestinationPath 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 UsePlatformLibs string const ( unknown_use_platform_libs UsePlatformLibs = "unknown_use_platform_libs" // Use the native libraries on the device, typically in /system directory use_platform_libs = "use_platform_libs" // Do not use any native libraries (ART will not be initialized) use_none = "use_none" ) func (use_platform_libs UsePlatformLibs) isValidUsePlatformLibs() bool { switch use_platform_libs { case "", unknown_use_platform_libs, use_platform_libs, use_none: 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)) } if !config.Use_platform_libs.isValidUsePlatformLibs() { panic(fmt.Errorf("Invalid use_platform_libs 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"` // Specifies libs used to initialize ART (java only, 'use_none' for no initialization) Use_platform_libs UsePlatformLibs `json:"use_platform_libs,omitempty"` // Specifies whether fuzz target should check presubmitted code changes for crashes. // Defaults to false. Use_for_presubmit *bool `json:"use_for_presubmit,omitempty"` // Specify which paths to exclude from fuzzing coverage reports Exclude_paths_from_reports []string `json:"exclude_paths_from_reports,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 Config android.Path Data android.Paths } 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(ctx android.ConfigAndErrorContext, fuzzModule FuzzModule) bool { // Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of // fuzz targets we're going to package anyway. if !fuzzModule.Enabled(ctx) || 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{SourceFilePath: 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{SourceFilePath: dataZip}) } // The dictionary. if fuzzModule.Dictionary != nil { files = append(files, FileToZip{SourceFilePath: fuzzModule.Dictionary}) } // Additional fuzz config. if fuzzModule.Config != nil && IsValidConfig(fuzzModule, module.Name()) { files = append(files, FileToZip{SourceFilePath: 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 ''") } if file.DestinationPath != "" { command.FlagWithArg("-e ", file.DestinationPath) } command.FlagWithInput("-f ", file.SourceFilePath) } builder.Build("create-"+fuzzZip.String(), "Package "+module.Name()+" for "+archString+"-"+hostOrTargetString) 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 !strings.Contains(hostOrTargetString, "host") && !BoolDefault(config.Fuzz_on_haiku_device, true) { return archDirs[archOs], false } } s.FuzzTargets[module.Name()] = true archDirs[archOs] = append(archDirs[archOs], FileToZip{SourceFilePath: 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, " ")) }