platform_build_soong/fuzz/fuzz_common.go
Cole Faust 021bf3d973 Reapply "Make the enabled property configurable"
Previously, I had changed some loadhook-appended property structs
to use selects instead of the "target" property struct. This seems
to not be exactly equivalent because "target" properties are merged
with the regular properties later, at the time the arch mutator runs.

With this reapplication, leave those target property structs alone
to avoid breakages, but I'll have to look into what the issue is
with them later.

This reverts commit ed5276f082.

Ignore-AOSP-First: This cl needs to be in a topic with internal-only projects, will cherrypick to aosp after.
Bug: 323382414
Test: m nothing --no-skip-soong-tests
Change-Id: If355d24506e3f117d27b21442a6c02bca3402dc7
2024-05-02 10:56:36 -07:00

602 lines
21 KiB
Go

// 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, " "))
}