blob: 79b00d1a72e15b2cf0caff31fcb054c79a63189b [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2018 The ChromiumOS Authors
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08002# 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
Chris McDonald59650c32021-07-20 15:29:28 -060043import logging
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080044import os
45import shutil
46
Chris McDonald59650c32021-07-20 15:29:28 -060047from chromite.third_party import lddtree
48from chromite.third_party.pyelftools.elftools.elf.elffile import ELFFile
49
Mike Frysinger06a51c82021-04-06 11:39:17 -040050from chromite.lib import build_target_lib
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080051from chromite.lib import commandline
52from chromite.lib import constants
53from chromite.lib import cros_build_lib
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080054from chromite.lib import gs
55from chromite.lib import osutils
Manoj Guptae5e1e612019-10-21 12:39:57 -070056from chromite.lib import portage_util
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080057
Mike Frysinger03b983f2020-02-21 02:31:49 -050058
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080059# Directory in sysroot's /tmp directory that this script will use for files it
60# needs to write. We need a directory to write files to because this script uses
61# external programs that must write and read to/from files and because these
62# must be run inside the sysroot and thus are usually unable to read or write
63# from directories in the chroot environment this script is executed in.
Alex Klein1699fab2022-09-08 08:46:06 -060064SCRIPT_STORAGE_DIRECTORY = "fuzz"
65SCRIPT_STORAGE_PATH = os.path.join("/", "tmp", SCRIPT_STORAGE_DIRECTORY)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080066
67# Names of subdirectories in "fuzz" directory used by this script to store
68# things.
Alex Klein1699fab2022-09-08 08:46:06 -060069CORPUS_DIRECTORY_NAME = "corpus"
70TESTCASE_DIRECTORY_NAME = "testcase"
71COVERAGE_REPORT_DIRECTORY_NAME = "coverage-report"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080072
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080073# Constants for names of libFuzzer command line options.
Alex Klein1699fab2022-09-08 08:46:06 -060074RUNS_OPTION_NAME = "runs"
75MAX_TOTAL_TIME_OPTION_NAME = "max_total_time"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080076
77# The default path a profraw file written by a clang coverage instrumented
78# binary when run by this script (default is current working directory).
Alex Klein1699fab2022-09-08 08:46:06 -060079DEFAULT_PROFRAW_PATH = "/default.profraw"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080080
81# Constants for libFuzzer command line values.
82# 0 runs means execute everything in the corpus and do no mutations.
83RUNS_DEFAULT_VALUE = 0
84# An arbitrary but short amount of time to run a fuzzer to get some coverage
85# data (when a corpus hasn't been provided and we aren't told to download one.
86MAX_TOTAL_TIME_DEFAULT_VALUE = 30
87
88
89class BuildType(object):
Alex Klein1699fab2022-09-08 08:46:06 -060090 """Class to hold the different kinds of build types."""
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080091
Alex Klein1699fab2022-09-08 08:46:06 -060092 ASAN = "asan"
93 MSAN = "msan"
94 UBSAN = "ubsan"
95 COVERAGE = "coverage"
96 STANDARD = ""
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080097
Alex Klein1699fab2022-09-08 08:46:06 -060098 # Build types that users can specify.
99 CHOICES = (ASAN, MSAN, UBSAN, COVERAGE)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800100
101
102class SysrootPath(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600103 """Class for representing a path that is in the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800104
Alex Klein1699fab2022-09-08 08:46:06 -0600105 Useful for dealing with paths that we must interact with when chrooted into
106 the sysroot and outside of it.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800107
Alex Klein1699fab2022-09-08 08:46:06 -0600108 For example, if we need to interact with the "/tmp" directory of the sysroot,
109 SysrootPath('/tmp').sysroot returns the path of the directory if we are in
110 chrooted into the sysroot, i.e. "/tmp".
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800111
Alex Klein1699fab2022-09-08 08:46:06 -0600112 SysrootPath('/tmp').chroot returns the path of the directory when in the
113 cros_sdk i.e. SYSROOT_DIRECTORY + "/tmp" (this will probably be
114 "/build/amd64-generic/tmp" in most cases).
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800115 """
116
Alex Klein1699fab2022-09-08 08:46:06 -0600117 # The actual path to the sysroot (from within the chroot).
118 path_to_sysroot = None
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800119
Alex Klein1699fab2022-09-08 08:46:06 -0600120 def __init__(self, path):
121 """Constructor.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800122
Alex Klein1699fab2022-09-08 08:46:06 -0600123 Args:
124 path: An absolute path representing something in the sysroot.
125 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800126
Alex Klein1699fab2022-09-08 08:46:06 -0600127 assert path.startswith("/")
128 if self.IsPathInSysroot(path):
129 path = self.FromChrootPathInSysroot(os.path.abspath(path))
130 self.path_list = path.split(os.sep)[1:]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800131
Alex Klein1699fab2022-09-08 08:46:06 -0600132 @classmethod
133 def SetPathToSysroot(cls, board):
134 """Sets path_to_sysroot
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800135
Alex Klein1699fab2022-09-08 08:46:06 -0600136 Args:
137 board: The board we will use for our sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800138
Alex Klein1699fab2022-09-08 08:46:06 -0600139 Returns:
140 The path to the sysroot (the value of path_to_sysroot).
141 """
142 cls.path_to_sysroot = build_target_lib.get_default_sysroot_path(board)
143 return cls.path_to_sysroot
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800144
Alex Klein1699fab2022-09-08 08:46:06 -0600145 @property
146 def chroot(self):
147 """Get the path of the object in the Chrome OS SDK chroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800148
Alex Klein1699fab2022-09-08 08:46:06 -0600149 Returns:
150 The path this object represents when chrooted into the sysroot.
151 """
152 assert (
153 self.path_to_sysroot is not None
154 ), "set SysrootPath.path_to_sysroot"
155 return os.path.join(self.path_to_sysroot, *self.path_list)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800156
Alex Klein1699fab2022-09-08 08:46:06 -0600157 @property
158 def sysroot(self):
159 """Get the path of the object when in the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800160
Alex Klein1699fab2022-09-08 08:46:06 -0600161 Returns:
162 The path this object represents when in the Chrome OS SDK .
163 """
164 return os.path.join("/", *self.path_list)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800165
Alex Klein1699fab2022-09-08 08:46:06 -0600166 @classmethod
167 def IsPathInSysroot(cls, path):
168 """Is a path in the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800169
Alex Klein1699fab2022-09-08 08:46:06 -0600170 Args:
171 path: The path we are checking is in the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800172
Alex Klein1699fab2022-09-08 08:46:06 -0600173 Returns:
174 True if path is within the sysroot's path in the chroot.
175 """
176 assert cls.path_to_sysroot
177 return path.startswith(cls.path_to_sysroot)
178
179 @classmethod
180 def FromChrootPathInSysroot(cls, path):
181 """Converts a chroot-relative path that is in sysroot into sysroot-relative.
182
183 Args:
184 path: The chroot-relative path we are converting to sysroot relative.
185
186 Returns:
187 The sysroot relative version of |path|.
188 """
189 assert cls.IsPathInSysroot(path)
190 common_prefix = os.path.commonprefix([cls.path_to_sysroot, path])
191 return path[len(common_prefix) :]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800192
193
194def GetScriptStoragePath(relative_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600195 """Get the SysrootPath representing a script storage path.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800196
Alex Klein1699fab2022-09-08 08:46:06 -0600197 Get a path of a directory this script will store things in.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800198
Alex Klein1699fab2022-09-08 08:46:06 -0600199 Args:
200 relative_path: The path relative to the root of the script storage
201 directory.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800202
Alex Klein1699fab2022-09-08 08:46:06 -0600203 Returns:
204 The SysrootPath representing absolute path of |relative_path| in the script
205 storage directory.
206 """
207 path = os.path.join(SCRIPT_STORAGE_PATH, relative_path)
208 return SysrootPath(path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800209
210
211def GetSysrootPath(path):
Alex Klein1699fab2022-09-08 08:46:06 -0600212 """Get the chroot-relative path of a path in the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800213
Alex Klein1699fab2022-09-08 08:46:06 -0600214 Args:
215 path: An absolute path in the sysroot that we will get the path in the
216 chroot for.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800217
Alex Klein1699fab2022-09-08 08:46:06 -0600218 Returns:
219 The chroot-relative path of |path| in the sysroot.
220 """
221 return SysrootPath(path).chroot
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800222
223
224def GetCoverageDirectory(fuzzer):
Alex Klein1699fab2022-09-08 08:46:06 -0600225 """Get a coverage report directory for a fuzzer
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800226
Alex Klein1699fab2022-09-08 08:46:06 -0600227 Args:
228 fuzzer: The fuzzer to get the coverage report directory for.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800229
Alex Klein1699fab2022-09-08 08:46:06 -0600230 Returns:
231 The location of the coverage report directory for the |fuzzer|.
232 """
233 relative_path = os.path.join(COVERAGE_REPORT_DIRECTORY_NAME, fuzzer)
234 return GetScriptStoragePath(relative_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800235
236
237def GetFuzzerSysrootPath(fuzzer):
Alex Klein1699fab2022-09-08 08:46:06 -0600238 """Get the path in the sysroot of a fuzzer.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800239
Alex Klein1699fab2022-09-08 08:46:06 -0600240 Args:
241 fuzzer: The fuzzer to get the path of.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800242
Alex Klein1699fab2022-09-08 08:46:06 -0600243 Returns:
244 The path of |fuzzer| in the sysroot.
245 """
246 return SysrootPath(os.path.join("/", "usr", "libexec", "fuzzers", fuzzer))
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800247
248
249def GetProfdataPath(fuzzer):
Alex Klein1699fab2022-09-08 08:46:06 -0600250 """Get the profdata file of a fuzzer.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800251
Alex Klein1699fab2022-09-08 08:46:06 -0600252 Args:
253 fuzzer: The fuzzer to get the profdata file of.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800254
Alex Klein1699fab2022-09-08 08:46:06 -0600255 Returns:
256 The path of the profdata file that should be used by |fuzzer|.
257 """
258 return GetScriptStoragePath("%s.profdata" % fuzzer)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800259
260
261def GetPathForCopy(parent_directory, chroot_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600262 """Returns a path in the script storage directory to copy chroot_path.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800263
Alex Klein1699fab2022-09-08 08:46:06 -0600264 Returns a SysrootPath representing the location where |chroot_path| should
265 copied. This path will be in the parent_directory which will be in the script
266 storage directory.
267 """
268 basename = os.path.basename(chroot_path)
269 return GetScriptStoragePath(os.path.join(parent_directory, basename))
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800270
271
272def CopyCorpusToSysroot(src_corpus_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600273 """Copies corpus into the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800274
Alex Klein1699fab2022-09-08 08:46:06 -0600275 Copies corpus into the sysroot. Doesn't copy if corpus is already in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800276
Alex Klein1699fab2022-09-08 08:46:06 -0600277 Args:
278 src_corpus_path: A path (in the chroot) to a corpus that will be copied into
279 sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800280
Alex Klein1699fab2022-09-08 08:46:06 -0600281 Returns:
282 The path in the sysroot that the corpus was copied to.
283 """
284 if src_corpus_path is None:
285 return None
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800286
Alex Klein1699fab2022-09-08 08:46:06 -0600287 if SysrootPath.IsPathInSysroot(src_corpus_path):
288 # Don't copy if |src_testcase_path| is already in sysroot. Just return it in
289 # the format expected by the caller.
290 return SysrootPath(src_corpus_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800291
Alex Klein1699fab2022-09-08 08:46:06 -0600292 dest_corpus_path = GetPathForCopy(CORPUS_DIRECTORY_NAME, src_corpus_path)
293 osutils.RmDir(dest_corpus_path.chroot, ignore_missing=True)
294 shutil.copytree(src_corpus_path, dest_corpus_path.chroot)
295 return dest_corpus_path
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800296
297
298def CopyTestcaseToSysroot(src_testcase_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600299 """Copies a testcase into the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800300
Alex Klein1699fab2022-09-08 08:46:06 -0600301 Copies a testcase into the sysroot. Doesn't copy if testcase is already in
302 sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800303
Alex Klein1699fab2022-09-08 08:46:06 -0600304 Args:
305 src_testcase_path: A path (in the chroot) to a testcase that will be copied
306 into sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800307
Alex Klein1699fab2022-09-08 08:46:06 -0600308 Returns:
309 The path in the sysroot that the testcase was copied to.
310 """
311 if SysrootPath.IsPathInSysroot(src_testcase_path):
312 # Don't copy if |src_testcase_path| is already in sysroot. Just return it in
313 # the format expected by the caller.
314 return SysrootPath(src_testcase_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800315
Alex Klein1699fab2022-09-08 08:46:06 -0600316 dest_testcase_path = GetPathForCopy(
317 TESTCASE_DIRECTORY_NAME, src_testcase_path
318 )
319 osutils.SafeMakedirsNonRoot(os.path.dirname(dest_testcase_path.chroot))
320 osutils.SafeUnlink(dest_testcase_path.chroot)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800321
Alex Klein1699fab2022-09-08 08:46:06 -0600322 shutil.copy(src_testcase_path, dest_testcase_path.chroot)
323 return dest_testcase_path
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800324
325
Mike Frysinger45602c72019-09-22 02:15:11 -0400326def sudo_run(*args, **kwargs):
Alex Klein1699fab2022-09-08 08:46:06 -0600327 """Wrapper around cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800328
Alex Klein1699fab2022-09-08 08:46:06 -0600329 Wrapper that calls cros_build_lib.sudo_run but sets debug_level by
330 default.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800331
Alex Klein1699fab2022-09-08 08:46:06 -0600332 Args:
333 *args: Positional arguments to pass to cros_build_lib.sudo_run.
334 *kwargs: Keyword arguments to pass to cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800335
Alex Klein1699fab2022-09-08 08:46:06 -0600336 Returns:
337 The value returned by calling cros_build_lib.sudo_run.
338 """
339 kwargs.setdefault("debug_level", logging.DEBUG)
340 return cros_build_lib.sudo_run(*args, **kwargs)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800341
342
343def GetLibFuzzerOption(option_name, option_value):
Alex Klein1699fab2022-09-08 08:46:06 -0600344 """Gets the libFuzzer command line option with the specified name and value.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800345
Alex Klein1699fab2022-09-08 08:46:06 -0600346 Args:
347 option_name: The name of the libFuzzer option.
348 option_value: The value of the libFuzzer option.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800349
Alex Klein1699fab2022-09-08 08:46:06 -0600350 Returns:
351 The libFuzzer option composed of |option_name| and |option_value|.
352 """
353 return "-%s=%s" % (option_name, option_value)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800354
355
356def IsOptionLimit(option):
Alex Klein1699fab2022-09-08 08:46:06 -0600357 """Determines if fuzzer option limits fuzzing time."""
358 for limit_name in [MAX_TOTAL_TIME_OPTION_NAME, RUNS_OPTION_NAME]:
359 if option.startswith("-%s" % limit_name):
360 return True
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800361
Alex Klein1699fab2022-09-08 08:46:06 -0600362 return False
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800363
364
365def LimitFuzzing(fuzz_command, corpus):
Alex Klein1699fab2022-09-08 08:46:06 -0600366 """Limits how long fuzzing will go if unspecified.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800367
Alex Klein1699fab2022-09-08 08:46:06 -0600368 Adds a reasonable limit on how much fuzzing will be done unless there already
369 is some kind of limit. Mutates fuzz_command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800370
Alex Klein1699fab2022-09-08 08:46:06 -0600371 Args:
372 fuzz_command: A command to run a fuzzer. Used to determine if a limit needs
373 to be set. Mutated if it is needed to specify a limit.
374 corpus: The corpus that will be passed to the fuzzer. If not None then
375 fuzzing is limited by running everything in the corpus once.
376 """
377 if any(IsOptionLimit(x) for x in fuzz_command[1:]):
378 # Don't do anything if there is already a limit.
379 return
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800380
Alex Klein1699fab2022-09-08 08:46:06 -0600381 if corpus:
382 # If there is a corpus, just run everything in the corpus once.
383 fuzz_command.append(
384 GetLibFuzzerOption(RUNS_OPTION_NAME, RUNS_DEFAULT_VALUE)
385 )
386 return
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800387
Alex Klein1699fab2022-09-08 08:46:06 -0600388 # Since there is no corpus, just fuzz for 30 seconds.
389 logging.info(
390 "Limiting fuzzing to %s seconds.", MAX_TOTAL_TIME_DEFAULT_VALUE
391 )
392 max_total_time_option = GetLibFuzzerOption(
393 MAX_TOTAL_TIME_OPTION_NAME, MAX_TOTAL_TIME_DEFAULT_VALUE
394 )
395 fuzz_command.append(max_total_time_option)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800396
397
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800398def GetFuzzExtraEnv(extra_options=None):
Alex Klein1699fab2022-09-08 08:46:06 -0600399 """Gets extra_env for fuzzing.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800400
Alex Klein1699fab2022-09-08 08:46:06 -0600401 Gets environment varaibles and values for running libFuzzer. Sets defaults and
402 allows user to specify extra sanitizer options.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800403
Alex Klein1699fab2022-09-08 08:46:06 -0600404 Args:
405 extra_options: A dict containing sanitizer options to set in addition to the
406 defaults.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800407
Alex Klein1699fab2022-09-08 08:46:06 -0600408 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 = {}
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800414
Alex Klein1699fab2022-09-08 08:46:06 -0600415 # log_path must be set because Chrome OS's patched compiler changes it.
416 # disable odr violation since many fuzzers hit it and it is also disabled on
417 # clusterfuzz.
Maksim Ivanovb4a6c4f2022-12-09 02:32:39 +0000418 # handle_sigtrap is useful for catching int3 in assertion checks in ChromeOS
419 # code.
420 options_dict = {
421 "log_path": "stderr",
422 "detect_odr_violation": "0",
423 "handle_sigtrap": "1",
424 }
Alex Klein1699fab2022-09-08 08:46:06 -0600425 options_dict.update(extra_options)
426 sanitizer_options = ":".join("%s=%s" % x for x in options_dict.items())
427 sanitizers = ("ASAN", "MSAN", "UBSAN")
428 return {x + "_OPTIONS": sanitizer_options for x in sanitizers}
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800429
430
Alex Klein1699fab2022-09-08 08:46:06 -0600431def RunFuzzer(
432 fuzzer,
433 corpus_path=None,
434 fuzz_args="",
435 testcase_path=None,
436 crash_expected=False,
437):
438 """Runs the fuzzer while chrooted into the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800439
Alex Klein1699fab2022-09-08 08:46:06 -0600440 Args:
441 fuzzer: The fuzzer to run.
442 corpus_path: A path to a corpus (not necessarily in the sysroot) to run the
443 fuzzer on.
444 fuzz_args: Additional arguments to pass to the fuzzer when running it.
445 testcase_path: A path to a testcase (not necessarily in the sysroot) to run
446 the fuzzer on.
447 crash_expected: Is it normal for the fuzzer to crash on this run?
448 """
449 logging.info("Running fuzzer: %s", fuzzer)
450 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
451 fuzz_command = [fuzzer_sysroot_path.sysroot]
452 fuzz_command += fuzz_args.split()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800453
Alex Klein1699fab2022-09-08 08:46:06 -0600454 if testcase_path:
455 fuzz_command.append(testcase_path)
456 else:
457 LimitFuzzing(fuzz_command, corpus_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800458
Alex Klein1699fab2022-09-08 08:46:06 -0600459 if corpus_path:
460 fuzz_command.append(corpus_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800461
Alex Klein1699fab2022-09-08 08:46:06 -0600462 if crash_expected:
463 # Don't return nonzero when fuzzer OOMs, leaks, or timesout, since we don't
464 # want an exception in those cases. The user may be trying to reproduce
465 # those issues.
466 fuzz_command += ["-error_exitcode=0", "-timeout_exitcode=0"]
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800467
Alex Klein1699fab2022-09-08 08:46:06 -0600468 # We must set exitcode=0 or else the fuzzer will return nonzero on
469 # successful reproduction.
470 sanitizer_options = {"exitcode": "0"}
471 else:
472 sanitizer_options = {}
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800473
Alex Klein1699fab2022-09-08 08:46:06 -0600474 extra_env = GetFuzzExtraEnv(sanitizer_options)
475 RunSysrootCommand(
476 fuzz_command, extra_env=extra_env, debug_level=logging.INFO
477 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800478
479
480def MergeProfraw(fuzzer):
Alex Klein1699fab2022-09-08 08:46:06 -0600481 """Merges profraw file from a fuzzer and creates a profdata file.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800482
Alex Klein1699fab2022-09-08 08:46:06 -0600483 Args:
484 fuzzer: The fuzzer to merge the profraw file from.
485 """
486 profdata_path = GetProfdataPath(fuzzer)
487 command = [
488 "llvm-profdata",
489 "merge",
490 "-sparse",
491 DEFAULT_PROFRAW_PATH,
492 "-o",
493 profdata_path.sysroot,
494 ]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800495
Alex Klein1699fab2022-09-08 08:46:06 -0600496 RunSysrootCommand(command)
497 return profdata_path
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800498
499
500def GenerateCoverageReport(fuzzer, shared_libraries):
Alex Klein1699fab2022-09-08 08:46:06 -0600501 """Generates an HTML coverage report from a fuzzer run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800502
Alex Klein1699fab2022-09-08 08:46:06 -0600503 Args:
504 fuzzer: The fuzzer to generate the coverage report for.
505 shared_libraries: Libraries loaded dynamically by |fuzzer|.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800506
Alex Klein1699fab2022-09-08 08:46:06 -0600507 Returns:
508 The path of the coverage report.
509 """
510 fuzzer_path = GetFuzzerSysrootPath(fuzzer).chroot
511 command = ["llvm-cov", "show", "-object", fuzzer_path]
512 for library in shared_libraries:
513 command += ["-object", library]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800514
Alex Klein1699fab2022-09-08 08:46:06 -0600515 coverage_directory = GetCoverageDirectory(fuzzer)
516 command += [
517 "-format=html",
518 "-instr-profile=%s" % GetProfdataPath(fuzzer).chroot,
519 "-output-dir=%s" % coverage_directory.chroot,
520 ]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800521
Alex Klein1699fab2022-09-08 08:46:06 -0600522 # TODO(metzman): Investigate error messages printed by this command.
523 cros_build_lib.run(command, stderr=True, debug_level=logging.DEBUG)
524 return coverage_directory
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800525
526
527def GetSharedLibraries(binary_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600528 """Gets the shared libraries used by a binary.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800529
Alex Klein1699fab2022-09-08 08:46:06 -0600530 Gets the shared libraries used by the binary. Based on GetSharedLibraries from
531 src/tools/code_coverage/coverage_utils.py in Chromium.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800532
Alex Klein1699fab2022-09-08 08:46:06 -0600533 Args:
534 binary_path: The path to the binary we want to find the shared libraries of.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800535
Alex Klein1699fab2022-09-08 08:46:06 -0600536 Returns:
537 The shared libraries used by |binary_path|.
538 """
539 logging.info("Finding shared libraries for targets (if any).")
540 shared_libraries = []
541 elf_dict = lddtree.ParseELF(
542 binary_path.chroot, root=SysrootPath.path_to_sysroot
543 )
544 for shared_library in elf_dict["libs"].values():
545 shared_library_path = shared_library["path"]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800546
Alex Klein1699fab2022-09-08 08:46:06 -0600547 if shared_library_path in shared_libraries:
548 continue
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800549
Alex Klein1699fab2022-09-08 08:46:06 -0600550 assert os.path.exists(shared_library_path), (
551 'Shared library "%s" used by '
552 "the given target(s) does not "
553 "exist." % shared_library_path
554 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800555
Alex Klein1699fab2022-09-08 08:46:06 -0600556 if IsInstrumentedWithClangCoverage(shared_library_path):
557 # Do not add non-instrumented libraries. Otherwise, llvm-cov errors out.
558 shared_libraries.append(shared_library_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800559
Alex Klein1699fab2022-09-08 08:46:06 -0600560 logging.debug(
561 "Found shared libraries (%d): %s.",
562 len(shared_libraries),
563 shared_libraries,
564 )
565 logging.info("Finished finding shared libraries for targets.")
566 return shared_libraries
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800567
568
569def IsInstrumentedWithClangCoverage(binary_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600570 """Determines if a binary is instrumented with clang source based coverage.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800571
Alex Klein1699fab2022-09-08 08:46:06 -0600572 Args:
573 binary_path: The path of the binary (executable or library) we are checking
574 is instrumented with clang source based coverage.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800575
Alex Klein1699fab2022-09-08 08:46:06 -0600576 Returns:
577 True if the binary is instrumented with clang source based coverage.
578 """
579 with open(binary_path, "rb") as file_handle:
580 elf_file = ELFFile(file_handle)
581 return elf_file.get_section_by_name(b"__llvm_covmap") is not None
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800582
583
584def RunFuzzerAndGenerateCoverageReport(fuzzer, corpus, fuzz_args):
Alex Klein1699fab2022-09-08 08:46:06 -0600585 """Runs a fuzzer generates a coverage report and returns the report's path.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800586
Alex Klein1699fab2022-09-08 08:46:06 -0600587 Gets a coverage report for a fuzzer.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800588
Alex Klein1699fab2022-09-08 08:46:06 -0600589 Args:
590 fuzzer: The fuzzer to run and generate the coverage report for.
591 corpus: The path to a corpus to run the fuzzer on.
592 fuzz_args: Additional arguments to pass to the fuzzer.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800593
Alex Klein1699fab2022-09-08 08:46:06 -0600594 Returns:
595 The path to the coverage report.
596 """
597 corpus_path = CopyCorpusToSysroot(corpus)
598 if corpus_path:
599 corpus_path = corpus_path.sysroot
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800600
Alex Klein1699fab2022-09-08 08:46:06 -0600601 RunFuzzer(fuzzer, corpus_path=corpus_path, fuzz_args=fuzz_args)
602 MergeProfraw(fuzzer)
603 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
604 shared_libraries = GetSharedLibraries(fuzzer_sysroot_path)
605 return GenerateCoverageReport(fuzzer, shared_libraries)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800606
607
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800608def RunSysrootCommand(command, **kwargs):
Alex Klein1699fab2022-09-08 08:46:06 -0600609 """Runs command while chrooted into sysroot and returns the output.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800610
Alex Klein1699fab2022-09-08 08:46:06 -0600611 Args:
Alex Klein361062b2023-04-05 09:45:28 -0600612 command: A command to run in the sysroot.
613 **kwargs: Extra arguments to pass to cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800614
Alex Klein1699fab2022-09-08 08:46:06 -0600615 Returns:
616 The result of a call to cros_build_lib.sudo_run.
617 """
618 command = ["chroot", SysrootPath.path_to_sysroot] + command
619 return sudo_run(command, **kwargs)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800620
621
622def GetBuildExtraEnv(build_type):
Alex Klein1699fab2022-09-08 08:46:06 -0600623 """Gets the extra_env for building a package.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800624
Alex Klein1699fab2022-09-08 08:46:06 -0600625 Args:
626 build_type: The type of build we want to do.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800627
Alex Klein1699fab2022-09-08 08:46:06 -0600628 Returns:
629 The extra_env to use when building.
630 """
631 if build_type is None:
632 build_type = BuildType.ASAN
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800633
Alex Klein1699fab2022-09-08 08:46:06 -0600634 use_flags = os.environ.get("USE", "").split()
635 # Check that the user hasn't already set USE flags that we can set.
636 # No good way to iterate over an enum in python2.
637 for use_flag in BuildType.CHOICES:
638 if use_flag in use_flags:
639 logging.warning(
640 "%s in USE flags. Please use --build_type instead.", use_flag
641 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800642
Alex Klein1699fab2022-09-08 08:46:06 -0600643 # Set USE flags.
644 fuzzer_build_type = "fuzzer"
645 use_flags += [fuzzer_build_type, build_type]
646 features_flags = os.environ.get("FEATURES", "").split()
647 if build_type == BuildType.COVERAGE:
648 # We must use ASan when doing coverage builds.
649 use_flags.append(BuildType.ASAN)
650 # Use noclean so that a coverage report can be generated based on the source
651 # code.
652 features_flags.append("noclean")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800653
Alex Klein1699fab2022-09-08 08:46:06 -0600654 return {
655 "FEATURES": " ".join(features_flags),
656 "USE": " ".join(use_flags),
657 }
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800658
659
660def BuildPackage(package, board, build_type):
Alex Klein1699fab2022-09-08 08:46:06 -0600661 """Builds a package on a specified board.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800662
Alex Klein1699fab2022-09-08 08:46:06 -0600663 Args:
664 package: The package to build. Nothing is built if None.
665 board: The board to build the package on.
666 build_type: The type of the build to do (e.g. asan, msan, ubsan, coverage).
667 """
668 if package is None:
669 return
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800670
Alex Klein1699fab2022-09-08 08:46:06 -0600671 logging.info("Building %s using %s.", package, build_type)
672 extra_env = GetBuildExtraEnv(build_type)
673 command = [
674 "build_packages",
675 "--board",
676 board,
677 "--skip-chroot-upgrade",
678 package,
679 ]
680 # For msan builds, always use "--no-usepkg" since all package needs to be
681 # instrumented with msan.
682 if build_type == BuildType.MSAN:
683 command += ["--no-usepkg"]
Manoj Gupta5ca17652019-05-13 11:15:33 -0700684
Alex Klein1699fab2022-09-08 08:46:06 -0600685 # Print the output of the build command. Do this because it is familiar to
686 # devs and we don't want to leave them not knowing about the build's progress
687 # for a long time.
688 cros_build_lib.run(command, extra_env=extra_env)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800689
690
691def DownloadFuzzerCorpus(fuzzer, dest_directory=None):
Alex Klein1699fab2022-09-08 08:46:06 -0600692 """Downloads a corpus and returns its path.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800693
Alex Klein1699fab2022-09-08 08:46:06 -0600694 Downloads a corpus to a subdirectory of dest_directory if specified and
695 returns path on the filesystem of the corpus. Asks users to authenticate
696 if permission to read from bucket is denied.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800697
Alex Klein1699fab2022-09-08 08:46:06 -0600698 Args:
699 fuzzer: The name of the fuzzer who's corpus we want to download.
700 dest_directory: The directory to download the corpus to.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800701
Alex Klein1699fab2022-09-08 08:46:06 -0600702 Returns:
703 The path to the downloaded corpus.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800704
Alex Klein1699fab2022-09-08 08:46:06 -0600705 Raises:
706 gs.NoSuchKey: A corpus for the fuzzer doesn't exist.
707 gs.GSCommandError: The corpus failed to download for another reason.
708 """
709 if not fuzzer.startswith("chromeos_"):
710 # ClusterFuzz internally appends "chromeos_" to chromeos targets' names.
711 # Therefore we must do so in order to find the corpus.
712 fuzzer = "chromeos_%s" % fuzzer
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800713
Alex Klein1699fab2022-09-08 08:46:06 -0600714 if dest_directory is None:
715 dest_directory = GetScriptStoragePath(CORPUS_DIRECTORY_NAME).chroot
716 osutils.SafeMakedirsNonRoot(dest_directory)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800717
Alex Klein1699fab2022-09-08 08:46:06 -0600718 clusterfuzz_gcs_corpus_bucket = "chromeos-corpus"
719 suburl = "libfuzzer/%s" % fuzzer
720 gcs_path = gs.GetGsURL(
721 clusterfuzz_gcs_corpus_bucket,
722 for_gsutil=True,
723 public=False,
724 suburl=suburl,
725 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800726
Alex Klein1699fab2022-09-08 08:46:06 -0600727 dest_path = os.path.join(dest_directory, fuzzer)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800728
Alex Klein1699fab2022-09-08 08:46:06 -0600729 try:
730 logging.info("Downloading corpus to %s.", dest_path)
731 ctx = gs.GSContext()
732 ctx.Copy(
733 gcs_path,
734 dest_directory,
735 recursive=True,
736 parallel=True,
737 debug_level=logging.DEBUG,
738 )
739 logging.info("Finished downloading corpus.")
740 except gs.GSNoSuchKey as exception:
741 logging.error("Corpus for fuzzer: %s does not exist.", fuzzer)
742 raise exception
743 # Try to authenticate if we were denied permission to access the corpus.
744 except gs.GSCommandError as exception:
745 logging.error(
746 "gsutil failed to download the corpus. You may need to log in. See:\n"
747 "https://chromium.googlesource.com/chromiumos/docs/+/HEAD/gsutil.md"
748 "#setup\n"
749 "for instructions on doing this."
750 )
751 raise exception
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800752
Alex Klein1699fab2022-09-08 08:46:06 -0600753 return dest_path
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800754
755
756def Reproduce(fuzzer, testcase_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600757 """Runs a fuzzer in the sysroot on a testcase.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800758
Alex Klein1699fab2022-09-08 08:46:06 -0600759 Args:
760 fuzzer: The fuzzer to run.
761 testcase_path: The path (not necessarily in the sysroot) of the testcase to
762 run the fuzzer on.
763 """
764 testcase_sysroot_path = CopyTestcaseToSysroot(testcase_path).sysroot
765 RunFuzzer(fuzzer, testcase_path=testcase_sysroot_path, crash_expected=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800766
767
768def SetUpSysrootForFuzzing():
Alex Klein1699fab2022-09-08 08:46:06 -0600769 """Sets up the the sysroot for fuzzing
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800770
Alex Klein1699fab2022-09-08 08:46:06 -0600771 Prepares the sysroot for fuzzing. Idempotent.
772 """
773 logging.info("Setting up sysroot for fuzzing.")
774 # TODO(metzman): Don't create devices or mount /proc, use platform2_test.py
775 # instead.
776 # Mount /proc in sysroot and setup dev there because they are needed by
777 # sanitizers.
778 proc_manager = ProcManager()
779 proc_manager.Mount()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800780
Alex Klein1699fab2022-09-08 08:46:06 -0600781 # Setup devices in /dev that are needed by libFuzzer.
782 device_manager = DeviceManager()
783 device_manager.SetUp()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800784
Alex Klein1699fab2022-09-08 08:46:06 -0600785 # Set up asan_symbolize.py, llvm-symbolizer, and llvm-profdata in the
786 # sysroot so that fuzzer output (including stack traces) can be symbolized
787 # and so that coverage reports can be generated.
788 tool_manager = ToolManager()
789 tool_manager.Install()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800790
Alex Klein1699fab2022-09-08 08:46:06 -0600791 osutils.SafeMakedirsNonRoot(GetSysrootPath(SCRIPT_STORAGE_PATH))
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800792
793
794def CleanUpSysroot():
Alex Klein1699fab2022-09-08 08:46:06 -0600795 """Cleans up the the sysroot from SetUpSysrootForFuzzing.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800796
Alex Klein1699fab2022-09-08 08:46:06 -0600797 Undoes SetUpSysrootForFuzzing. Idempotent.
798 """
799 logging.info("Cleaning up the sysroot.")
800 proc_manager = ProcManager()
801 proc_manager.Unmount()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800802
Alex Klein1699fab2022-09-08 08:46:06 -0600803 device_manager = DeviceManager()
804 device_manager.CleanUp()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800805
Alex Klein1699fab2022-09-08 08:46:06 -0600806 tool_manager = ToolManager()
807 tool_manager.Uninstall()
808 osutils.RmDir(GetSysrootPath(SCRIPT_STORAGE_PATH), ignore_missing=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800809
810
811class ToolManager(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600812 """Class that installs or uninstalls fuzzing tools to/from the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800813
Alex Klein1699fab2022-09-08 08:46:06 -0600814 Install and Uninstall methods are idempotent. Both are safe to call at any
815 point.
816 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800817
Alex Klein1699fab2022-09-08 08:46:06 -0600818 # Path to asan_symbolize.py.
819 ASAN_SYMBOLIZE_PATH = os.path.join("/", "usr", "bin", "asan_symbolize.py")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800820
Alex Klein1699fab2022-09-08 08:46:06 -0600821 # List of LLVM binaries we must install in sysroot.
822 LLVM_BINARY_NAMES = ["gdbserver", "llvm-symbolizer", "llvm-profdata"]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800823
Alex Klein1699fab2022-09-08 08:46:06 -0600824 def __init__(self):
825 self.asan_symbolize_sysroot_path = GetSysrootPath(
826 self.ASAN_SYMBOLIZE_PATH
827 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800828
Alex Klein1699fab2022-09-08 08:46:06 -0600829 def Install(self):
830 """Installs tools to the sysroot."""
831 # Install asan_symbolize.py.
832 sudo_run(
833 ["cp", self.ASAN_SYMBOLIZE_PATH, self.asan_symbolize_sysroot_path]
834 )
835 # Install the LLVM binaries.
836 # TODO(metzman): Build these tools so that we don't mess up when board is
837 # for a different ISA.
838 for llvm_binary in self._GetLLVMBinaries():
839 llvm_binary.Install()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800840
Alex Klein1699fab2022-09-08 08:46:06 -0600841 def Uninstall(self):
842 """Uninstalls tools from the sysroot. Undoes Install."""
843 # Uninstall asan_symbolize.py.
844 osutils.SafeUnlink(self.asan_symbolize_sysroot_path, sudo=True)
845 # Uninstall the LLVM binaries.
846 for llvm_binary in self._GetLLVMBinaries():
847 llvm_binary.Uninstall()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800848
Alex Klein1699fab2022-09-08 08:46:06 -0600849 def _GetLLVMBinaries(self):
850 """Creates LllvmBinary objects for each binary name in LLVM_BINARY_NAMES."""
851 return [LlvmBinary(x) for x in self.LLVM_BINARY_NAMES]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800852
853
854class LlvmBinary(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600855 """Class for representing installing/uninstalling an LLVM binary in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800856
Alex Klein1699fab2022-09-08 08:46:06 -0600857 Install and Uninstall methods are idempotent. Both are safe to call at any
858 time.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800859 """
Manoj Guptafeb1b7a2019-02-20 11:04:05 -0800860
Alex Klein1699fab2022-09-08 08:46:06 -0600861 # Path to the lddtree chromite script.
Mike Frysinger164ec032023-03-27 16:15:14 -0400862 LDDTREE_SCRIPT_PATH = constants.CHROMITE_BIN_DIR / "lddtree"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800863
Alex Klein1699fab2022-09-08 08:46:06 -0600864 def __init__(self, binary):
865 self.binary = binary
866 self.install_dir = GetSysrootPath(
867 os.path.join("/", "usr", "libexec", binary)
868 )
869 self.binary_dir_path = GetSysrootPath(os.path.join("/", "usr", "bin"))
870 self.binary_chroot_dest_path = os.path.join(
871 self.binary_dir_path, binary
872 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800873
Alex Klein1699fab2022-09-08 08:46:06 -0600874 def Uninstall(self):
875 """Removes an LLVM binary from sysroot. Undoes Install."""
876 osutils.RmDir(self.install_dir, ignore_missing=True, sudo=True)
877 osutils.SafeUnlink(self.binary_chroot_dest_path, sudo=True)
878
879 def Install(self):
880 """Installs (sets up) an LLVM binary in the sysroot.
881
882 Sets up an llvm binary in the sysroot so that it can be run there.
883 """
884 # Create a directory for installing |binary| and all of its dependencies in
885 # the sysroot.
886 binary_rel_path = ["usr", "bin", self.binary]
887 binary_chroot_path = os.path.join("/", *binary_rel_path)
888 if not os.path.exists(binary_chroot_path):
889 logging.warning(
890 "Cannot copy %s, file does not exist in chroot.",
891 binary_chroot_path,
892 )
893 logging.warning(
894 "Functionality provided by %s will be missing.",
895 binary_chroot_path,
896 )
897 return
898
899 osutils.SafeMakedirsNonRoot(self.install_dir)
900
901 # Copy the binary and everything needed to run it into the sysroot.
902 cmd = [
903 self.LDDTREE_SCRIPT_PATH,
904 "-v",
905 "--generate-wrappers",
906 "--root",
907 "/",
908 "--copy-to-tree",
909 self.install_dir,
910 binary_chroot_path,
911 ]
912 sudo_run(cmd)
913
914 # Create a symlink to the copy of the binary (we can't do lddtree in
915 # self.binary_dir_path). Note that symlink should be relative so that it
916 # will be valid when chrooted into the sysroot.
917 rel_path = os.path.relpath(self.install_dir, self.binary_dir_path)
918 link_path = os.path.join(rel_path, *binary_rel_path)
919 osutils.SafeSymlink(link_path, self.binary_chroot_dest_path, sudo=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800920
921
922class DeviceManager(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600923 """Class that creates or removes devices from /dev in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800924
Alex Klein1699fab2022-09-08 08:46:06 -0600925 SetUp and CleanUp methods are idempotent. Both are safe to call at any point.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800926 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800927
Alex Klein1699fab2022-09-08 08:46:06 -0600928 DEVICE_MKNOD_PARAMS = {
929 "null": (666, 3),
930 "random": (444, 8),
931 "urandom": (444, 9),
932 }
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800933
Alex Klein1699fab2022-09-08 08:46:06 -0600934 MKNOD_MAJOR = "1"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800935
Alex Klein1699fab2022-09-08 08:46:06 -0600936 def __init__(self):
937 self.dev_path_chroot = GetSysrootPath("/dev")
938
939 def _GetDevicePath(self, device_name):
940 """Returns the path of |device_name| in sysroot's /dev."""
941 return os.path.join(self.dev_path_chroot, device_name)
942
943 def SetUp(self):
944 """Sets up devices in the sysroot's /dev.
945
946 Creates /dev/null, /dev/random, and /dev/urandom. If they already exist then
947 recreates them.
948 """
949 self.CleanUp()
950 osutils.SafeMakedirsNonRoot(self.dev_path_chroot)
951 for device, mknod_params in self.DEVICE_MKNOD_PARAMS.items():
952 device_path = self._GetDevicePath(device)
953 self._MakeCharDevice(device_path, *mknod_params)
954
955 def CleanUp(self):
956 """Cleans up devices in the sysroot's /dev. Undoes SetUp.
957
958 Removes /dev/null, /dev/random, and /dev/urandom if they exist.
959 """
960 for device in self.DEVICE_MKNOD_PARAMS:
961 device_path = self._GetDevicePath(device)
962 if os.path.exists(device_path):
963 # Use -r since dev/null is sometimes a directory.
964 sudo_run(["rm", "-r", device_path])
965
966 def _MakeCharDevice(self, path, mode, minor):
967 """Make a character device."""
968 mode = str(mode)
969 minor = str(minor)
970 command = ["mknod", "-m", mode, path, "c", self.MKNOD_MAJOR, minor]
971 sudo_run(command)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800972
973
974class ProcManager(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600975 """Class that mounts or unmounts /proc in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800976
Alex Klein1699fab2022-09-08 08:46:06 -0600977 Mount and Unmount are idempotent. Both are safe to call at any point.
978 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800979
Alex Klein1699fab2022-09-08 08:46:06 -0600980 PROC_PATH = "/proc"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800981
Alex Klein1699fab2022-09-08 08:46:06 -0600982 def __init__(self):
983 self.proc_path_chroot = GetSysrootPath(self.PROC_PATH)
984 self.is_mounted = osutils.IsMounted(self.proc_path_chroot)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800985
Alex Klein1699fab2022-09-08 08:46:06 -0600986 def Unmount(self):
987 """Unmounts /proc in chroot. Undoes Mount."""
988 if not self.is_mounted:
989 return
990 osutils.UmountDir(self.proc_path_chroot, cleanup=False)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800991
Alex Klein1699fab2022-09-08 08:46:06 -0600992 def Mount(self):
993 """Mounts /proc in chroot. Remounts it if already mounted."""
994 self.Unmount()
995 osutils.MountDir(
996 self.PROC_PATH,
997 self.proc_path_chroot,
998 "proc",
999 debug_level=logging.DEBUG,
1000 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001001
1002
1003def EnterSysrootShell():
Alex Klein1699fab2022-09-08 08:46:06 -06001004 """Spawns and gives user access to a bash shell in the sysroot."""
1005 command = ["/bin/bash", "-i"]
1006 return RunSysrootCommand(
1007 command,
1008 extra_env=GetFuzzExtraEnv(),
1009 debug_level=logging.INFO,
1010 check=False,
1011 ).returncode
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001012
1013
1014def StripFuzzerPrefixes(fuzzer_name):
Alex Klein1699fab2022-09-08 08:46:06 -06001015 """Strip the prefix ClusterFuzz uses in case they are specified.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001016
Alex Klein1699fab2022-09-08 08:46:06 -06001017 Strip the prefixes used by ClusterFuzz if the users has included them by
1018 accident.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001019
Alex Klein1699fab2022-09-08 08:46:06 -06001020 Args:
1021 fuzzer_name: The fuzzer who's name may contain prefixes.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001022
Alex Klein1699fab2022-09-08 08:46:06 -06001023 Returns:
1024 The name of the fuzz target without prefixes.
1025 """
1026 initial_name = fuzzer_name
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001027
Alex Klein1699fab2022-09-08 08:46:06 -06001028 def StripPrefix(prefix):
1029 if fuzzer_name.startswith(prefix):
1030 return fuzzer_name[len(prefix) :]
1031 return fuzzer_name
1032
1033 clusterfuzz_prefixes = ["libFuzzer_", "chromeos_"]
1034
1035 for prefix in clusterfuzz_prefixes:
1036 fuzzer_name = StripPrefix(prefix)
1037
1038 if initial_name != fuzzer_name:
1039 logging.warning(
1040 "%s contains a prefix from ClusterFuzz (one or more of %s) that is not "
1041 "part of the fuzzer's name. Interpreting --fuzzer as %s.",
1042 initial_name,
1043 clusterfuzz_prefixes,
1044 fuzzer_name,
1045 )
1046
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001047 return fuzzer_name
1048
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001049
1050def ExecuteShellCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001051 """Executes the "shell" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001052
Alex Klein1699fab2022-09-08 08:46:06 -06001053 Sets up the sysroot for fuzzing and gives user access to a bash shell it
1054 spawns in the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001055
Alex Klein1699fab2022-09-08 08:46:06 -06001056 Returns:
1057 The exit code of the shell command.
1058 """
1059 SetUpSysrootForFuzzing()
1060 return EnterSysrootShell()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001061
1062
1063def ExecuteSetupCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001064 """Executes the "setup" command. Wrapper for SetUpSysrootForFuzzing.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001065
Alex Klein1699fab2022-09-08 08:46:06 -06001066 Sets up the sysroot for fuzzing.
1067 """
1068 SetUpSysrootForFuzzing()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001069
1070
1071def ExecuteCleanupCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001072 """Executes the "cleanup" command. Wrapper for CleanUpSysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001073
Alex Klein1699fab2022-09-08 08:46:06 -06001074 Undoes pre-fuzzing setup.
1075 """
1076 CleanUpSysroot()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001077
1078
1079def ExecuteCoverageCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001080 """Executes the "coverage" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001081
Alex Klein1699fab2022-09-08 08:46:06 -06001082 Executes the "coverage" command by optionally doing a coverage build of a
1083 package, optionally downloading the fuzzer's corpus, optionally copying it
1084 into the sysroot, running the fuzzer and then generating a coverage report
1085 for the user to view. Causes program to exit if fuzzer is not instrumented
1086 with source based coverage.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001087
Alex Klein1699fab2022-09-08 08:46:06 -06001088 Args:
1089 options: The parsed arguments passed to this program.
1090 """
1091 BuildPackage(options.package, options.board, BuildType.COVERAGE)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001092
Alex Klein1699fab2022-09-08 08:46:06 -06001093 fuzzer = StripFuzzerPrefixes(options.fuzzer)
1094 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
1095 if not IsInstrumentedWithClangCoverage(fuzzer_sysroot_path.chroot):
1096 # Don't run the fuzzer if it isn't instrumented with source based coverage.
1097 # Quit and let the user know how to build the fuzzer properly.
1098 cros_build_lib.Die(
1099 "%s is not instrumented with source based coverage.\nSpecify --package "
1100 'to do a coverage build or build with USE flag: "coverage".',
1101 fuzzer,
1102 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001103
Alex Klein1699fab2022-09-08 08:46:06 -06001104 corpus = options.corpus
1105 if options.download:
1106 corpus = DownloadFuzzerCorpus(options.fuzzer)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001107
Alex Klein1699fab2022-09-08 08:46:06 -06001108 # Set up sysroot for fuzzing.
1109 SetUpSysrootForFuzzing()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001110
Alex Klein1699fab2022-09-08 08:46:06 -06001111 coverage_report_path = RunFuzzerAndGenerateCoverageReport(
1112 fuzzer, corpus, options.fuzz_args
1113 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001114
Alex Klein1699fab2022-09-08 08:46:06 -06001115 # Get path on host so user can access it with their browser.
1116 # TODO(metzman): Add the ability to convert to host paths to path_util.
1117 external_trunk_path = os.getenv("EXTERNAL_TRUNK_PATH")
1118 coverage_report_host_path = os.path.join(
1119 external_trunk_path, "chroot", coverage_report_path.chroot[1:]
1120 )
1121 print(
1122 "Coverage report written to file://%s/index.html"
1123 % coverage_report_host_path
1124 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001125
1126
1127def ExecuteDownloadCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001128 """Executes the "download" command. Wrapper around DownloadFuzzerCorpus."""
1129 DownloadFuzzerCorpus(StripFuzzerPrefixes(options.fuzzer), options.directory)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001130
1131
1132def ExecuteReproduceCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001133 """Executes the "reproduce" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001134
Alex Klein1699fab2022-09-08 08:46:06 -06001135 Executes the "reproduce" command by Running a fuzzer on a testcase.
1136 May build the fuzzer before running.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001137
Alex Klein1699fab2022-09-08 08:46:06 -06001138 Args:
1139 options: The parsed arguments passed to this program.
1140 """
1141 if options.build_type and not options.package:
1142 raise Exception(
1143 "Cannot specify --build_type without specifying --package."
1144 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001145
Alex Klein1699fab2022-09-08 08:46:06 -06001146 # Verify that "msan-fuzzer" profile is being used with msan.
1147 # Check presence of "-fsanitize=memory" in CFLAGS.
1148 if options.build_type == BuildType.MSAN:
1149 cmd = ["portageq-%s" % options.board, "envvar", "CFLAGS"]
1150 cflags = cros_build_lib.run(
1151 cmd, capture_output=True, encoding="utf-8"
1152 ).stdout.splitlines()
1153 check_string = "-fsanitize=memory"
1154 if not any(check_string in s for s in cflags):
1155 logging.error(
1156 "-fsanitize=memory not found in CFLAGS. "
1157 'Use "setup_board --board=amd64-generic --profile=msan-fuzzer" '
1158 "for MSan Fuzzing Builds."
1159 )
1160 raise Exception("Incompatible profile used for msan fuzzing.")
Manoj Gupta5ca17652019-05-13 11:15:33 -07001161
Alex Klein1699fab2022-09-08 08:46:06 -06001162 BuildPackage(options.package, options.board, options.build_type)
1163 SetUpSysrootForFuzzing()
1164 Reproduce(StripFuzzerPrefixes(options.fuzzer), options.testcase)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001165
Manoj Guptae5e1e612019-10-21 12:39:57 -07001166
Manoj Guptaec08b812019-10-10 14:21:16 -07001167def InstallBaseDependencies(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001168 """Installs the base packages needed to chroot in board sysroot.
Manoj Guptaec08b812019-10-10 14:21:16 -07001169
Alex Klein1699fab2022-09-08 08:46:06 -06001170 Args:
1171 options: The parsed arguments passed to this program.
1172 """
1173 package = "virtual/implicit-system"
1174 if not portage_util.IsPackageInstalled(
1175 package, sysroot=SysrootPath.path_to_sysroot
1176 ):
1177 build_type = getattr(options, "build_type", None)
1178 BuildPackage(package, options.board, build_type)
Manoj Guptae5e1e612019-10-21 12:39:57 -07001179
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001180
1181def ParseArgs(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001182 """Parses program arguments.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001183
Alex Klein1699fab2022-09-08 08:46:06 -06001184 Args:
1185 argv: The program arguments we want to parse.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001186
Alex Klein1699fab2022-09-08 08:46:06 -06001187 Returns:
1188 An options object which will tell us which command to run and which options
1189 to use for that command.
1190 """
1191 parser = commandline.ArgumentParser(description=__doc__)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001192
Alex Klein1699fab2022-09-08 08:46:06 -06001193 parser.add_argument(
1194 "--board",
1195 default=cros_build_lib.GetDefaultBoard(),
1196 help="Board on which to run test.",
1197 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001198
Alex Klein1699fab2022-09-08 08:46:06 -06001199 subparsers = parser.add_subparsers(dest="command")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001200
Alex Klein1699fab2022-09-08 08:46:06 -06001201 subparsers.add_parser("cleanup", help="Undo setup command.")
1202 coverage_parser = subparsers.add_parser(
1203 "coverage", help="Get a coverage report for a fuzzer."
1204 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001205
Alex Klein1699fab2022-09-08 08:46:06 -06001206 coverage_parser.add_argument("--package", help="Package to build.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001207
Alex Klein1699fab2022-09-08 08:46:06 -06001208 corpus_parser = coverage_parser.add_mutually_exclusive_group()
1209 corpus_parser.add_argument("--corpus", help="Corpus to run fuzzer on.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001210
Alex Klein1699fab2022-09-08 08:46:06 -06001211 corpus_parser.add_argument(
1212 "--download",
1213 action="store_true",
1214 help="Generate coverage report based on corpus from ClusterFuzz.",
1215 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001216
Alex Klein1699fab2022-09-08 08:46:06 -06001217 coverage_parser.add_argument(
1218 "--fuzzer",
1219 required=True,
1220 help="The fuzz target to generate a coverage report for.",
1221 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001222
Alex Klein1699fab2022-09-08 08:46:06 -06001223 coverage_parser.add_argument(
1224 "--fuzz-args",
1225 default="",
1226 help="Arguments to pass libFuzzer. "
1227 "Please use an equals sign or parsing will fail "
1228 '(i.e. --fuzzer_args="-rss_limit_mb=2048 -print_funcs=1").',
1229 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001230
Alex Klein1699fab2022-09-08 08:46:06 -06001231 download_parser = subparsers.add_parser(
1232 "download", help="Download a corpus."
1233 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001234
Alex Klein1699fab2022-09-08 08:46:06 -06001235 download_parser.add_argument(
1236 "--directory", help="Path to directory to download the corpus to."
1237 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001238
Alex Klein1699fab2022-09-08 08:46:06 -06001239 download_parser.add_argument(
1240 "--fuzzer", required=True, help="Fuzzer to download the corpus for."
1241 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001242
Alex Klein1699fab2022-09-08 08:46:06 -06001243 reproduce_parser = subparsers.add_parser(
1244 "reproduce", help="Run a fuzzer on a testcase."
1245 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001246
Alex Klein1699fab2022-09-08 08:46:06 -06001247 reproduce_parser.add_argument(
1248 "--testcase", required=True, help="Path of testcase to run fuzzer on."
1249 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001250
Alex Klein1699fab2022-09-08 08:46:06 -06001251 reproduce_parser.add_argument(
1252 "--fuzzer", required=True, help="Fuzzer to reproduce the crash on."
1253 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001254
Alex Klein1699fab2022-09-08 08:46:06 -06001255 reproduce_parser.add_argument("--package", help="Package to build.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001256
Alex Klein1699fab2022-09-08 08:46:06 -06001257 reproduce_parser.add_argument(
1258 "--build-type",
1259 choices=BuildType.CHOICES,
1260 help="Type of build.",
1261 type=str.lower,
1262 ) # Ignore sanitizer case.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001263
Alex Klein1699fab2022-09-08 08:46:06 -06001264 subparsers.add_parser("setup", help="Set up the sysroot to test fuzzing.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001265
Alex Klein1699fab2022-09-08 08:46:06 -06001266 subparsers.add_parser(
1267 "shell",
1268 help="Set up sysroot for fuzzing and get a shell in the sysroot.",
1269 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001270
Alex Klein1699fab2022-09-08 08:46:06 -06001271 opts = parser.parse_args(argv)
1272 opts.Freeze()
1273 return opts
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001274
1275
1276def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001277 """Parses arguments and executes a command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001278
Alex Klein1699fab2022-09-08 08:46:06 -06001279 Args:
1280 argv: The prorgram arguments.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001281
Alex Klein1699fab2022-09-08 08:46:06 -06001282 Returns:
1283 0 on success. Non-zero on failure.
1284 """
1285 cros_build_lib.AssertInsideChroot()
1286 options = ParseArgs(argv)
1287 if options.board is None:
1288 logging.error('Please specify "--board" or set ".default_board".')
1289 return 1
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001290
Alex Klein1699fab2022-09-08 08:46:06 -06001291 SysrootPath.SetPathToSysroot(options.board)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001292
Alex Klein1699fab2022-09-08 08:46:06 -06001293 InstallBaseDependencies(options)
Manoj Guptaec08b812019-10-10 14:21:16 -07001294
Alex Klein1699fab2022-09-08 08:46:06 -06001295 if options.command == "cleanup":
1296 ExecuteCleanupCommand()
1297 elif options.command == "coverage":
1298 ExecuteCoverageCommand(options)
1299 elif options.command == "setup":
1300 ExecuteSetupCommand()
1301 elif options.command == "download":
1302 ExecuteDownloadCommand(options)
1303 elif options.command == "reproduce":
1304 ExecuteReproduceCommand(options)
1305 elif options.command == "shell":
1306 return ExecuteShellCommand()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001307
Alex Klein1699fab2022-09-08 08:46:06 -06001308 return 0