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