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