xref: /aosp_15_r20/build/soong/fuzz/fuzz_common.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2021 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package fuzz
16
17// This file contains the common code for compiling C/C++ and Rust fuzzers for Android.
18
19import (
20	"encoding/json"
21	"fmt"
22	"sort"
23	"strings"
24
25	"github.com/google/blueprint/proptools"
26
27	"android/soong/android"
28)
29
30type Lang string
31
32const (
33	Cc   Lang = "cc"
34	Rust Lang = "rust"
35	Java Lang = "java"
36)
37
38type Framework string
39
40const (
41	AFL              Framework = "afl"
42	LibFuzzer        Framework = "libfuzzer"
43	Jazzer           Framework = "jazzer"
44	UnknownFramework Framework = "unknownframework"
45)
46
47func (f Framework) Variant() string {
48	switch f {
49	case AFL:
50		return "afl"
51	case LibFuzzer:
52		return "libfuzzer"
53	case Jazzer:
54		return "jazzer"
55	default:
56		panic(fmt.Errorf("unknown fuzzer %q when getting variant", f))
57	}
58}
59
60func FrameworkFromVariant(v string) Framework {
61	switch v {
62	case "afl":
63		return AFL
64	case "libfuzzer":
65		return LibFuzzer
66	case "jazzer":
67		return Jazzer
68	default:
69		panic(fmt.Errorf("unknown variant %q when getting fuzzer", v))
70	}
71}
72
73var BoolDefault = proptools.BoolDefault
74
75type FuzzModule struct {
76	android.ModuleBase
77	android.DefaultableModuleBase
78	android.ApexModuleBase
79}
80
81type FuzzPackager struct {
82	Packages                android.Paths
83	FuzzTargets             map[string]bool
84	SharedLibInstallStrings []string
85}
86
87type FileToZip struct {
88	SourceFilePath        android.Path
89	DestinationPathPrefix string
90	DestinationPath       string
91}
92
93type ArchOs struct {
94	HostOrTarget string
95	Arch         string
96	Dir          string
97}
98
99type Vector string
100
101const (
102	unknown_access_vector Vector = "unknown_access_vector"
103	// The code being fuzzed is reachable from a remote source, or using data
104	// provided by a remote source.  For example: media codecs process media files
105	// from the internet, SMS processing handles remote message data.
106	// See
107	// https://source.android.com/docs/security/overview/updates-resources#local-vs-remote
108	// for an explanation of what's considered "remote."
109	remote = "remote"
110	// The code being fuzzed can only be reached locally, such as from an
111	// installed app.  As an example, if it's fuzzing a Binder interface, it's
112	// assumed that you'd need a local app to make arbitrary Binder calls.
113	// And the app that's calling the fuzzed code does not require any privileges;
114	// any 3rd party app could make these calls.
115	local_no_privileges_required = "local_no_privileges_required"
116	// The code being fuzzed can only be called locally, and the calling process
117	// requires additional permissions that prevent arbitrary 3rd party apps from
118	// calling the code.  For instance: this requires a privileged or signature
119	// permission to reach, or SELinux restrictions prevent the untrusted_app
120	// domain from calling it.
121	local_privileges_required = "local_privileges_required"
122	// The code is only callable on a PC host, not on a production Android device.
123	// For instance, this is fuzzing code used during the build process, or
124	// tooling that does not exist on a user's actual Android device.
125	host_access = "host_access"
126	// The code being fuzzed is only reachable if the user has enabled Developer
127	// Options, or has enabled a persistent Developer Options setting.
128	local_with_developer_options = "local_with_developer_options"
129)
130
131func (vector Vector) isValidVector() bool {
132	switch vector {
133	case "",
134		unknown_access_vector,
135		remote,
136		local_no_privileges_required,
137		local_privileges_required,
138		host_access,
139		local_with_developer_options:
140		return true
141	}
142	return false
143}
144
145type ServicePrivilege string
146
147const (
148	unknown_service_privilege ServicePrivilege = "unknown_service_privilege"
149	// The code being fuzzed runs on a Secure Element.  This has access to some
150	// of the most privileged data on the device, such as authentication keys.
151	// Not all devices have a Secure Element.
152	secure_element = "secure_element"
153	// The code being fuzzed runs in the TEE.  The TEE is designed to be resistant
154	// to a compromised kernel, and stores sensitive data.
155	trusted_execution = "trusted_execution"
156	// The code being fuzzed has privileges beyond what arbitrary 3rd party apps
157	// have.  For instance, it's running as the System UID, or it's in an SELinux
158	// domain that's able to perform calls that can't be made by 3rd party apps.
159	privileged = "privileged"
160	// The code being fuzzed is equivalent to a 3rd party app.  It runs in the
161	// untrusted_app SELinux domain, or it only has privileges that are equivalent
162	// to what a 3rd party app could have.
163	unprivileged = "unprivileged"
164	// The code being fuzzed is significantly constrained, and even if it's
165	// compromised, it has significant restrictions that prevent it from
166	// performing most actions.  This is significantly more restricted than
167	// UNPRIVILEGED.  An example is the isolatedProcess=true setting in a 3rd
168	// party app.  Or a process that's very restricted by SELinux, such as
169	// anything in the mediacodec SELinux domain.
170	constrained = "constrained"
171	// The code being fuzzed always has Negligible Security Impact.  Even
172	// arbitrary out of bounds writes and full code execution would not be
173	// considered a security vulnerability.  This typically only makes sense if
174	// FuzzedCodeUsage is set to FUTURE_VERSION or EXPERIMENTAL, and if
175	// AutomaticallyRouteTo is set to ALWAYS_NSI.
176	nsi = "nsi"
177	// The code being fuzzed only runs on a PC host, not on a production Android
178	// device.  For instance, the fuzzer is fuzzing code used during the build
179	// process, or tooling that does not exist on a user's actual Android device.
180	host_only = "host_only"
181)
182
183func (service_privilege ServicePrivilege) isValidServicePrivilege() bool {
184	switch service_privilege {
185	case "",
186		unknown_service_privilege,
187		secure_element,
188		trusted_execution,
189		privileged,
190		unprivileged,
191		constrained,
192		nsi,
193		host_only:
194		return true
195	}
196	return false
197}
198
199type UsePlatformLibs string
200
201const (
202	unknown_use_platform_libs UsePlatformLibs = "unknown_use_platform_libs"
203	// Use the native libraries on the device, typically in /system directory
204	use_platform_libs = "use_platform_libs"
205	// Do not use any native libraries (ART will not be initialized)
206	use_none = "use_none"
207)
208
209func (use_platform_libs UsePlatformLibs) isValidUsePlatformLibs() bool {
210	switch use_platform_libs {
211	case "",
212		unknown_use_platform_libs,
213		use_platform_libs,
214		use_none:
215		return true
216	}
217	return false
218}
219
220type UserData string
221
222const (
223	unknown_user_data UserData = "unknown_user_data"
224	// The process being fuzzed only handles data from a single user, or from a
225	// single process or app.  It's possible the process shuts down before
226	// handling data from another user/process/app, or it's possible the process
227	// only ever handles one user's/process's/app's data.  As an example, some
228	// print spooler processes are started for a single document and terminate
229	// when done, so each instance only handles data from a single user/app.
230	single_user = "single_user"
231	// The process handles data from multiple users, or from multiple other apps
232	// or processes.  Media processes, for instance, can handle media requests
233	// from multiple different apps without restarting.  Wi-Fi and network
234	// processes handle data from multiple users, and processes, and apps.
235	multi_user = "multi_user"
236)
237
238func (user_data UserData) isValidUserData() bool {
239	switch user_data {
240	case "",
241		unknown_user_data,
242		single_user,
243		multi_user:
244		return true
245	}
246	return false
247}
248
249type FuzzedCodeUsage string
250
251const (
252	undefined FuzzedCodeUsage = "undefined"
253	unknown                   = "unknown"
254	// The code being fuzzed exists in a shipped version of Android and runs on
255	// devices in production.
256	shipped = "shipped"
257	// The code being fuzzed is not yet in a shipping version of Android, but it
258	// will be at some point in the future.
259	future_version = "future_version"
260	// The code being fuzzed is not in a shipping version of Android, and there
261	// are no plans to ship it in the future.
262	experimental = "experimental"
263)
264
265func (fuzzed_code_usage FuzzedCodeUsage) isValidFuzzedCodeUsage() bool {
266	switch fuzzed_code_usage {
267	case "",
268		undefined,
269		unknown,
270		shipped,
271		future_version,
272		experimental:
273		return true
274	}
275	return false
276}
277
278type AutomaticallyRouteTo string
279
280const (
281	undefined_routing AutomaticallyRouteTo = "undefined_routing"
282	// Automatically route this to the Android Automotive security team for
283	// assessment.
284	android_automotive = "android_automotive"
285	// This should not be used in fuzzer configurations.  It is used internally
286	// by Severity Assigner to flag memory leak reports.
287	memory_leak = "memory_leak"
288	// Route this vulnerability to our Ittiam vendor team for assessment.
289	ittiam = "ittiam"
290	// Reports from this fuzzer are always NSI (see the NSI ServicePrivilegeEnum
291	// value for additional context).  It is not possible for this code to ever
292	// have a security vulnerability.
293	always_nsi = "always_nsi"
294	// Route this vulnerability to AIDL team for assessment.
295	aidl = "aidl"
296)
297
298func (automatically_route_to AutomaticallyRouteTo) isValidAutomaticallyRouteTo() bool {
299	switch automatically_route_to {
300	case "",
301		undefined_routing,
302		android_automotive,
303		memory_leak,
304		ittiam,
305		always_nsi,
306		aidl:
307		return true
308	}
309	return false
310}
311
312func IsValidConfig(fuzzModule FuzzPackagedModule, moduleName string) bool {
313	var config = fuzzModule.FuzzProperties.Fuzz_config
314	if config != nil {
315		if !config.Vector.isValidVector() {
316			panic(fmt.Errorf("Invalid vector in fuzz config in %s", moduleName))
317		}
318
319		if !config.Service_privilege.isValidServicePrivilege() {
320			panic(fmt.Errorf("Invalid service_privilege in fuzz config in %s", moduleName))
321		}
322
323		if !config.Users.isValidUserData() {
324			panic(fmt.Errorf("Invalid users (user_data) in fuzz config in %s", moduleName))
325		}
326
327		if !config.Fuzzed_code_usage.isValidFuzzedCodeUsage() {
328			panic(fmt.Errorf("Invalid fuzzed_code_usage in fuzz config in %s", moduleName))
329		}
330
331		if !config.Automatically_route_to.isValidAutomaticallyRouteTo() {
332			panic(fmt.Errorf("Invalid automatically_route_to in fuzz config in %s", moduleName))
333		}
334
335		if !config.Use_platform_libs.isValidUsePlatformLibs() {
336			panic(fmt.Errorf("Invalid use_platform_libs in fuzz config in %s", moduleName))
337		}
338	}
339	return true
340}
341
342type FuzzConfig struct {
343	// Email address of people to CC on bugs or contact about this fuzz target.
344	Cc []string `json:"cc,omitempty"`
345	// A brief description of what the fuzzed code does.
346	Description string `json:"description,omitempty"`
347	// Whether the code being fuzzed is remotely accessible or requires privileges
348	// to access locally.
349	Vector Vector `json:"vector,omitempty"`
350	// How privileged the service being fuzzed is.
351	Service_privilege ServicePrivilege `json:"service_privilege,omitempty"`
352	// Whether the service being fuzzed handles data from multiple users or only
353	// a single one.
354	Users UserData `json:"users,omitempty"`
355	// Specifies the use state of the code being fuzzed. This state factors into
356	// how an issue is handled.
357	Fuzzed_code_usage FuzzedCodeUsage `json:"fuzzed_code_usage,omitempty"`
358	// Comment describing how we came to these settings for this fuzzer.
359	Config_comment string
360	// Which team to route this to, if it should be routed automatically.
361	Automatically_route_to AutomaticallyRouteTo `json:"automatically_route_to,omitempty"`
362	// Can third party/untrusted apps supply data to fuzzed code.
363	Untrusted_data *bool `json:"untrusted_data,omitempty"`
364	// When code was released or will be released.
365	Production_date string `json:"production_date,omitempty"`
366	// Prevents critical service functionality like phone calls, bluetooth, etc.
367	Critical *bool `json:"critical,omitempty"`
368	// Specify whether to enable continuous fuzzing on devices. Defaults to true.
369	Fuzz_on_haiku_device *bool `json:"fuzz_on_haiku_device,omitempty"`
370	// Specify whether to enable continuous fuzzing on host. Defaults to true.
371	Fuzz_on_haiku_host *bool `json:"fuzz_on_haiku_host,omitempty"`
372	// Component in Google's bug tracking system that bugs should be filed to.
373	Componentid *int64 `json:"componentid,omitempty"`
374	// Hotlist(s) in Google's bug tracking system that bugs should be marked with.
375	Hotlists []string `json:"hotlists,omitempty"`
376	// Specify whether this fuzz target was submitted by a researcher. Defaults
377	// to false.
378	Researcher_submitted *bool `json:"researcher_submitted,omitempty"`
379	// Specify who should be acknowledged for CVEs in the Android Security
380	// Bulletin.
381	Acknowledgement []string `json:"acknowledgement,omitempty"`
382	// Additional options to be passed to libfuzzer when run in Haiku.
383	Libfuzzer_options []string `json:"libfuzzer_options,omitempty"`
384	// Additional options to be passed to HWASAN when running on-device in Haiku.
385	Hwasan_options []string `json:"hwasan_options,omitempty"`
386	// Additional options to be passed to HWASAN when running on host in Haiku.
387	Asan_options []string `json:"asan_options,omitempty"`
388	// If there's a Java fuzzer with JNI, a different version of Jazzer would
389	// need to be added to the fuzzer package than one without JNI
390	IsJni *bool `json:"is_jni,omitempty"`
391	// List of modules for monitoring coverage drops in directories (e.g. "libicu")
392	Target_modules []string `json:"target_modules,omitempty"`
393	// Specifies a bug assignee to replace default ISE assignment
394	Triage_assignee string `json:"triage_assignee,omitempty"`
395	// Specifies libs used to initialize ART (java only, 'use_none' for no initialization)
396	Use_platform_libs UsePlatformLibs `json:"use_platform_libs,omitempty"`
397	// Specifies whether fuzz target should check presubmitted code changes for crashes.
398	// Defaults to false.
399	Use_for_presubmit *bool `json:"use_for_presubmit,omitempty"`
400	// Specify which paths to exclude from fuzzing coverage reports
401	Exclude_paths_from_reports []string `json:"exclude_paths_from_reports,omitempty"`
402}
403
404type FuzzFrameworks struct {
405	Afl       *bool
406	Libfuzzer *bool
407	Jazzer    *bool
408}
409
410type FuzzProperties struct {
411	// Optional list of seed files to be installed to the fuzz target's output
412	// directory.
413	Corpus []string `android:"path"`
414
415	// Same as corpus, but adds dependencies on module references using the device's os variant
416	// and the common arch variant.
417	Device_common_corpus []string `android:"path_device_common"`
418
419	// Optional list of data files to be installed to the fuzz target's output
420	// directory. Directory structure relative to the module is preserved.
421	Data []string `android:"path"`
422	// Optional dictionary to be installed to the fuzz target's output directory.
423	Dictionary *string `android:"path"`
424	// Define the fuzzing frameworks this fuzz target can be built for. If
425	// empty then the fuzz target will be available to be  built for all fuzz
426	// frameworks available
427	Fuzzing_frameworks *FuzzFrameworks
428	// Config for running the target on fuzzing infrastructure.
429	Fuzz_config *FuzzConfig
430}
431
432type FuzzPackagedModule struct {
433	FuzzProperties FuzzProperties
434	Dictionary     android.Path
435	Corpus         android.Paths
436	Config         android.Path
437	Data           android.Paths
438}
439
440func GetFramework(ctx android.LoadHookContext, lang Lang) Framework {
441	framework := ctx.Config().Getenv("FUZZ_FRAMEWORK")
442
443	if lang == Cc {
444		switch strings.ToLower(framework) {
445		case "":
446			return LibFuzzer
447		case "libfuzzer":
448			return LibFuzzer
449		case "afl":
450			return AFL
451		}
452	} else if lang == Rust {
453		return LibFuzzer
454	} else if lang == Java {
455		return Jazzer
456	}
457
458	ctx.ModuleErrorf(fmt.Sprintf("%s is not a valid fuzzing framework for %s", framework, lang))
459	return UnknownFramework
460}
461
462func IsValidFrameworkForModule(targetFramework Framework, lang Lang, moduleFrameworks *FuzzFrameworks) bool {
463	if targetFramework == UnknownFramework {
464		return false
465	}
466
467	if moduleFrameworks == nil {
468		return true
469	}
470
471	switch targetFramework {
472	case LibFuzzer:
473		return proptools.BoolDefault(moduleFrameworks.Libfuzzer, true)
474	case AFL:
475		return proptools.BoolDefault(moduleFrameworks.Afl, true)
476	case Jazzer:
477		return proptools.BoolDefault(moduleFrameworks.Jazzer, true)
478	default:
479		panic("%s is not supported as a fuzz framework")
480	}
481}
482
483func IsValid(ctx android.ConfigurableEvaluatorContext, fuzzModule FuzzModule) bool {
484	// Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of
485	// fuzz targets we're going to package anyway.
486	if !fuzzModule.Enabled(ctx) || fuzzModule.InRamdisk() || fuzzModule.InVendorRamdisk() || fuzzModule.InRecovery() {
487		return false
488	}
489
490	// Discard modules that are in an unavailable namespace.
491	if !fuzzModule.ExportedToMake() {
492		return false
493	}
494
495	return true
496}
497
498func (s *FuzzPackager) PackageArtifacts(ctx android.SingletonContext, module android.Module, fuzzModule FuzzPackagedModule, archDir android.OutputPath, builder *android.RuleBuilder) []FileToZip {
499	// Package the corpora into a zipfile.
500	var files []FileToZip
501	if fuzzModule.Corpus != nil {
502		corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip")
503		command := builder.Command().BuiltTool("soong_zip").
504			Flag("-j").
505			FlagWithOutput("-o ", corpusZip)
506		rspFile := corpusZip.ReplaceExtension(ctx, "rsp")
507		command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.Corpus)
508		files = append(files, FileToZip{SourceFilePath: corpusZip})
509	}
510
511	// Package the data into a zipfile.
512	if fuzzModule.Data != nil {
513		dataZip := archDir.Join(ctx, module.Name()+"_data.zip")
514		command := builder.Command().BuiltTool("soong_zip").
515			FlagWithOutput("-o ", dataZip)
516		for _, f := range fuzzModule.Data {
517			intermediateDir := strings.TrimSuffix(f.String(), f.Rel())
518			command.FlagWithArg("-C ", intermediateDir)
519			command.FlagWithInput("-f ", f)
520		}
521		files = append(files, FileToZip{SourceFilePath: dataZip})
522	}
523
524	// The dictionary.
525	if fuzzModule.Dictionary != nil {
526		files = append(files, FileToZip{SourceFilePath: fuzzModule.Dictionary})
527	}
528
529	// Additional fuzz config.
530	if fuzzModule.Config != nil && IsValidConfig(fuzzModule, module.Name()) {
531		files = append(files, FileToZip{SourceFilePath: fuzzModule.Config})
532	}
533
534	return files
535}
536
537func (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) {
538	fuzzZip := archDir.Join(ctx, module.Name()+".zip")
539
540	command := builder.Command().BuiltTool("soong_zip").
541		Flag("-j").
542		FlagWithOutput("-o ", fuzzZip)
543
544	for _, file := range files {
545		if file.DestinationPathPrefix != "" {
546			command.FlagWithArg("-P ", file.DestinationPathPrefix)
547		} else {
548			command.Flag("-P ''")
549		}
550		if file.DestinationPath != "" {
551			command.FlagWithArg("-e ", file.DestinationPath)
552		}
553		command.FlagWithInput("-f ", file.SourceFilePath)
554	}
555
556	builder.Build("create-"+fuzzZip.String(),
557		"Package "+module.Name()+" for "+archString+"-"+hostOrTargetString)
558
559	if config := fuzzModule.FuzzProperties.Fuzz_config; config != nil {
560		if strings.Contains(hostOrTargetString, "host") && !BoolDefault(config.Fuzz_on_haiku_host, true) {
561			return archDirs[archOs], false
562		} else if !strings.Contains(hostOrTargetString, "host") && !BoolDefault(config.Fuzz_on_haiku_device, true) {
563			return archDirs[archOs], false
564		}
565	}
566
567	s.FuzzTargets[module.Name()] = true
568	archDirs[archOs] = append(archDirs[archOs], FileToZip{SourceFilePath: fuzzZip})
569
570	return archDirs[archOs], true
571}
572
573func (f *FuzzConfig) String() string {
574	b, err := json.Marshal(f)
575	if err != nil {
576		panic(err)
577	}
578
579	return string(b)
580}
581
582func (s *FuzzPackager) CreateFuzzPackage(ctx android.SingletonContext, archDirs map[ArchOs][]FileToZip, fuzzType Lang, pctx android.PackageContext) {
583	var archOsList []ArchOs
584	for archOs := range archDirs {
585		archOsList = append(archOsList, archOs)
586	}
587	sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].Dir < archOsList[j].Dir })
588
589	for _, archOs := range archOsList {
590		filesToZip := archDirs[archOs]
591		arch := archOs.Arch
592		hostOrTarget := archOs.HostOrTarget
593		builder := android.NewRuleBuilder(pctx, ctx)
594		zipFileName := "fuzz-" + hostOrTarget + "-" + arch + ".zip"
595		if fuzzType == Rust {
596			zipFileName = "fuzz-rust-" + hostOrTarget + "-" + arch + ".zip"
597		}
598		if fuzzType == Java {
599			zipFileName = "fuzz-java-" + hostOrTarget + "-" + arch + ".zip"
600		}
601
602		outputFile := android.PathForOutput(ctx, zipFileName)
603
604		s.Packages = append(s.Packages, outputFile)
605
606		command := builder.Command().BuiltTool("soong_zip").
607			Flag("-j").
608			FlagWithOutput("-o ", outputFile).
609			Flag("-L 0") // No need to try and re-compress the zipfiles.
610
611		for _, fileToZip := range filesToZip {
612			if fileToZip.DestinationPathPrefix != "" {
613				command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix)
614			} else {
615				command.Flag("-P ''")
616			}
617			command.FlagWithInput("-f ", fileToZip.SourceFilePath)
618
619		}
620		builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget,
621			"Create fuzz target packages for "+arch+"-"+hostOrTarget)
622	}
623}
624
625func (s *FuzzPackager) PreallocateSlice(ctx android.MakeVarsContext, targets string) {
626	fuzzTargets := make([]string, 0, len(s.FuzzTargets))
627	for target, _ := range s.FuzzTargets {
628		fuzzTargets = append(fuzzTargets, target)
629	}
630
631	sort.Strings(fuzzTargets)
632	ctx.Strict(targets, strings.Join(fuzzTargets, " "))
633}
634