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