1// Copyright 2016 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 cc
16
17import (
18	"encoding/json"
19	"path/filepath"
20	"sort"
21	"strings"
22
23	"github.com/google/blueprint/proptools"
24
25	"android/soong/android"
26	"android/soong/cc/config"
27)
28
29type FuzzConfig struct {
30	// Email address of people to CC on bugs or contact about this fuzz target.
31	Cc []string `json:"cc,omitempty"`
32	// Specify whether to enable continuous fuzzing on devices. Defaults to true.
33	Fuzz_on_haiku_device *bool `json:"fuzz_on_haiku_device,omitempty"`
34	// Specify whether to enable continuous fuzzing on host. Defaults to true.
35	Fuzz_on_haiku_host *bool `json:"fuzz_on_haiku_host,omitempty"`
36	// Component in Google's bug tracking system that bugs should be filed to.
37	Componentid *int64 `json:"componentid,omitempty"`
38	// Hotlists in Google's bug tracking system that bugs should be marked with.
39	Hotlists []string `json:"hotlists,omitempty"`
40	// Specify whether this fuzz target was submitted by a researcher. Defaults
41	// to false.
42	Researcher_submitted *bool `json:"researcher_submitted,omitempty"`
43	// Specify who should be acknowledged for CVEs in the Android Security
44	// Bulletin.
45	Acknowledgement []string `json:"acknowledgement,omitempty"`
46	// Additional options to be passed to libfuzzer when run in Haiku.
47	Libfuzzer_options []string `json:"libfuzzer_options,omitempty"`
48	// Additional options to be passed to HWASAN when running on-device in Haiku.
49	Hwasan_options []string `json:"hwasan_options,omitempty"`
50	// Additional options to be passed to HWASAN when running on host in Haiku.
51	Asan_options []string `json:"asan_options,omitempty"`
52}
53
54func (f *FuzzConfig) String() string {
55	b, err := json.Marshal(f)
56	if err != nil {
57		panic(err)
58	}
59
60	return string(b)
61}
62
63type FuzzProperties struct {
64	// Optional list of seed files to be installed to the fuzz target's output
65	// directory.
66	Corpus []string `android:"path"`
67	// Optional list of data files to be installed to the fuzz target's output
68	// directory. Directory structure relative to the module is preserved.
69	Data []string `android:"path"`
70	// Optional dictionary to be installed to the fuzz target's output directory.
71	Dictionary *string `android:"path"`
72	// Config for running the target on fuzzing infrastructure.
73	Fuzz_config *FuzzConfig
74}
75
76func init() {
77	android.RegisterModuleType("cc_fuzz", FuzzFactory)
78	android.RegisterSingletonType("cc_fuzz_packaging", fuzzPackagingFactory)
79}
80
81// cc_fuzz creates a host/device fuzzer binary. Host binaries can be found at
82// $ANDROID_HOST_OUT/fuzz/, and device binaries can be found at /data/fuzz on
83// your device, or $ANDROID_PRODUCT_OUT/data/fuzz in your build tree.
84func FuzzFactory() android.Module {
85	module := NewFuzz(android.HostAndDeviceSupported)
86	return module.Init()
87}
88
89func NewFuzzInstaller() *baseInstaller {
90	return NewBaseInstaller("fuzz", "fuzz", InstallInData)
91}
92
93type fuzzBinary struct {
94	*binaryDecorator
95	*baseCompiler
96
97	Properties            FuzzProperties
98	dictionary            android.Path
99	corpus                android.Paths
100	corpusIntermediateDir android.Path
101	config                android.Path
102	data                  android.Paths
103	dataIntermediateDir   android.Path
104	installedSharedDeps   []string
105}
106
107func (fuzz *fuzzBinary) linkerProps() []interface{} {
108	props := fuzz.binaryDecorator.linkerProps()
109	props = append(props, &fuzz.Properties)
110	return props
111}
112
113func (fuzz *fuzzBinary) linkerInit(ctx BaseModuleContext) {
114	fuzz.binaryDecorator.linkerInit(ctx)
115}
116
117func (fuzz *fuzzBinary) linkerDeps(ctx DepsContext, deps Deps) Deps {
118	deps.StaticLibs = append(deps.StaticLibs,
119		config.LibFuzzerRuntimeLibrary(ctx.toolchain()))
120	deps = fuzz.binaryDecorator.linkerDeps(ctx, deps)
121	return deps
122}
123
124func (fuzz *fuzzBinary) linkerFlags(ctx ModuleContext, flags Flags) Flags {
125	flags = fuzz.binaryDecorator.linkerFlags(ctx, flags)
126	// RunPaths on devices isn't instantiated by the base linker. `../lib` for
127	// installed fuzz targets (both host and device), and `./lib` for fuzz
128	// target packages.
129	flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN/../lib`)
130	flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN/lib`)
131	return flags
132}
133
134// This function performs a breadth-first search over the provided module's
135// dependencies using `visitDirectDeps` to enumerate all shared library
136// dependencies. We require breadth-first expansion, as otherwise we may
137// incorrectly use the core libraries (sanitizer runtimes, libc, libdl, etc.)
138// from a dependency. This may cause issues when dependencies have explicit
139// sanitizer tags, as we may get a dependency on an unsanitized libc, etc.
140func collectAllSharedDependencies(ctx android.SingletonContext, module android.Module) android.Paths {
141	var fringe []android.Module
142
143	seen := make(map[string]bool)
144
145	// Enumerate the first level of dependencies, as we discard all non-library
146	// modules in the BFS loop below.
147	ctx.VisitDirectDeps(module, func(dep android.Module) {
148		if isValidSharedDependency(dep) {
149			fringe = append(fringe, dep)
150		}
151	})
152
153	var sharedLibraries android.Paths
154
155	for i := 0; i < len(fringe); i++ {
156		module := fringe[i]
157		if seen[module.Name()] {
158			continue
159		}
160		seen[module.Name()] = true
161
162		ccModule := module.(*Module)
163		sharedLibraries = append(sharedLibraries, ccModule.UnstrippedOutputFile())
164		ctx.VisitDirectDeps(module, func(dep android.Module) {
165			if isValidSharedDependency(dep) && !seen[dep.Name()] {
166				fringe = append(fringe, dep)
167			}
168		})
169	}
170
171	return sharedLibraries
172}
173
174// This function takes a module and determines if it is a unique shared library
175// that should be installed in the fuzz target output directories. This function
176// returns true, unless:
177//  - The module is not an installable shared library, or
178//  - The module is a header, stub, or vendor-linked library, or
179//  - The module is a prebuilt and its source is available, or
180//  - The module is a versioned member of an SDK snapshot.
181func isValidSharedDependency(dependency android.Module) bool {
182	// TODO(b/144090547): We should be parsing these modules using
183	// ModuleDependencyTag instead of the current brute-force checking.
184
185	linkable, ok := dependency.(LinkableInterface)
186	if !ok || !linkable.CcLibraryInterface() {
187		// Discard non-linkables.
188		return false
189	}
190
191	if !linkable.Shared() {
192		// Discard static libs.
193		return false
194	}
195
196	if linkable.UseVndk() {
197		// Discard vendor linked libraries.
198		return false
199	}
200
201	if lib := moduleLibraryInterface(dependency); lib != nil && lib.buildStubs() && linkable.CcLibrary() {
202		// Discard stubs libs (only CCLibrary variants). Prebuilt libraries should not
203		// be excluded on the basis of they're not CCLibrary()'s.
204		return false
205	}
206
207	// We discarded module stubs libraries above, but the LLNDK prebuilts stubs
208	// libraries must be handled differently - by looking for the stubDecorator.
209	// Discard LLNDK prebuilts stubs as well.
210	if ccLibrary, isCcLibrary := dependency.(*Module); isCcLibrary {
211		if _, isLLndkStubLibrary := ccLibrary.linker.(*stubDecorator); isLLndkStubLibrary {
212			return false
213		}
214		// Discard installable:false libraries because they are expected to be absent
215		// in runtime.
216		if !proptools.BoolDefault(ccLibrary.Properties.Installable, true) {
217			return false
218		}
219	}
220
221	// If the same library is present both as source and a prebuilt we must pick
222	// only one to avoid a conflict. Always prefer the source since the prebuilt
223	// probably won't be built with sanitizers enabled.
224	if prebuilt := android.GetEmbeddedPrebuilt(dependency); prebuilt != nil && prebuilt.SourceExists() {
225		return false
226	}
227
228	// Discard versioned members of SDK snapshots, because they will conflict with
229	// unversioned ones.
230	if sdkMember, ok := dependency.(android.SdkAware); ok && !sdkMember.ContainingSdk().Unversioned() {
231		return false
232	}
233
234	return true
235}
236
237func sharedLibraryInstallLocation(
238	libraryPath android.Path, isHost bool, archString string) string {
239	installLocation := "$(PRODUCT_OUT)/data"
240	if isHost {
241		installLocation = "$(HOST_OUT)"
242	}
243	installLocation = filepath.Join(
244		installLocation, "fuzz", archString, "lib", libraryPath.Base())
245	return installLocation
246}
247
248// Get the device-only shared library symbols install directory.
249func sharedLibrarySymbolsInstallLocation(libraryPath android.Path, archString string) string {
250	return filepath.Join("$(PRODUCT_OUT)/symbols/data/fuzz/", archString, "/lib/", libraryPath.Base())
251}
252
253func (fuzz *fuzzBinary) install(ctx ModuleContext, file android.Path) {
254	fuzz.binaryDecorator.baseInstaller.dir = filepath.Join(
255		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
256	fuzz.binaryDecorator.baseInstaller.dir64 = filepath.Join(
257		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
258	fuzz.binaryDecorator.baseInstaller.install(ctx, file)
259
260	fuzz.corpus = android.PathsForModuleSrc(ctx, fuzz.Properties.Corpus)
261	builder := android.NewRuleBuilder(pctx, ctx)
262	intermediateDir := android.PathForModuleOut(ctx, "corpus")
263	for _, entry := range fuzz.corpus {
264		builder.Command().Text("cp").
265			Input(entry).
266			Output(intermediateDir.Join(ctx, entry.Base()))
267	}
268	builder.Build("copy_corpus", "copy corpus")
269	fuzz.corpusIntermediateDir = intermediateDir
270
271	fuzz.data = android.PathsForModuleSrc(ctx, fuzz.Properties.Data)
272	builder = android.NewRuleBuilder(pctx, ctx)
273	intermediateDir = android.PathForModuleOut(ctx, "data")
274	for _, entry := range fuzz.data {
275		builder.Command().Text("cp").
276			Input(entry).
277			Output(intermediateDir.Join(ctx, entry.Rel()))
278	}
279	builder.Build("copy_data", "copy data")
280	fuzz.dataIntermediateDir = intermediateDir
281
282	if fuzz.Properties.Dictionary != nil {
283		fuzz.dictionary = android.PathForModuleSrc(ctx, *fuzz.Properties.Dictionary)
284		if fuzz.dictionary.Ext() != ".dict" {
285			ctx.PropertyErrorf("dictionary",
286				"Fuzzer dictionary %q does not have '.dict' extension",
287				fuzz.dictionary.String())
288		}
289	}
290
291	if fuzz.Properties.Fuzz_config != nil {
292		configPath := android.PathForModuleOut(ctx, "config").Join(ctx, "config.json")
293		android.WriteFileRule(ctx, configPath, fuzz.Properties.Fuzz_config.String())
294		fuzz.config = configPath
295	}
296
297	// Grab the list of required shared libraries.
298	seen := make(map[string]bool)
299	var sharedLibraries android.Paths
300	ctx.WalkDeps(func(child, parent android.Module) bool {
301		if seen[child.Name()] {
302			return false
303		}
304		seen[child.Name()] = true
305
306		if isValidSharedDependency(child) {
307			sharedLibraries = append(sharedLibraries, child.(*Module).UnstrippedOutputFile())
308			return true
309		}
310		return false
311	})
312
313	for _, lib := range sharedLibraries {
314		fuzz.installedSharedDeps = append(fuzz.installedSharedDeps,
315			sharedLibraryInstallLocation(
316				lib, ctx.Host(), ctx.Arch().ArchType.String()))
317
318		// Also add the dependency on the shared library symbols dir.
319		if !ctx.Host() {
320			fuzz.installedSharedDeps = append(fuzz.installedSharedDeps,
321				sharedLibrarySymbolsInstallLocation(lib, ctx.Arch().ArchType.String()))
322		}
323	}
324}
325
326func NewFuzz(hod android.HostOrDeviceSupported) *Module {
327	module, binary := NewBinary(hod)
328
329	binary.baseInstaller = NewFuzzInstaller()
330	module.sanitize.SetSanitizer(Fuzzer, true)
331
332	fuzz := &fuzzBinary{
333		binaryDecorator: binary,
334		baseCompiler:    NewBaseCompiler(),
335	}
336	module.compiler = fuzz
337	module.linker = fuzz
338	module.installer = fuzz
339
340	// The fuzzer runtime is not present for darwin host modules, disable cc_fuzz modules when targeting darwin.
341	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
342		disableDarwinAndLinuxBionic := struct {
343			Target struct {
344				Darwin struct {
345					Enabled *bool
346				}
347				Linux_bionic struct {
348					Enabled *bool
349				}
350			}
351		}{}
352		disableDarwinAndLinuxBionic.Target.Darwin.Enabled = BoolPtr(false)
353		disableDarwinAndLinuxBionic.Target.Linux_bionic.Enabled = BoolPtr(false)
354		ctx.AppendProperties(&disableDarwinAndLinuxBionic)
355	})
356
357	return module
358}
359
360// Responsible for generating GNU Make rules that package fuzz targets into
361// their architecture & target/host specific zip file.
362type fuzzPackager struct {
363	packages                android.Paths
364	sharedLibInstallStrings []string
365	fuzzTargets             map[string]bool
366}
367
368func fuzzPackagingFactory() android.Singleton {
369	return &fuzzPackager{}
370}
371
372type fileToZip struct {
373	SourceFilePath        android.Path
374	DestinationPathPrefix string
375}
376
377type archOs struct {
378	hostOrTarget string
379	arch         string
380	dir          string
381}
382
383func (s *fuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
384	// Map between each architecture + host/device combination, and the files that
385	// need to be packaged (in the tuple of {source file, destination folder in
386	// archive}).
387	archDirs := make(map[archOs][]fileToZip)
388
389	// Map tracking whether each shared library has an install rule to avoid duplicate install rules from
390	// multiple fuzzers that depend on the same shared library.
391	sharedLibraryInstalled := make(map[string]bool)
392
393	// List of individual fuzz targets, so that 'make fuzz' also installs the targets
394	// to the correct output directories as well.
395	s.fuzzTargets = make(map[string]bool)
396
397	ctx.VisitAllModules(func(module android.Module) {
398		// Discard non-fuzz targets.
399		ccModule, ok := module.(*Module)
400		if !ok {
401			return
402		}
403
404		fuzzModule, ok := ccModule.compiler.(*fuzzBinary)
405		if !ok {
406			return
407		}
408
409		// Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of
410		// fuzz targets we're going to package anyway.
411		if !ccModule.Enabled() || ccModule.Properties.PreventInstall ||
412			ccModule.InRamdisk() || ccModule.InVendorRamdisk() || ccModule.InRecovery() {
413			return
414		}
415
416		// Discard modules that are in an unavailable namespace.
417		if !ccModule.ExportedToMake() {
418			return
419		}
420
421		hostOrTargetString := "target"
422		if ccModule.Host() {
423			hostOrTargetString = "host"
424		}
425
426		archString := ccModule.Arch().ArchType.String()
427		archDir := android.PathForIntermediates(ctx, "fuzz", hostOrTargetString, archString)
428		archOs := archOs{hostOrTarget: hostOrTargetString, arch: archString, dir: archDir.String()}
429
430		// Grab the list of required shared libraries.
431		sharedLibraries := collectAllSharedDependencies(ctx, module)
432
433		var files []fileToZip
434		builder := android.NewRuleBuilder(pctx, ctx)
435
436		// Package the corpora into a zipfile.
437		if fuzzModule.corpus != nil {
438			corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip")
439			command := builder.Command().BuiltTool("soong_zip").
440				Flag("-j").
441				FlagWithOutput("-o ", corpusZip)
442			rspFile := corpusZip.ReplaceExtension(ctx, "rsp")
443			command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.corpus)
444			files = append(files, fileToZip{corpusZip, ""})
445		}
446
447		// Package the data into a zipfile.
448		if fuzzModule.data != nil {
449			dataZip := archDir.Join(ctx, module.Name()+"_data.zip")
450			command := builder.Command().BuiltTool("soong_zip").
451				FlagWithOutput("-o ", dataZip)
452			for _, f := range fuzzModule.data {
453				intermediateDir := strings.TrimSuffix(f.String(), f.Rel())
454				command.FlagWithArg("-C ", intermediateDir)
455				command.FlagWithInput("-f ", f)
456			}
457			files = append(files, fileToZip{dataZip, ""})
458		}
459
460		// Find and mark all the transiently-dependent shared libraries for
461		// packaging.
462		for _, library := range sharedLibraries {
463			files = append(files, fileToZip{library, "lib"})
464
465			// For each architecture-specific shared library dependency, we need to
466			// install it to the output directory. Setup the install destination here,
467			// which will be used by $(copy-many-files) in the Make backend.
468			installDestination := sharedLibraryInstallLocation(
469				library, ccModule.Host(), archString)
470			if sharedLibraryInstalled[installDestination] {
471				continue
472			}
473			sharedLibraryInstalled[installDestination] = true
474
475			// Escape all the variables, as the install destination here will be called
476			// via. $(eval) in Make.
477			installDestination = strings.ReplaceAll(
478				installDestination, "$", "$$")
479			s.sharedLibInstallStrings = append(s.sharedLibInstallStrings,
480				library.String()+":"+installDestination)
481
482			// Ensure that on device, the library is also reinstalled to the /symbols/
483			// dir. Symbolized DSO's are always installed to the device when fuzzing, but
484			// we want symbolization tools (like `stack`) to be able to find the symbols
485			// in $ANDROID_PRODUCT_OUT/symbols automagically.
486			if !ccModule.Host() {
487				symbolsInstallDestination := sharedLibrarySymbolsInstallLocation(library, archString)
488				symbolsInstallDestination = strings.ReplaceAll(symbolsInstallDestination, "$", "$$")
489				s.sharedLibInstallStrings = append(s.sharedLibInstallStrings,
490					library.String()+":"+symbolsInstallDestination)
491			}
492		}
493
494		// The executable.
495		files = append(files, fileToZip{ccModule.UnstrippedOutputFile(), ""})
496
497		// The dictionary.
498		if fuzzModule.dictionary != nil {
499			files = append(files, fileToZip{fuzzModule.dictionary, ""})
500		}
501
502		// Additional fuzz config.
503		if fuzzModule.config != nil {
504			files = append(files, fileToZip{fuzzModule.config, ""})
505		}
506
507		fuzzZip := archDir.Join(ctx, module.Name()+".zip")
508		command := builder.Command().BuiltTool("soong_zip").
509			Flag("-j").
510			FlagWithOutput("-o ", fuzzZip)
511		for _, file := range files {
512			if file.DestinationPathPrefix != "" {
513				command.FlagWithArg("-P ", file.DestinationPathPrefix)
514			} else {
515				command.Flag("-P ''")
516			}
517			command.FlagWithInput("-f ", file.SourceFilePath)
518		}
519
520		builder.Build("create-"+fuzzZip.String(),
521			"Package "+module.Name()+" for "+archString+"-"+hostOrTargetString)
522
523		// Don't add modules to 'make haiku' that are set to not be exported to the
524		// fuzzing infrastructure.
525		if config := fuzzModule.Properties.Fuzz_config; config != nil {
526			if ccModule.Host() && !BoolDefault(config.Fuzz_on_haiku_host, true) {
527				return
528			} else if !BoolDefault(config.Fuzz_on_haiku_device, true) {
529				return
530			}
531		}
532
533		s.fuzzTargets[module.Name()] = true
534		archDirs[archOs] = append(archDirs[archOs], fileToZip{fuzzZip, ""})
535	})
536
537	var archOsList []archOs
538	for archOs := range archDirs {
539		archOsList = append(archOsList, archOs)
540	}
541	sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].dir < archOsList[j].dir })
542
543	for _, archOs := range archOsList {
544		filesToZip := archDirs[archOs]
545		arch := archOs.arch
546		hostOrTarget := archOs.hostOrTarget
547		builder := android.NewRuleBuilder(pctx, ctx)
548		outputFile := android.PathForOutput(ctx, "fuzz-"+hostOrTarget+"-"+arch+".zip")
549		s.packages = append(s.packages, outputFile)
550
551		command := builder.Command().BuiltTool("soong_zip").
552			Flag("-j").
553			FlagWithOutput("-o ", outputFile).
554			Flag("-L 0") // No need to try and re-compress the zipfiles.
555
556		for _, fileToZip := range filesToZip {
557			if fileToZip.DestinationPathPrefix != "" {
558				command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix)
559			} else {
560				command.Flag("-P ''")
561			}
562			command.FlagWithInput("-f ", fileToZip.SourceFilePath)
563		}
564
565		builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget,
566			"Create fuzz target packages for "+arch+"-"+hostOrTarget)
567	}
568}
569
570func (s *fuzzPackager) MakeVars(ctx android.MakeVarsContext) {
571	packages := s.packages.Strings()
572	sort.Strings(packages)
573	sort.Strings(s.sharedLibInstallStrings)
574	// TODO(mitchp): Migrate this to use MakeVarsContext::DistForGoal() when it's
575	// ready to handle phony targets created in Soong. In the meantime, this
576	// exports the phony 'fuzz' target and dependencies on packages to
577	// core/main.mk so that we can use dist-for-goals.
578	ctx.Strict("SOONG_FUZZ_PACKAGING_ARCH_MODULES", strings.Join(packages, " "))
579	ctx.Strict("FUZZ_TARGET_SHARED_DEPS_INSTALL_PAIRS",
580		strings.Join(s.sharedLibInstallStrings, " "))
581
582	// Preallocate the slice of fuzz targets to minimise memory allocations.
583	fuzzTargets := make([]string, 0, len(s.fuzzTargets))
584	for target, _ := range s.fuzzTargets {
585		fuzzTargets = append(fuzzTargets, target)
586	}
587	sort.Strings(fuzzTargets)
588	ctx.Strict("ALL_FUZZ_TARGETS", strings.Join(fuzzTargets, " "))
589}
590