1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
|
// 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"
)
func (f Framework) Variant() string {
switch f {
case AFL:
return "afl"
case LibFuzzer:
return "libfuzzer"
case Jazzer:
return "jazzer"
default:
panic(fmt.Errorf("unknown fuzzer %q when getting variant", f))
}
}
func FrameworkFromVariant(v string) Framework {
switch v {
case "afl":
return AFL
case "libfuzzer":
return LibFuzzer
case "jazzer":
return Jazzer
default:
panic(fmt.Errorf("unknown variant %q when getting fuzzer", v))
}
}
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.ConfigurableEvaluatorContext, 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, " "))
}
|