blob: f76529d31698247b66092d87c1a3121ea84ee813 [file] [log] [blame]
// 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, " "))
}