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