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