blob: c8cd21b7e9d7cb217abdeb68fee85ab7251d6363 [file] [log] [blame]
hamzeh41ad8812021-07-07 14:00:07 -07001// 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
hamzehc0a671f2021-07-22 12:05:08 -070015package fuzz
hamzeh41ad8812021-07-07 14:00:07 -070016
17// This file contains the common code for compiling C/C++ and Rust fuzzers for Android.
18
19import (
hamzehc0a671f2021-07-22 12:05:08 -070020 "encoding/json"
hamzehe8a1bfa2022-06-21 12:22:06 -070021 "fmt"
hamzeh41ad8812021-07-07 14:00:07 -070022 "sort"
23 "strings"
24
hamzehc0a671f2021-07-22 12:05:08 -070025 "github.com/google/blueprint/proptools"
26
hamzeh41ad8812021-07-07 14:00:07 -070027 "android/soong/android"
28)
29
Cory Barker9cfcf6d2022-07-22 17:22:02 +000030type Lang string
hamzeh41ad8812021-07-07 14:00:07 -070031
32const (
Cory Barker9cfcf6d2022-07-22 17:22:02 +000033 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"
hamzeh41ad8812021-07-07 14:00:07 -070045)
46
hamzehc0a671f2021-07-22 12:05:08 -070047var BoolDefault = proptools.BoolDefault
48
hamzeh41ad8812021-07-07 14:00:07 -070049type FuzzModule struct {
50 android.ModuleBase
51 android.DefaultableModuleBase
52 android.ApexModuleBase
53}
54
55type FuzzPackager struct {
Ivan Lozano39b0bf02021-10-14 12:22:09 -040056 Packages android.Paths
57 FuzzTargets map[string]bool
58 SharedLibInstallStrings []string
hamzeh41ad8812021-07-07 14:00:07 -070059}
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
hamzehe8a1bfa2022-06-21 12:22:06 -070072type PrivilegedLevel string
73
74const (
75 // Environment with the most minimal permissions.
76 Constrained PrivilegedLevel = "Constrained"
77 // Typical execution environment running unprivileged code.
78 Unprivileged = "Unprivileged"
79 // May have access to elevated permissions.
80 Privileged = "Privileged"
81 // Trusted computing base.
82 Tcb = "TCB"
83 // Bootloader chain.
84 Bootloader = "Bootloader"
85 // Tusted execution environment.
86 Tee = "Tee"
87 // Secure enclave.
88 Se = "Se"
89 // Other.
90 Other = "Other"
91)
92
93func IsValidConfig(fuzzModule FuzzPackagedModule, moduleName string) bool {
94 var config = fuzzModule.FuzzProperties.Fuzz_config
95 if config != nil {
96 var level = PrivilegedLevel(config.Privilege_level)
97 if level != "" {
98 switch level {
99 case Constrained, Unprivileged, Privileged, Tcb, Bootloader, Tee, Se, Other:
100 return true
101 }
102 panic(fmt.Errorf("Invalid privileged level in fuzz config in %s", moduleName))
103 }
104 return true
105 } else {
106 return false
107 }
108}
109
hamzehc0a671f2021-07-22 12:05:08 -0700110type FuzzConfig struct {
111 // Email address of people to CC on bugs or contact about this fuzz target.
112 Cc []string `json:"cc,omitempty"`
hamzehe8a1bfa2022-06-21 12:22:06 -0700113 // A brief description of what the fuzzed code does.
114 Description string `json:"description,omitempty"`
115 // Can this code be triggered remotely or only locally.
hamzeh3c983d22022-07-26 14:19:22 -0700116 Remotely_accessible *bool `json:"remotely_accessible,omitempty"`
hamzehe8a1bfa2022-06-21 12:22:06 -0700117 // Is the fuzzed code host only, i.e. test frameworks or support utilities.
hamzeh3c983d22022-07-26 14:19:22 -0700118 Host_only *bool `json:"host_only,omitempty"`
hamzehe8a1bfa2022-06-21 12:22:06 -0700119 // Can third party/untrusted apps supply data to fuzzed code.
hamzeh3c983d22022-07-26 14:19:22 -0700120 Untrusted_data *bool `json:"untrusted_data,omitempty"`
hamzehe8a1bfa2022-06-21 12:22:06 -0700121 // Is the code being fuzzed in a privileged, constrained or any other
122 // context from:
123 // https://source.android.com/security/overview/updates-resources#context_types.
124 Privilege_level PrivilegedLevel `json:"privilege_level,omitempty"`
125 // Can the fuzzed code isolated or can be called by multiple users/processes.
hamzeh3c983d22022-07-26 14:19:22 -0700126 Isolated *bool `json:"users_isolation,omitempty"`
hamzehe8a1bfa2022-06-21 12:22:06 -0700127 // When code was relaeased or will be released.
128 Production_date string `json:"production_date,omitempty"`
129 // Prevents critical service functionality like phone calls, bluetooth, etc.
hamzeh3c983d22022-07-26 14:19:22 -0700130 Critical *bool `json:"critical,omitempty"`
hamzehc0a671f2021-07-22 12:05:08 -0700131 // Specify whether to enable continuous fuzzing on devices. Defaults to true.
132 Fuzz_on_haiku_device *bool `json:"fuzz_on_haiku_device,omitempty"`
133 // Specify whether to enable continuous fuzzing on host. Defaults to true.
134 Fuzz_on_haiku_host *bool `json:"fuzz_on_haiku_host,omitempty"`
135 // Component in Google's bug tracking system that bugs should be filed to.
136 Componentid *int64 `json:"componentid,omitempty"`
137 // Hotlists in Google's bug tracking system that bugs should be marked with.
138 Hotlists []string `json:"hotlists,omitempty"`
139 // Specify whether this fuzz target was submitted by a researcher. Defaults
140 // to false.
141 Researcher_submitted *bool `json:"researcher_submitted,omitempty"`
142 // Specify who should be acknowledged for CVEs in the Android Security
143 // Bulletin.
144 Acknowledgement []string `json:"acknowledgement,omitempty"`
145 // Additional options to be passed to libfuzzer when run in Haiku.
146 Libfuzzer_options []string `json:"libfuzzer_options,omitempty"`
147 // Additional options to be passed to HWASAN when running on-device in Haiku.
148 Hwasan_options []string `json:"hwasan_options,omitempty"`
149 // Additional options to be passed to HWASAN when running on host in Haiku.
150 Asan_options []string `json:"asan_options,omitempty"`
Muhammad Haseeb Ahmad7e744052022-03-25 22:50:53 +0000151 // If there's a Java fuzzer with JNI, a different version of Jazzer would
152 // need to be added to the fuzzer package than one without JNI
153 IsJni *bool `json:"is_jni,omitempty"`
hamzehc0a671f2021-07-22 12:05:08 -0700154}
155
Cory Barker9cfcf6d2022-07-22 17:22:02 +0000156type FuzzFrameworks struct {
157 Afl *bool
158 Libfuzzer *bool
159 Jazzer *bool
160}
161
hamzehc0a671f2021-07-22 12:05:08 -0700162type FuzzProperties struct {
163 // Optional list of seed files to be installed to the fuzz target's output
164 // directory.
165 Corpus []string `android:"path"`
166 // Optional list of data files to be installed to the fuzz target's output
167 // directory. Directory structure relative to the module is preserved.
168 Data []string `android:"path"`
169 // Optional dictionary to be installed to the fuzz target's output directory.
170 Dictionary *string `android:"path"`
Cory Barker9cfcf6d2022-07-22 17:22:02 +0000171 // Define the fuzzing frameworks this fuzz target can be built for. If
172 // empty then the fuzz target will be available to be built for all fuzz
173 // frameworks available
174 Fuzzing_frameworks *FuzzFrameworks
hamzehc0a671f2021-07-22 12:05:08 -0700175 // Config for running the target on fuzzing infrastructure.
176 Fuzz_config *FuzzConfig
177}
178
hamzeh41ad8812021-07-07 14:00:07 -0700179type FuzzPackagedModule struct {
180 FuzzProperties FuzzProperties
181 Dictionary android.Path
182 Corpus android.Paths
183 CorpusIntermediateDir android.Path
184 Config android.Path
185 Data android.Paths
186 DataIntermediateDir android.Path
187}
188
Cory Barker9cfcf6d2022-07-22 17:22:02 +0000189func GetFramework(ctx android.LoadHookContext, lang Lang) Framework {
190 framework := ctx.Config().Getenv("FUZZ_FRAMEWORK")
191
192 if lang == Cc {
193 switch strings.ToLower(framework) {
194 case "":
195 return LibFuzzer
196 case "libfuzzer":
197 return LibFuzzer
198 case "afl":
199 return AFL
200 }
201 } else if lang == Rust {
202 return LibFuzzer
203 } else if lang == Java {
204 return Jazzer
205 }
206
207 ctx.ModuleErrorf(fmt.Sprintf("%s is not a valid fuzzing framework for %s", framework, lang))
208 return UnknownFramework
209}
210
211func IsValidFrameworkForModule(targetFramework Framework, lang Lang, moduleFrameworks *FuzzFrameworks) bool {
212 if targetFramework == UnknownFramework {
213 return false
214 }
215
216 if moduleFrameworks == nil {
217 return true
218 }
219
220 switch targetFramework {
221 case LibFuzzer:
222 return proptools.BoolDefault(moduleFrameworks.Libfuzzer, true)
223 case AFL:
224 return proptools.BoolDefault(moduleFrameworks.Afl, true)
225 case Jazzer:
226 return proptools.BoolDefault(moduleFrameworks.Jazzer, true)
227 default:
228 panic("%s is not supported as a fuzz framework")
229 }
230}
231
hamzeh41ad8812021-07-07 14:00:07 -0700232func IsValid(fuzzModule FuzzModule) bool {
233 // Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of
234 // fuzz targets we're going to package anyway.
235 if !fuzzModule.Enabled() || fuzzModule.InRamdisk() || fuzzModule.InVendorRamdisk() || fuzzModule.InRecovery() {
236 return false
237 }
238
239 // Discard modules that are in an unavailable namespace.
240 if !fuzzModule.ExportedToMake() {
241 return false
242 }
243
244 return true
245}
246
247func (s *FuzzPackager) PackageArtifacts(ctx android.SingletonContext, module android.Module, fuzzModule FuzzPackagedModule, archDir android.OutputPath, builder *android.RuleBuilder) []FileToZip {
248 // Package the corpora into a zipfile.
249 var files []FileToZip
250 if fuzzModule.Corpus != nil {
251 corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip")
252 command := builder.Command().BuiltTool("soong_zip").
253 Flag("-j").
254 FlagWithOutput("-o ", corpusZip)
255 rspFile := corpusZip.ReplaceExtension(ctx, "rsp")
256 command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.Corpus)
257 files = append(files, FileToZip{corpusZip, ""})
258 }
259
260 // Package the data into a zipfile.
261 if fuzzModule.Data != nil {
262 dataZip := archDir.Join(ctx, module.Name()+"_data.zip")
263 command := builder.Command().BuiltTool("soong_zip").
264 FlagWithOutput("-o ", dataZip)
265 for _, f := range fuzzModule.Data {
266 intermediateDir := strings.TrimSuffix(f.String(), f.Rel())
267 command.FlagWithArg("-C ", intermediateDir)
268 command.FlagWithInput("-f ", f)
269 }
270 files = append(files, FileToZip{dataZip, ""})
271 }
272
273 // The dictionary.
274 if fuzzModule.Dictionary != nil {
275 files = append(files, FileToZip{fuzzModule.Dictionary, ""})
276 }
277
278 // Additional fuzz config.
hamzehe8a1bfa2022-06-21 12:22:06 -0700279 if fuzzModule.Config != nil && IsValidConfig(fuzzModule, module.Name()) {
hamzeh41ad8812021-07-07 14:00:07 -0700280 files = append(files, FileToZip{fuzzModule.Config, ""})
281 }
282
283 return files
284}
285
286func (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) {
287 fuzzZip := archDir.Join(ctx, module.Name()+".zip")
288
289 command := builder.Command().BuiltTool("soong_zip").
290 Flag("-j").
291 FlagWithOutput("-o ", fuzzZip)
292
293 for _, file := range files {
294 if file.DestinationPathPrefix != "" {
295 command.FlagWithArg("-P ", file.DestinationPathPrefix)
296 } else {
297 command.Flag("-P ''")
298 }
299 command.FlagWithInput("-f ", file.SourceFilePath)
300 }
301
302 builder.Build("create-"+fuzzZip.String(),
303 "Package "+module.Name()+" for "+archString+"-"+hostOrTargetString)
304
305 // Don't add modules to 'make haiku-rust' that are set to not be
306 // exported to the fuzzing infrastructure.
307 if config := fuzzModule.FuzzProperties.Fuzz_config; config != nil {
308 if strings.Contains(hostOrTargetString, "host") && !BoolDefault(config.Fuzz_on_haiku_host, true) {
309 return archDirs[archOs], false
310 } else if !BoolDefault(config.Fuzz_on_haiku_device, true) {
311 return archDirs[archOs], false
312 }
313 }
314
315 s.FuzzTargets[module.Name()] = true
316 archDirs[archOs] = append(archDirs[archOs], FileToZip{fuzzZip, ""})
317
318 return archDirs[archOs], true
319}
320
hamzehc0a671f2021-07-22 12:05:08 -0700321func (f *FuzzConfig) String() string {
322 b, err := json.Marshal(f)
323 if err != nil {
324 panic(err)
325 }
326
327 return string(b)
328}
329
Cory Barker9cfcf6d2022-07-22 17:22:02 +0000330func (s *FuzzPackager) CreateFuzzPackage(ctx android.SingletonContext, archDirs map[ArchOs][]FileToZip, fuzzType Lang, pctx android.PackageContext) {
hamzeh41ad8812021-07-07 14:00:07 -0700331 var archOsList []ArchOs
332 for archOs := range archDirs {
333 archOsList = append(archOsList, archOs)
334 }
335 sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].Dir < archOsList[j].Dir })
336
337 for _, archOs := range archOsList {
338 filesToZip := archDirs[archOs]
339 arch := archOs.Arch
340 hostOrTarget := archOs.HostOrTarget
341 builder := android.NewRuleBuilder(pctx, ctx)
342 zipFileName := "fuzz-" + hostOrTarget + "-" + arch + ".zip"
Cory Barkera1da26f2022-06-07 20:12:06 +0000343 if fuzzType == Rust {
hamzeh41ad8812021-07-07 14:00:07 -0700344 zipFileName = "fuzz-rust-" + hostOrTarget + "-" + arch + ".zip"
345 }
Cory Barkera1da26f2022-06-07 20:12:06 +0000346 if fuzzType == Java {
Muhammad Haseeb Ahmade3803102022-01-10 21:37:07 +0000347 zipFileName = "fuzz-java-" + hostOrTarget + "-" + arch + ".zip"
348 }
Cory Barker9cfcf6d2022-07-22 17:22:02 +0000349
hamzeh41ad8812021-07-07 14:00:07 -0700350 outputFile := android.PathForOutput(ctx, zipFileName)
351
352 s.Packages = append(s.Packages, outputFile)
353
354 command := builder.Command().BuiltTool("soong_zip").
355 Flag("-j").
356 FlagWithOutput("-o ", outputFile).
357 Flag("-L 0") // No need to try and re-compress the zipfiles.
358
359 for _, fileToZip := range filesToZip {
hamzeh41ad8812021-07-07 14:00:07 -0700360 if fileToZip.DestinationPathPrefix != "" {
361 command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix)
362 } else {
363 command.Flag("-P ''")
364 }
365 command.FlagWithInput("-f ", fileToZip.SourceFilePath)
366
367 }
368 builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget,
369 "Create fuzz target packages for "+arch+"-"+hostOrTarget)
370 }
371}
372
373func (s *FuzzPackager) PreallocateSlice(ctx android.MakeVarsContext, targets string) {
374 fuzzTargets := make([]string, 0, len(s.FuzzTargets))
375 for target, _ := range s.FuzzTargets {
376 fuzzTargets = append(fuzzTargets, target)
377 }
Cory Barkera1da26f2022-06-07 20:12:06 +0000378
hamzeh41ad8812021-07-07 14:00:07 -0700379 sort.Strings(fuzzTargets)
380 ctx.Strict(targets, strings.Join(fuzzTargets, " "))
381}
Ivan Lozano39b0bf02021-10-14 12:22:09 -0400382
383// CollectAllSharedDependencies performs a breadth-first search over the provided module's
384// dependencies using `visitDirectDeps` to enumerate all shared library
385// dependencies. We require breadth-first expansion, as otherwise we may
386// incorrectly use the core libraries (sanitizer runtimes, libc, libdl, etc.)
387// from a dependency. This may cause issues when dependencies have explicit
388// sanitizer tags, as we may get a dependency on an unsanitized libc, etc.
389func CollectAllSharedDependencies(ctx android.SingletonContext, module android.Module, unstrippedOutputFile func(module android.Module) android.Path, isValidSharedDependency func(dependency android.Module) bool) android.Paths {
390 var fringe []android.Module
391
392 seen := make(map[string]bool)
393
394 // Enumerate the first level of dependencies, as we discard all non-library
395 // modules in the BFS loop below.
396 ctx.VisitDirectDeps(module, func(dep android.Module) {
397 if isValidSharedDependency(dep) {
398 fringe = append(fringe, dep)
399 }
400 })
401
402 var sharedLibraries android.Paths
403
404 for i := 0; i < len(fringe); i++ {
405 module := fringe[i]
406 if seen[module.Name()] {
407 continue
408 }
409 seen[module.Name()] = true
410
411 sharedLibraries = append(sharedLibraries, unstrippedOutputFile(module))
412 ctx.VisitDirectDeps(module, func(dep android.Module) {
413 if isValidSharedDependency(dep) && !seen[dep.Name()] {
414 fringe = append(fringe, dep)
415 }
416 })
417 }
418
419 return sharedLibraries
420}