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