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