31d89b4e0c
Make rust and cc fuzzers collect their shared libraries once in GenerateAndroidBuildActions and store it for later use by the packaging singleton. Also use android.OutputFileForModule to get the paths. Together this will fix fuzzers that depend on architecture specific prebuilt shared libraries that are missing a prebuilt for an architecture when building with AllowMissingDependencies. Bug: 250918230 Test: lunch aosp_riscv64-userdebug && m ALLOW_MISSING_DEPENDENCIES=true nothing Change-Id: I154a6f3a767c883e9fe7067003615db73ee78e2d
381 lines
12 KiB
Go
381 lines
12 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
|
|
}
|
|
|
|
type ArchOs struct {
|
|
HostOrTarget string
|
|
Arch string
|
|
Dir string
|
|
}
|
|
|
|
type PrivilegedLevel string
|
|
|
|
const (
|
|
// Environment with the most minimal permissions.
|
|
Constrained PrivilegedLevel = "Constrained"
|
|
// Typical execution environment running unprivileged code.
|
|
Unprivileged = "Unprivileged"
|
|
// May have access to elevated permissions.
|
|
Privileged = "Privileged"
|
|
// Trusted computing base.
|
|
Tcb = "TCB"
|
|
// Bootloader chain.
|
|
Bootloader = "Bootloader"
|
|
// Tusted execution environment.
|
|
Tee = "Tee"
|
|
// Secure enclave.
|
|
Se = "Se"
|
|
// Other.
|
|
Other = "Other"
|
|
)
|
|
|
|
func IsValidConfig(fuzzModule FuzzPackagedModule, moduleName string) bool {
|
|
var config = fuzzModule.FuzzProperties.Fuzz_config
|
|
if config != nil {
|
|
var level = PrivilegedLevel(config.Privilege_level)
|
|
if level != "" {
|
|
switch level {
|
|
case Constrained, Unprivileged, Privileged, Tcb, Bootloader, Tee, Se, Other:
|
|
return true
|
|
}
|
|
panic(fmt.Errorf("Invalid privileged level in fuzz config in %s", moduleName))
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
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"`
|
|
// Can this code be triggered remotely or only locally.
|
|
Remotely_accessible *bool `json:"remotely_accessible,omitempty"`
|
|
// Is the fuzzed code host only, i.e. test frameworks or support utilities.
|
|
Host_only *bool `json:"host_only,omitempty"`
|
|
// Can third party/untrusted apps supply data to fuzzed code.
|
|
Untrusted_data *bool `json:"untrusted_data,omitempty"`
|
|
// Is the code being fuzzed in a privileged, constrained or any other
|
|
// context from:
|
|
// https://source.android.com/security/overview/updates-resources#context_types.
|
|
Privilege_level PrivilegedLevel `json:"privilege_level,omitempty"`
|
|
// Can the fuzzed code isolated or can be called by multiple users/processes.
|
|
Isolated *bool `json:"users_isolation,omitempty"`
|
|
// When code was relaeased 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"`
|
|
// Hotlists 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"`
|
|
}
|
|
|
|
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, " "))
|
|
}
|