blob: 70395fd80ac945a92de49cf1bbec901a349f476a [file] [log] [blame]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001# -*- coding: utf-8 -*-
2# Copyright 2018 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Script for performing tasks that are useful for fuzzer development.
7
8Run "cros_fuzz" in the chroot for a list of command or "cros_fuzz $COMMAND
9--help" for their full details. Below is a summary of commands that the script
10can perform:
11
12coverage: Generate a coverage report for a given fuzzer (specified by "--fuzzer"
13 option). You almost certainly want to specify the package to build (using
14 the "--package" option) so that a coverage build is done, since a coverage
15 build is needed to generate a report. If your fuzz target is running on
16 ClusterFuzz already, you can use the "--download" option to download the
17 corpus from ClusterFuzz. Otherwise, you can use the "--corpus" option to
18 specify the path of the corpus to run the fuzzer on and generate a report.
19 The corpus will be copied to the sysroot so that the fuzzer can use it.
20 Note that "--download" and "--corpus" are mutually exclusive.
21
22reproduce: Runs the fuzzer specified by the "--fuzzer" option on a testcase
23 (path specified by the "--testcase" argument). Optionally does a build when
24 the "--package" option is used. The type of build can be specified using the
25 "--build_type" argument.
26
27download: Downloads the corpus from ClusterFuzz of the fuzzer specified by the
28 "--fuzzer" option. The path of the directory the corpus directory is
29 downloaded to can be specified using the "--directory" option.
30
31shell: Sets up the sysroot for fuzzing and then chroots into the sysroot giving
32 you a shell that is ready to fuzz.
33
34setup: Sets up the sysroot for fuzzing (done prior to doing "reproduce", "shell"
35 and "coverage" commands).
36
37cleanup: Undoes "setup".
38
39Note that cros_fuzz will print every shell command it runs if you set the
40log-level to debug ("--log-level debug"). Otherwise it will print commands that
41fail.
42"""
43
44from __future__ import print_function
45
46import os
47import shutil
Mike Frysinger03b983f2020-02-21 02:31:49 -050048import sys
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080049
50from elftools.elf.elffile import ELFFile
51import lddtree
52
53from chromite.lib import commandline
54from chromite.lib import constants
55from chromite.lib import cros_build_lib
56from chromite.lib import cros_logging as logging
57from chromite.lib import gs
58from chromite.lib import osutils
Manoj Guptae5e1e612019-10-21 12:39:57 -070059from chromite.lib import portage_util
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080060
Mike Frysinger03b983f2020-02-21 02:31:49 -050061
62assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
63
64
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080065# Directory in sysroot's /tmp directory that this script will use for files it
66# needs to write. We need a directory to write files to because this script uses
67# external programs that must write and read to/from files and because these
68# must be run inside the sysroot and thus are usually unable to read or write
69# from directories in the chroot environment this script is executed in.
70SCRIPT_STORAGE_DIRECTORY = 'fuzz'
71SCRIPT_STORAGE_PATH = os.path.join('/', 'tmp', SCRIPT_STORAGE_DIRECTORY)
72
73# Names of subdirectories in "fuzz" directory used by this script to store
74# things.
75CORPUS_DIRECTORY_NAME = 'corpus'
76TESTCASE_DIRECTORY_NAME = 'testcase'
77COVERAGE_REPORT_DIRECTORY_NAME = 'coverage-report'
78
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080079# Constants for names of libFuzzer command line options.
80RUNS_OPTION_NAME = 'runs'
81MAX_TOTAL_TIME_OPTION_NAME = 'max_total_time'
82
83# The default path a profraw file written by a clang coverage instrumented
84# binary when run by this script (default is current working directory).
85DEFAULT_PROFRAW_PATH = '/default.profraw'
86
87# Constants for libFuzzer command line values.
88# 0 runs means execute everything in the corpus and do no mutations.
89RUNS_DEFAULT_VALUE = 0
90# An arbitrary but short amount of time to run a fuzzer to get some coverage
91# data (when a corpus hasn't been provided and we aren't told to download one.
92MAX_TOTAL_TIME_DEFAULT_VALUE = 30
93
94
95class BuildType(object):
96 """Class to hold the different kinds of build types."""
97
98 ASAN = 'asan'
Manoj Guptae207b562019-05-02 11:30:35 -070099 MSAN = 'msan'
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800100 UBSAN = 'ubsan'
101 COVERAGE = 'coverage'
102 STANDARD = ''
103
104 # Build types that users can specify.
Manoj Guptae207b562019-05-02 11:30:35 -0700105 CHOICES = (ASAN, MSAN, UBSAN, COVERAGE)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800106
107
108class SysrootPath(object):
109 """Class for representing a path that is in the sysroot.
110
111 Useful for dealing with paths that we must interact with when chrooted into
112 the sysroot and outside of it.
113
114 For example, if we need to interact with the "/tmp" directory of the sysroot,
115 SysrootPath('/tmp').sysroot returns the path of the directory if we are in
116 chrooted into the sysroot, i.e. "/tmp".
117
118 SysrootPath('/tmp').chroot returns the path of the directory when in the
119 cros_sdk i.e. SYSROOT_DIRECTORY + "/tmp" (this will probably be
120 "/build/amd64-generic/tmp" in most cases).
121 """
122
123 # The actual path to the sysroot (from within the chroot).
124 path_to_sysroot = None
125
126 def __init__(self, path):
127 """Constructor.
128
129 Args:
130 path: An absolute path representing something in the sysroot.
131 """
132
133 assert path.startswith('/')
134 if self.IsPathInSysroot(path):
135 path = self.FromChrootPathInSysroot(os.path.abspath(path))
136 self.path_list = path.split(os.sep)[1:]
137
138 @classmethod
139 def SetPathToSysroot(cls, board):
140 """Sets path_to_sysroot
141
142 Args:
143 board: The board we will use for our sysroot.
144
145 Returns:
146 The path to the sysroot (the value of path_to_sysroot).
147 """
148 cls.path_to_sysroot = cros_build_lib.GetSysroot(board)
149 return cls.path_to_sysroot
150
151 @property
152 def chroot(self):
153 """Get the path of the object in the Chrome OS SDK chroot.
154
155 Returns:
156 The path this object represents when chrooted into the sysroot.
157 """
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800158 assert self.path_to_sysroot is not None, 'set SysrootPath.path_to_sysroot'
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800159 return os.path.join(self.path_to_sysroot, *self.path_list)
160
161 @property
162 def sysroot(self):
163 """Get the path of the object when in the sysroot.
164
165 Returns:
166 The path this object represents when in the Chrome OS SDK .
167 """
168 return os.path.join('/', *self.path_list)
169
170 @classmethod
171 def IsPathInSysroot(cls, path):
172 """Is a path in the sysroot.
173
174 Args:
175 path: The path we are checking is in the sysroot.
176
177 Returns:
178 True if path is within the sysroot's path in the chroot.
179 """
180 assert cls.path_to_sysroot
181 return path.startswith(cls.path_to_sysroot)
182
183 @classmethod
184 def FromChrootPathInSysroot(cls, path):
185 """Converts a chroot-relative path that is in sysroot into sysroot-relative.
186
187 Args:
188 path: The chroot-relative path we are converting to sysroot relative.
189
190 Returns:
191 The sysroot relative version of |path|.
192 """
193 assert cls.IsPathInSysroot(path)
194 common_prefix = os.path.commonprefix([cls.path_to_sysroot, path])
195 return path[len(common_prefix):]
196
197
198def GetScriptStoragePath(relative_path):
199 """Get the SysrootPath representing a script storage path.
200
201 Get a path of a directory this script will store things in.
202
203 Args:
204 relative_path: The path relative to the root of the script storage
205 directory.
206
207 Returns:
208 The SysrootPath representing absolute path of |relative_path| in the script
209 storage directory.
210 """
211 path = os.path.join(SCRIPT_STORAGE_PATH, relative_path)
212 return SysrootPath(path)
213
214
215def GetSysrootPath(path):
216 """Get the chroot-relative path of a path in the sysroot.
217
218 Args:
219 path: An absolute path in the sysroot that we will get the path in the
220 chroot for.
221
222 Returns:
223 The chroot-relative path of |path| in the sysroot.
224 """
225 return SysrootPath(path).chroot
226
227
228def GetCoverageDirectory(fuzzer):
229 """Get a coverage report directory for a fuzzer
230
231 Args:
232 fuzzer: The fuzzer to get the coverage report directory for.
233
234 Returns:
235 The location of the coverage report directory for the |fuzzer|.
236 """
237 relative_path = os.path.join(COVERAGE_REPORT_DIRECTORY_NAME, fuzzer)
238 return GetScriptStoragePath(relative_path)
239
240
241def GetFuzzerSysrootPath(fuzzer):
242 """Get the path in the sysroot of a fuzzer.
243
244 Args:
245 fuzzer: The fuzzer to get the path of.
246
247 Returns:
248 The path of |fuzzer| in the sysroot.
249 """
250 return SysrootPath(os.path.join('/', 'usr', 'libexec', 'fuzzers', fuzzer))
251
252
253def GetProfdataPath(fuzzer):
254 """Get the profdata file of a fuzzer.
255
256 Args:
257 fuzzer: The fuzzer to get the profdata file of.
258
259 Returns:
260 The path of the profdata file that should be used by |fuzzer|.
261 """
262 return GetScriptStoragePath('%s.profdata' % fuzzer)
263
264
265def GetPathForCopy(parent_directory, chroot_path):
266 """Returns a path in the script storage directory to copy chroot_path.
267
268 Returns a SysrootPath representing the location where |chroot_path| should
269 copied. This path will be in the parent_directory which will be in the script
270 storage directory.
271 """
272 basename = os.path.basename(chroot_path)
273 return GetScriptStoragePath(os.path.join(parent_directory, basename))
274
275
276def CopyCorpusToSysroot(src_corpus_path):
277 """Copies corpus into the sysroot.
278
279 Copies corpus into the sysroot. Doesn't copy if corpus is already in sysroot.
280
281 Args:
282 src_corpus_path: A path (in the chroot) to a corpus that will be copied into
283 sysroot.
284
285 Returns:
286 The path in the sysroot that the corpus was copied to.
287 """
288 if src_corpus_path is None:
289 return None
290
291 if SysrootPath.IsPathInSysroot(src_corpus_path):
292 # Don't copy if |src_testcase_path| is already in sysroot. Just return it in
293 # the format expected by the caller.
294 return SysrootPath(src_corpus_path)
295
296 dest_corpus_path = GetPathForCopy(CORPUS_DIRECTORY_NAME, src_corpus_path)
Manoj Gupta639d30f2019-10-28 16:24:25 -0700297 osutils.RmDir(dest_corpus_path.chroot, ignore_missing=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800298 shutil.copytree(src_corpus_path, dest_corpus_path.chroot)
299 return dest_corpus_path
300
301
302def CopyTestcaseToSysroot(src_testcase_path):
303 """Copies a testcase into the sysroot.
304
305 Copies a testcase into the sysroot. Doesn't copy if testcase is already in
306 sysroot.
307
308 Args:
309 src_testcase_path: A path (in the chroot) to a testcase that will be copied
310 into sysroot.
311
312 Returns:
313 The path in the sysroot that the testcase was copied to.
314 """
315 if SysrootPath.IsPathInSysroot(src_testcase_path):
316 # Don't copy if |src_testcase_path| is already in sysroot. Just return it in
317 # the format expected by the caller.
318 return SysrootPath(src_testcase_path)
319
320 dest_testcase_path = GetPathForCopy(TESTCASE_DIRECTORY_NAME,
321 src_testcase_path)
322 osutils.SafeMakedirsNonRoot(os.path.dirname(dest_testcase_path.chroot))
323 osutils.SafeUnlink(dest_testcase_path.chroot)
324
325 shutil.copy(src_testcase_path, dest_testcase_path.chroot)
326 return dest_testcase_path
327
328
Mike Frysinger45602c72019-09-22 02:15:11 -0400329def sudo_run(*args, **kwargs):
330 """Wrapper around cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800331
Mike Frysinger45602c72019-09-22 02:15:11 -0400332 Wrapper that calls cros_build_lib.sudo_run but sets debug_level by
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800333 default.
334
335 Args:
Mike Frysinger45602c72019-09-22 02:15:11 -0400336 *args: Positional arguments to pass to cros_build_lib.sudo_run.
337 *kwargs: Keyword arguments to pass to cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800338
339 Returns:
Mike Frysinger45602c72019-09-22 02:15:11 -0400340 The value returned by calling cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800341 """
342 kwargs.setdefault('debug_level', logging.DEBUG)
Mike Frysinger45602c72019-09-22 02:15:11 -0400343 return cros_build_lib.sudo_run(*args, **kwargs)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800344
345
346def GetLibFuzzerOption(option_name, option_value):
347 """Gets the libFuzzer command line option with the specified name and value.
348
349 Args:
350 option_name: The name of the libFuzzer option.
351 option_value: The value of the libFuzzer option.
352
353 Returns:
354 The libFuzzer option composed of |option_name| and |option_value|.
355 """
356 return '-%s=%s' % (option_name, option_value)
357
358
359def IsOptionLimit(option):
360 """Determines if fuzzer option limits fuzzing time."""
361 for limit_name in [MAX_TOTAL_TIME_OPTION_NAME, RUNS_OPTION_NAME]:
362 if option.startswith('-%s' % limit_name):
363 return True
364
365 return False
366
367
368def LimitFuzzing(fuzz_command, corpus):
369 """Limits how long fuzzing will go if unspecified.
370
371 Adds a reasonable limit on how much fuzzing will be done unless there already
372 is some kind of limit. Mutates fuzz_command.
373
374 Args:
375 fuzz_command: A command to run a fuzzer. Used to determine if a limit needs
376 to be set. Mutated if it is needed to specify a limit.
377 corpus: The corpus that will be passed to the fuzzer. If not None then
378 fuzzing is limited by running everything in the corpus once.
379 """
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800380 if any(IsOptionLimit(x) for x in fuzz_command[1:]):
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800381 # Don't do anything if there is already a limit.
382 return
383
384 if corpus:
385 # If there is a corpus, just run everything in the corpus once.
386 fuzz_command.append(
387 GetLibFuzzerOption(RUNS_OPTION_NAME, RUNS_DEFAULT_VALUE))
388 return
389
390 # Since there is no corpus, just fuzz for 30 seconds.
391 logging.info('Limiting fuzzing to %s seconds.', MAX_TOTAL_TIME_DEFAULT_VALUE)
392 max_total_time_option = GetLibFuzzerOption(MAX_TOTAL_TIME_OPTION_NAME,
393 MAX_TOTAL_TIME_DEFAULT_VALUE)
394 fuzz_command.append(max_total_time_option)
395
396
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800397def GetFuzzExtraEnv(extra_options=None):
398 """Gets extra_env for fuzzing.
399
400 Gets environment varaibles and values for running libFuzzer. Sets defaults and
401 allows user to specify extra sanitizer options.
402
403 Args:
404 extra_options: A dict containing sanitizer options to set in addition to the
405 defaults.
406
407 Returns:
408 A dict containing environment variables and their values that can be used in
409 the environment libFuzzer runs in.
410 """
411 if extra_options is None:
412 extra_options = {}
413
414 # log_path must be set because Chrome OS's patched compiler changes it.
415 options_dict = {'log_path': 'stderr'}
416 options_dict.update(extra_options)
417 sanitizer_options = ':'.join('%s=%s' % x for x in options_dict.items())
418 sanitizers = ('ASAN', 'MSAN', 'UBSAN')
419 return {x + '_OPTIONS': sanitizer_options for x in sanitizers}
420
421
Manoj Gupta5ca17652019-05-13 11:15:33 -0700422def RunFuzzer(fuzzer,
423 corpus_path=None,
424 fuzz_args='',
425 testcase_path=None,
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800426 crash_expected=False):
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800427 """Runs the fuzzer while chrooted into the sysroot.
428
429 Args:
430 fuzzer: The fuzzer to run.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800431 corpus_path: A path to a corpus (not necessarily in the sysroot) to run the
432 fuzzer on.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800433 fuzz_args: Additional arguments to pass to the fuzzer when running it.
434 testcase_path: A path to a testcase (not necessarily in the sysroot) to run
435 the fuzzer on.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800436 crash_expected: Is it normal for the fuzzer to crash on this run?
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800437 """
438 logging.info('Running fuzzer: %s', fuzzer)
439 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
440 fuzz_command = [fuzzer_sysroot_path.sysroot]
441 fuzz_command += fuzz_args.split()
442
443 if testcase_path:
444 fuzz_command.append(testcase_path)
445 else:
446 LimitFuzzing(fuzz_command, corpus_path)
447
448 if corpus_path:
449 fuzz_command.append(corpus_path)
450
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800451 if crash_expected:
452 # Don't return nonzero when fuzzer OOMs, leaks, or timesout, since we don't
453 # want an exception in those cases. The user may be trying to reproduce
454 # those issues.
455 fuzz_command += ['-error_exitcode=0', '-timeout_exitcode=0']
456
457 # We must set exitcode=0 or else the fuzzer will return nonzero on
458 # successful reproduction.
459 sanitizer_options = {'exitcode': '0'}
460 else:
461 sanitizer_options = {}
462
463 extra_env = GetFuzzExtraEnv(sanitizer_options)
464 RunSysrootCommand(fuzz_command, extra_env=extra_env, debug_level=logging.INFO)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800465
466
467def MergeProfraw(fuzzer):
468 """Merges profraw file from a fuzzer and creates a profdata file.
469
470 Args:
471 fuzzer: The fuzzer to merge the profraw file from.
472 """
473 profdata_path = GetProfdataPath(fuzzer)
474 command = [
475 'llvm-profdata',
476 'merge',
477 '-sparse',
478 DEFAULT_PROFRAW_PATH,
479 '-o',
480 profdata_path.sysroot,
481 ]
482
483 RunSysrootCommand(command)
484 return profdata_path
485
486
487def GenerateCoverageReport(fuzzer, shared_libraries):
488 """Generates an HTML coverage report from a fuzzer run.
489
490 Args:
491 fuzzer: The fuzzer to generate the coverage report for.
492 shared_libraries: Libraries loaded dynamically by |fuzzer|.
493
494 Returns:
495 The path of the coverage report.
496 """
497 fuzzer_path = GetFuzzerSysrootPath(fuzzer).chroot
498 command = ['llvm-cov', 'show', '-object', fuzzer_path]
499 for library in shared_libraries:
500 command += ['-object', library]
501
502 coverage_directory = GetCoverageDirectory(fuzzer)
503 command += [
504 '-format=html',
505 '-instr-profile=%s' % GetProfdataPath(fuzzer).chroot,
506 '-output-dir=%s' % coverage_directory.chroot,
507 ]
508
509 # TODO(metzman): Investigate error messages printed by this command.
Mike Frysinger0282d222019-12-17 17:15:48 -0500510 cros_build_lib.run(command, stderr=True, debug_level=logging.DEBUG)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800511 return coverage_directory
512
513
514def GetSharedLibraries(binary_path):
515 """Gets the shared libraries used by a binary.
516
517 Gets the shared libraries used by the binary. Based on GetSharedLibraries from
518 src/tools/code_coverage/coverage_utils.py in Chromium.
519
520 Args:
521 binary_path: The path to the binary we want to find the shared libraries of.
522
523 Returns:
524 The shared libraries used by |binary_path|.
525 """
526 logging.info('Finding shared libraries for targets (if any).')
527 shared_libraries = []
528 elf_dict = lddtree.ParseELF(
529 binary_path.chroot, root=SysrootPath.path_to_sysroot)
Mike Frysinger0bdbc102019-06-13 15:27:29 -0400530 for shared_library in elf_dict['libs'].values():
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800531 shared_library_path = shared_library['path']
532
533 if shared_library_path in shared_libraries:
534 continue
535
536 assert os.path.exists(shared_library_path), ('Shared library "%s" used by '
537 'the given target(s) does not '
538 'exist.' % shared_library_path)
539
540 if IsInstrumentedWithClangCoverage(shared_library_path):
541 # Do not add non-instrumented libraries. Otherwise, llvm-cov errors out.
542 shared_libraries.append(shared_library_path)
543
544 logging.debug('Found shared libraries (%d): %s.', len(shared_libraries),
545 shared_libraries)
546 logging.info('Finished finding shared libraries for targets.')
547 return shared_libraries
548
549
550def IsInstrumentedWithClangCoverage(binary_path):
551 """Determines if a binary is instrumented with clang source based coverage.
552
553 Args:
554 binary_path: The path of the binary (executable or library) we are checking
555 is instrumented with clang source based coverage.
556
557 Returns:
558 True if the binary is instrumented with clang source based coverage.
559 """
560 with open(binary_path, 'rb') as file_handle:
561 elf_file = ELFFile(file_handle)
562 return elf_file.get_section_by_name('__llvm_covmap') is not None
563
564
565def RunFuzzerAndGenerateCoverageReport(fuzzer, corpus, fuzz_args):
566 """Runs a fuzzer generates a coverage report and returns the report's path.
567
568 Gets a coverage report for a fuzzer.
569
570 Args:
571 fuzzer: The fuzzer to run and generate the coverage report for.
572 corpus: The path to a corpus to run the fuzzer on.
573 fuzz_args: Additional arguments to pass to the fuzzer.
574
575 Returns:
576 The path to the coverage report.
577 """
578 corpus_path = CopyCorpusToSysroot(corpus)
579 if corpus_path:
580 corpus_path = corpus_path.sysroot
581
582 RunFuzzer(fuzzer, corpus_path=corpus_path, fuzz_args=fuzz_args)
583 MergeProfraw(fuzzer)
584 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
585 shared_libraries = GetSharedLibraries(fuzzer_sysroot_path)
586 return GenerateCoverageReport(fuzzer, shared_libraries)
587
588
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800589def RunSysrootCommand(command, **kwargs):
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800590 """Runs command while chrooted into sysroot and returns the output.
591
592 Args:
593 command: A command to run in the sysroot.
Mike Frysinger45602c72019-09-22 02:15:11 -0400594 kwargs: Extra arguments to pass to cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800595
596 Returns:
Mike Frysinger45602c72019-09-22 02:15:11 -0400597 The result of a call to cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800598 """
599 command = ['chroot', SysrootPath.path_to_sysroot] + command
Mike Frysinger45602c72019-09-22 02:15:11 -0400600 return sudo_run(command, **kwargs)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800601
602
603def GetBuildExtraEnv(build_type):
604 """Gets the extra_env for building a package.
605
606 Args:
607 build_type: The type of build we want to do.
608
609 Returns:
610 The extra_env to use when building.
611 """
612 if build_type is None:
613 build_type = BuildType.ASAN
614
615 use_flags = os.environ.get('USE', '').split()
616 # Check that the user hasn't already set USE flags that we can set.
617 # No good way to iterate over an enum in python2.
618 for use_flag in BuildType.CHOICES:
619 if use_flag in use_flags:
Mike Frysinger968c1142020-05-09 00:37:56 -0400620 logging.warning('%s in USE flags. Please use --build_type instead.',
621 use_flag)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800622
623 # Set USE flags.
624 fuzzer_build_type = 'fuzzer'
625 use_flags += [fuzzer_build_type, build_type]
626 features_flags = os.environ.get('FEATURES', '').split()
627 if build_type == BuildType.COVERAGE:
628 # We must use ASan when doing coverage builds.
629 use_flags.append(BuildType.ASAN)
630 # Use noclean so that a coverage report can be generated based on the source
631 # code.
632 features_flags.append('noclean')
633
634 return {
635 'FEATURES': ' '.join(features_flags),
636 'USE': ' '.join(use_flags),
637 }
638
639
640def BuildPackage(package, board, build_type):
641 """Builds a package on a specified board.
642
643 Args:
644 package: The package to build. Nothing is built if None.
645 board: The board to build the package on.
Manoj Guptae207b562019-05-02 11:30:35 -0700646 build_type: The type of the build to do (e.g. asan, msan, ubsan, coverage).
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800647 """
648 if package is None:
649 return
650
651 logging.info('Building %s using %s.', package, build_type)
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800652 extra_env = GetBuildExtraEnv(build_type)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800653 build_packages_path = os.path.join(constants.SOURCE_ROOT, 'src', 'scripts',
654 'build_packages')
655 command = [
656 build_packages_path,
657 '--board',
658 board,
659 '--skip_chroot_upgrade',
660 package,
661 ]
Manoj Gupta5ca17652019-05-13 11:15:33 -0700662 # For msan builds, always use "--nousepkg" since all package needs to be
663 # instrumented with msan.
664 if build_type == BuildType.MSAN:
665 command += ['--nousepkg']
666
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800667 # Print the output of the build command. Do this because it is familiar to
668 # devs and we don't want to leave them not knowing about the build's progress
669 # for a long time.
Mike Frysinger45602c72019-09-22 02:15:11 -0400670 cros_build_lib.run(command, extra_env=extra_env)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800671
672
673def DownloadFuzzerCorpus(fuzzer, dest_directory=None):
674 """Downloads a corpus and returns its path.
675
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800676 Downloads a corpus to a subdirectory of dest_directory if specified and
677 returns path on the filesystem of the corpus. Asks users to authenticate
678 if permission to read from bucket is denied.
679
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800680 Args:
681 fuzzer: The name of the fuzzer who's corpus we want to download.
682 dest_directory: The directory to download the corpus to.
683
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800684 Returns:
685 The path to the downloaded corpus.
686
687 Raises:
688 gs.NoSuchKey: A corpus for the fuzzer doesn't exist.
689 gs.GSCommandError: The corpus failed to download for another reason.
690 """
691 if not fuzzer.startswith('chromeos_'):
692 # ClusterFuzz internally appends "chromeos_" to chromeos targets' names.
693 # Therefore we must do so in order to find the corpus.
694 fuzzer = 'chromeos_%s' % fuzzer
695
696 if dest_directory is None:
697 dest_directory = GetScriptStoragePath(CORPUS_DIRECTORY_NAME).chroot
698 osutils.SafeMakedirsNonRoot(dest_directory)
699
700 clusterfuzz_gcs_corpus_bucket = 'chromeos-corpus'
701 suburl = 'libfuzzer/%s' % fuzzer
702 gcs_path = gs.GetGsURL(
703 clusterfuzz_gcs_corpus_bucket,
704 for_gsutil=True,
705 public=False,
706 suburl=suburl)
707
708 dest_path = os.path.join(dest_directory, fuzzer)
709
710 try:
711 logging.info('Downloading corpus to %s.', dest_path)
712 ctx = gs.GSContext()
713 ctx.Copy(
714 gcs_path,
715 dest_directory,
716 recursive=True,
717 parallel=True,
718 debug_level=logging.DEBUG)
719 logging.info('Finished downloading corpus.')
720 except gs.GSNoSuchKey as exception:
721 logging.error('Corpus for fuzzer: %s does not exist.', fuzzer)
722 raise exception
723 # Try to authenticate if we were denied permission to access the corpus.
724 except gs.GSCommandError as exception:
725 logging.error(
726 'gsutil failed to download the corpus. You may need to log in. See:\n'
727 'https://chromium.googlesource.com/chromiumos/docs/+/master/gsutil.md'
728 '#setup\n'
729 'for instructions on doing this.')
730 raise exception
731
732 return dest_path
733
734
735def Reproduce(fuzzer, testcase_path):
736 """Runs a fuzzer in the sysroot on a testcase.
737
738 Args:
739 fuzzer: The fuzzer to run.
740 testcase_path: The path (not necessarily in the sysroot) of the testcase to
741 run the fuzzer on.
742 """
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800743 testcase_sysroot_path = CopyTestcaseToSysroot(testcase_path).sysroot
744 RunFuzzer(fuzzer, testcase_path=testcase_sysroot_path, crash_expected=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800745
746
747def SetUpSysrootForFuzzing():
748 """Sets up the the sysroot for fuzzing
749
750 Prepares the sysroot for fuzzing. Idempotent.
751 """
752 logging.info('Setting up sysroot for fuzzing.')
753 # TODO(metzman): Don't create devices or mount /proc, use platform2_test.py
754 # instead.
755 # Mount /proc in sysroot and setup dev there because they are needed by
756 # sanitizers.
757 proc_manager = ProcManager()
758 proc_manager.Mount()
759
760 # Setup devices in /dev that are needed by libFuzzer.
761 device_manager = DeviceManager()
762 device_manager.SetUp()
763
764 # Set up asan_symbolize.py, llvm-symbolizer, and llvm-profdata in the
765 # sysroot so that fuzzer output (including stack traces) can be symbolized
766 # and so that coverage reports can be generated.
767 tool_manager = ToolManager()
768 tool_manager.Install()
769
770 osutils.SafeMakedirsNonRoot(GetSysrootPath(SCRIPT_STORAGE_PATH))
771
772
773def CleanUpSysroot():
774 """Cleans up the the sysroot from SetUpSysrootForFuzzing.
775
776 Undoes SetUpSysrootForFuzzing. Idempotent.
777 """
778 logging.info('Cleaning up the sysroot.')
779 proc_manager = ProcManager()
780 proc_manager.Unmount()
781
782 device_manager = DeviceManager()
783 device_manager.CleanUp()
784
785 tool_manager = ToolManager()
786 tool_manager.Uninstall()
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800787 osutils.RmDir(GetSysrootPath(SCRIPT_STORAGE_PATH), ignore_missing=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800788
789
790class ToolManager(object):
791 """Class that installs or uninstalls fuzzing tools to/from the sysroot.
792
793 Install and Uninstall methods are idempotent. Both are safe to call at any
794 point.
795 """
796
797 # Path to asan_symbolize.py.
798 ASAN_SYMBOLIZE_PATH = os.path.join('/', 'usr', 'bin', 'asan_symbolize.py')
799
800 # List of LLVM binaries we must install in sysroot.
Manoj Guptafeb1b7a2019-02-20 11:04:05 -0800801 LLVM_BINARY_NAMES = ['gdbserver', 'llvm-symbolizer', 'llvm-profdata']
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800802
803 def __init__(self):
804 self.asan_symbolize_sysroot_path = GetSysrootPath(self.ASAN_SYMBOLIZE_PATH)
805
806 def Install(self):
807 """Installs tools to the sysroot."""
808 # Install asan_symbolize.py.
Mike Frysinger45602c72019-09-22 02:15:11 -0400809 sudo_run(['cp', self.ASAN_SYMBOLIZE_PATH, self.asan_symbolize_sysroot_path])
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800810 # Install the LLVM binaries.
811 # TODO(metzman): Build these tools so that we don't mess up when board is
812 # for a different ISA.
813 for llvm_binary in self._GetLLVMBinaries():
814 llvm_binary.Install()
815
816 def Uninstall(self):
817 """Uninstalls tools from the sysroot. Undoes Install."""
818 # Uninstall asan_symbolize.py.
819 osutils.SafeUnlink(self.asan_symbolize_sysroot_path, sudo=True)
820 # Uninstall the LLVM binaries.
821 for llvm_binary in self._GetLLVMBinaries():
822 llvm_binary.Uninstall()
823
824 def _GetLLVMBinaries(self):
825 """Creates LllvmBinary objects for each binary name in LLVM_BINARY_NAMES."""
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800826 return [LlvmBinary(x) for x in self.LLVM_BINARY_NAMES]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800827
828
829class LlvmBinary(object):
830 """Class for representing installing/uninstalling an LLVM binary in sysroot.
831
832 Install and Uninstall methods are idempotent. Both are safe to call at any
833 time.
834 """
835
836 # Path to the lddtree chromite script.
837 LDDTREE_SCRIPT_PATH = os.path.join(constants.CHROMITE_BIN_DIR, 'lddtree')
838
839 def __init__(self, binary):
840 self.binary = binary
841 self.install_dir = GetSysrootPath(
842 os.path.join('/', 'usr', 'libexec', binary))
843 self.binary_dir_path = GetSysrootPath(os.path.join('/', 'usr', 'bin'))
844 self.binary_chroot_dest_path = os.path.join(self.binary_dir_path, binary)
845
846 def Uninstall(self):
847 """Removes an LLVM binary from sysroot. Undoes Install."""
848 osutils.RmDir(self.install_dir, ignore_missing=True, sudo=True)
849 osutils.SafeUnlink(self.binary_chroot_dest_path, sudo=True)
850
851 def Install(self):
852 """Installs (sets up) an LLVM binary in the sysroot.
853
854 Sets up an llvm binary in the sysroot so that it can be run there.
855 """
856 # Create a directory for installing |binary| and all of its dependencies in
857 # the sysroot.
858 binary_rel_path = ['usr', 'bin', self.binary]
859 binary_chroot_path = os.path.join('/', *binary_rel_path)
Manoj Guptafeb1b7a2019-02-20 11:04:05 -0800860 if not os.path.exists(binary_chroot_path):
861 logging.warning('Cannot copy %s, file does not exist in chroot.',
862 binary_chroot_path)
863 logging.warning('Functionality provided by %s will be missing.',
864 binary_chroot_path)
865 return
866
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800867 osutils.SafeMakedirsNonRoot(self.install_dir)
868
869 # Copy the binary and everything needed to run it into the sysroot.
870 cmd = [
871 self.LDDTREE_SCRIPT_PATH,
872 '-v',
873 '--generate-wrappers',
874 '--root',
875 '/',
876 '--copy-to-tree',
877 self.install_dir,
878 binary_chroot_path,
879 ]
Mike Frysinger45602c72019-09-22 02:15:11 -0400880 sudo_run(cmd)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800881
882 # Create a symlink to the copy of the binary (we can't do lddtree in
883 # self.binary_dir_path). Note that symlink should be relative so that it
884 # will be valid when chrooted into the sysroot.
885 rel_path = os.path.relpath(self.install_dir, self.binary_dir_path)
886 link_path = os.path.join(rel_path, *binary_rel_path)
887 osutils.SafeSymlink(link_path, self.binary_chroot_dest_path, sudo=True)
888
889
890class DeviceManager(object):
891 """Class that creates or removes devices from /dev in sysroot.
892
893 SetUp and CleanUp methods are idempotent. Both are safe to call at any point.
894 """
895
896 DEVICE_MKNOD_PARAMS = {
897 'null': (666, 3),
898 'random': (444, 8),
899 'urandom': (444, 9),
900 }
901
902 MKNOD_MAJOR = '1'
903
904 def __init__(self):
905 self.dev_path_chroot = GetSysrootPath('/dev')
906
907 def _GetDevicePath(self, device_name):
908 """Returns the path of |device_name| in sysroot's /dev."""
909 return os.path.join(self.dev_path_chroot, device_name)
910
911 def SetUp(self):
912 """Sets up devices in the sysroot's /dev.
913
914 Creates /dev/null, /dev/random, and /dev/urandom. If they already exist then
915 recreates them.
916 """
917 self.CleanUp()
918 osutils.SafeMakedirsNonRoot(self.dev_path_chroot)
Mike Frysinger0bdbc102019-06-13 15:27:29 -0400919 for device, mknod_params in self.DEVICE_MKNOD_PARAMS.items():
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800920 device_path = self._GetDevicePath(device)
921 self._MakeCharDevice(device_path, *mknod_params)
922
923 def CleanUp(self):
924 """Cleans up devices in the sysroot's /dev. Undoes SetUp.
925
926 Removes /dev/null, /dev/random, and /dev/urandom if they exist.
927 """
928 for device in self.DEVICE_MKNOD_PARAMS:
929 device_path = self._GetDevicePath(device)
930 if os.path.exists(device_path):
931 # Use -r since dev/null is sometimes a directory.
Mike Frysinger45602c72019-09-22 02:15:11 -0400932 sudo_run(['rm', '-r', device_path])
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800933
934 def _MakeCharDevice(self, path, mode, minor):
935 """Make a character device."""
936 mode = str(mode)
937 minor = str(minor)
938 command = ['mknod', '-m', mode, path, 'c', self.MKNOD_MAJOR, minor]
Mike Frysinger45602c72019-09-22 02:15:11 -0400939 sudo_run(command)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800940
941
942class ProcManager(object):
943 """Class that mounts or unmounts /proc in sysroot.
944
945 Mount and Unmount are idempotent. Both are safe to call at any point.
946 """
947
948 PROC_PATH = '/proc'
949
950 def __init__(self):
951 self.proc_path_chroot = GetSysrootPath(self.PROC_PATH)
952 self.is_mounted = osutils.IsMounted(self.proc_path_chroot)
953
954 def Unmount(self):
955 """Unmounts /proc in chroot. Undoes Mount."""
956 if not self.is_mounted:
957 return
958 osutils.UmountDir(self.proc_path_chroot, cleanup=False)
959
960 def Mount(self):
961 """Mounts /proc in chroot. Remounts it if already mounted."""
962 self.Unmount()
963 osutils.MountDir(
964 self.PROC_PATH,
965 self.proc_path_chroot,
966 'proc',
967 debug_level=logging.DEBUG)
968
969
970def EnterSysrootShell():
971 """Spawns and gives user access to a bash shell in the sysroot."""
972 command = ['/bin/bash', '-i']
973 return RunSysrootCommand(
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800974 command,
975 extra_env=GetFuzzExtraEnv(),
976 debug_level=logging.INFO,
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500977 check=False).returncode
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800978
979
980def StripFuzzerPrefixes(fuzzer_name):
981 """Strip the prefix ClusterFuzz uses in case they are specified.
982
983 Strip the prefixes used by ClusterFuzz if the users has included them by
984 accident.
985
986 Args:
987 fuzzer_name: The fuzzer who's name may contain prefixes.
988
989 Returns:
990 The name of the fuzz target without prefixes.
991 """
992 initial_name = fuzzer_name
993
994 def StripPrefix(prefix):
995 if fuzzer_name.startswith(prefix):
996 return fuzzer_name[len(prefix):]
997 return fuzzer_name
998
999 clusterfuzz_prefixes = ['libFuzzer_', 'chromeos_']
1000
1001 for prefix in clusterfuzz_prefixes:
1002 fuzzer_name = StripPrefix(prefix)
1003
1004 if initial_name != fuzzer_name:
Mike Frysinger968c1142020-05-09 00:37:56 -04001005 logging.warning(
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001006 '%s contains a prefix from ClusterFuzz (one or more of %s) that is not '
Mike Frysinger80de5012019-08-01 14:10:53 -04001007 "part of the fuzzer's name. Interpreting --fuzzer as %s.",
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001008 initial_name, clusterfuzz_prefixes, fuzzer_name)
1009
1010 return fuzzer_name
1011
1012
1013def ExecuteShellCommand():
1014 """Executes the "shell" command.
1015
1016 Sets up the sysroot for fuzzing and gives user access to a bash shell it
1017 spawns in the sysroot.
1018
1019 Returns:
1020 The exit code of the shell command.
1021 """
1022 SetUpSysrootForFuzzing()
1023 return EnterSysrootShell()
1024
1025
1026def ExecuteSetupCommand():
1027 """Executes the "setup" command. Wrapper for SetUpSysrootForFuzzing.
1028
1029 Sets up the sysroot for fuzzing.
1030 """
1031 SetUpSysrootForFuzzing()
1032
1033
1034def ExecuteCleanupCommand():
1035 """Executes the "cleanup" command. Wrapper for CleanUpSysroot.
1036
1037 Undoes pre-fuzzing setup.
1038 """
1039 CleanUpSysroot()
1040
1041
1042def ExecuteCoverageCommand(options):
1043 """Executes the "coverage" command.
1044
1045 Executes the "coverage" command by optionally doing a coverage build of a
1046 package, optionally downloading the fuzzer's corpus, optionally copying it
1047 into the sysroot, running the fuzzer and then generating a coverage report
1048 for the user to view. Causes program to exit if fuzzer is not instrumented
1049 with source based coverage.
1050
1051 Args:
1052 options: The parsed arguments passed to this program.
1053 """
1054 BuildPackage(options.package, options.board, BuildType.COVERAGE)
1055
1056 fuzzer = StripFuzzerPrefixes(options.fuzzer)
1057 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
1058 if not IsInstrumentedWithClangCoverage(fuzzer_sysroot_path.chroot):
1059 # Don't run the fuzzer if it isn't instrumented with source based coverage.
1060 # Quit and let the user know how to build the fuzzer properly.
1061 cros_build_lib.Die(
1062 '%s is not instrumented with source based coverage.\nSpecify --package '
1063 'to do a coverage build or build with USE flag: "coverage".', fuzzer)
1064
1065 corpus = options.corpus
1066 if options.download:
1067 corpus = DownloadFuzzerCorpus(options.fuzzer)
1068
1069 # Set up sysroot for fuzzing.
1070 SetUpSysrootForFuzzing()
1071
1072 coverage_report_path = RunFuzzerAndGenerateCoverageReport(
1073 fuzzer, corpus, options.fuzz_args)
1074
1075 # Get path on host so user can access it with their browser.
1076 # TODO(metzman): Add the ability to convert to host paths to path_util.
1077 external_trunk_path = os.getenv('EXTERNAL_TRUNK_PATH')
1078 coverage_report_host_path = os.path.join(external_trunk_path, 'chroot',
1079 coverage_report_path.chroot[1:])
1080 print('Coverage report written to file://%s/index.html' %
1081 coverage_report_host_path)
1082
1083
1084def ExecuteDownloadCommand(options):
1085 """Executes the "download" command. Wrapper around DownloadFuzzerCorpus."""
1086 DownloadFuzzerCorpus(StripFuzzerPrefixes(options.fuzzer), options.directory)
1087
1088
1089def ExecuteReproduceCommand(options):
1090 """Executes the "reproduce" command.
1091
1092 Executes the "reproduce" command by Running a fuzzer on a testcase.
1093 May build the fuzzer before running.
1094
1095 Args:
1096 options: The parsed arguments passed to this program.
1097 """
1098 if options.build_type and not options.package:
1099 raise Exception('Cannot specify --build_type without specifying --package.')
1100
Manoj Gupta5ca17652019-05-13 11:15:33 -07001101 # Verify that "msan-fuzzer" profile is being used with msan.
1102 # Check presence of "-fsanitize=memory" in CFLAGS.
1103 if options.build_type == BuildType.MSAN:
1104 cmd = ['portageq-%s' % options.board, 'envvar', 'CFLAGS']
Manoj Guptabc6fc7f2020-04-17 19:24:24 -07001105 cflags = cros_build_lib.run(
1106 cmd, capture_output=True, encoding='utf-8').stdout.splitlines()
Manoj Gupta5ca17652019-05-13 11:15:33 -07001107 check_string = '-fsanitize=memory'
1108 if not any(check_string in s for s in cflags):
1109 logging.error(
1110 '-fsanitize=memory not found in CFLAGS. '
1111 'Use "setup_board --board=amd64-generic --profile=msan-fuzzer" '
1112 'for MSan Fuzzing Builds.')
1113 raise Exception('Incompatible profile used for msan fuzzing.')
1114
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001115 BuildPackage(options.package, options.board, options.build_type)
1116 SetUpSysrootForFuzzing()
1117 Reproduce(StripFuzzerPrefixes(options.fuzzer), options.testcase)
1118
Manoj Guptae5e1e612019-10-21 12:39:57 -07001119
Manoj Guptaec08b812019-10-10 14:21:16 -07001120def InstallBaseDependencies(options):
Mike Frysingerdf1d0b02019-11-12 17:44:12 -05001121 """Installs the base packages needed to chroot in board sysroot.
Manoj Guptaec08b812019-10-10 14:21:16 -07001122
1123 Args:
1124 options: The parsed arguments passed to this program.
1125 """
Manoj Guptae5e1e612019-10-21 12:39:57 -07001126 package = 'virtual/implicit-system'
1127 if not portage_util.IsPackageInstalled(
1128 package, sysroot=SysrootPath.path_to_sysroot):
1129 build_type = getattr(options, 'build_type', None)
1130 BuildPackage(package, options.board, build_type)
1131
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001132
1133def ParseArgs(argv):
1134 """Parses program arguments.
1135
1136 Args:
1137 argv: The program arguments we want to parse.
1138
1139 Returns:
1140 An options object which will tell us which command to run and which options
1141 to use for that command.
1142 """
1143 parser = commandline.ArgumentParser(description=__doc__)
1144
1145 parser.add_argument(
1146 '--board',
1147 default=cros_build_lib.GetDefaultBoard(),
1148 help='Board on which to run test.')
1149
1150 subparsers = parser.add_subparsers(dest='command')
1151
1152 subparsers.add_parser('cleanup', help='Undo setup command.')
1153 coverage_parser = subparsers.add_parser(
1154 'coverage', help='Get a coverage report for a fuzzer.')
1155
1156 coverage_parser.add_argument('--package', help='Package to build.')
1157
1158 corpus_parser = coverage_parser.add_mutually_exclusive_group()
1159 corpus_parser.add_argument('--corpus', help='Corpus to run fuzzer on.')
1160
1161 corpus_parser.add_argument(
1162 '--download',
1163 action='store_true',
1164 help='Generate coverage report based on corpus from ClusterFuzz.')
1165
1166 coverage_parser.add_argument(
1167 '--fuzzer',
1168 required=True,
1169 help='The fuzz target to generate a coverage report for.')
1170
1171 coverage_parser.add_argument(
1172 '--fuzz-args',
1173 default='',
1174 help='Arguments to pass libFuzzer. '
1175 'Please use an equals sign or parsing will fail '
1176 '(i.e. --fuzzer_args="-rss_limit_mb=2048 -print_funcs=1").')
1177
1178 download_parser = subparsers.add_parser('download', help='Download a corpus.')
1179
1180 download_parser.add_argument(
1181 '--directory', help='Path to directory to download the corpus to.')
1182
1183 download_parser.add_argument(
1184 '--fuzzer', required=True, help='Fuzzer to download the corpus for.')
1185
1186 reproduce_parser = subparsers.add_parser(
1187 'reproduce', help='Run a fuzzer on a testcase.')
1188
1189 reproduce_parser.add_argument(
1190 '--testcase', required=True, help='Path of testcase to run fuzzer on.')
1191
1192 reproduce_parser.add_argument(
1193 '--fuzzer', required=True, help='Fuzzer to reproduce the crash on.')
1194
1195 reproduce_parser.add_argument('--package', help='Package to build.')
1196
1197 reproduce_parser.add_argument(
1198 '--build-type',
1199 choices=BuildType.CHOICES,
1200 help='Type of build.',
1201 type=str.lower) # Ignore sanitizer case.
1202
1203 subparsers.add_parser('setup', help='Set up the sysroot to test fuzzing.')
1204
1205 subparsers.add_parser(
1206 'shell',
1207 help='Set up sysroot for fuzzing and get a shell in the sysroot.')
1208
1209 opts = parser.parse_args(argv)
1210 opts.Freeze()
1211 return opts
1212
1213
1214def main(argv):
1215 """Parses arguments and executes a command.
1216
1217 Args:
1218 argv: The prorgram arguments.
1219
1220 Returns:
1221 0 on success. Non-zero on failure.
1222 """
1223 cros_build_lib.AssertInsideChroot()
1224 options = ParseArgs(argv)
1225 if options.board is None:
1226 logging.error('Please specify "--board" or set ".default_board".')
1227 return 1
1228
1229 SysrootPath.SetPathToSysroot(options.board)
1230
Manoj Guptaec08b812019-10-10 14:21:16 -07001231 InstallBaseDependencies(options)
1232
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001233 if options.command == 'cleanup':
1234 ExecuteCleanupCommand()
1235 elif options.command == 'coverage':
1236 ExecuteCoverageCommand(options)
1237 elif options.command == 'setup':
1238 ExecuteSetupCommand()
1239 elif options.command == 'download':
1240 ExecuteDownloadCommand(options)
1241 elif options.command == 'reproduce':
1242 ExecuteReproduceCommand(options)
1243 elif options.command == 'shell':
1244 return ExecuteShellCommand()
1245
1246 return 0