blob: f5406c04f9117581a52d95d782d43208da95cfc8 [file] [log] [blame]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001# Copyright 2018 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Script for performing tasks that are useful for fuzzer development.
6
7Run "cros_fuzz" in the chroot for a list of command or "cros_fuzz $COMMAND
8--help" for their full details. Below is a summary of commands that the script
9can perform:
10
11coverage: Generate a coverage report for a given fuzzer (specified by "--fuzzer"
12 option). You almost certainly want to specify the package to build (using
13 the "--package" option) so that a coverage build is done, since a coverage
14 build is needed to generate a report. If your fuzz target is running on
15 ClusterFuzz already, you can use the "--download" option to download the
16 corpus from ClusterFuzz. Otherwise, you can use the "--corpus" option to
17 specify the path of the corpus to run the fuzzer on and generate a report.
18 The corpus will be copied to the sysroot so that the fuzzer can use it.
19 Note that "--download" and "--corpus" are mutually exclusive.
20
21reproduce: Runs the fuzzer specified by the "--fuzzer" option on a testcase
22 (path specified by the "--testcase" argument). Optionally does a build when
23 the "--package" option is used. The type of build can be specified using the
24 "--build_type" argument.
25
26download: Downloads the corpus from ClusterFuzz of the fuzzer specified by the
27 "--fuzzer" option. The path of the directory the corpus directory is
28 downloaded to can be specified using the "--directory" option.
29
30shell: Sets up the sysroot for fuzzing and then chroots into the sysroot giving
31 you a shell that is ready to fuzz.
32
33setup: Sets up the sysroot for fuzzing (done prior to doing "reproduce", "shell"
34 and "coverage" commands).
35
36cleanup: Undoes "setup".
37
38Note that cros_fuzz will print every shell command it runs if you set the
39log-level to debug ("--log-level debug"). Otherwise it will print commands that
40fail.
41"""
42
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.
418 options_dict = {"log_path": "stderr", "detect_odr_violation": "0"}
419 options_dict.update(extra_options)
420 sanitizer_options = ":".join("%s=%s" % x for x in options_dict.items())
421 sanitizers = ("ASAN", "MSAN", "UBSAN")
422 return {x + "_OPTIONS": sanitizer_options for x in sanitizers}
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800423
424
Alex Klein1699fab2022-09-08 08:46:06 -0600425def RunFuzzer(
426 fuzzer,
427 corpus_path=None,
428 fuzz_args="",
429 testcase_path=None,
430 crash_expected=False,
431):
432 """Runs the fuzzer while chrooted into the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800433
Alex Klein1699fab2022-09-08 08:46:06 -0600434 Args:
435 fuzzer: The fuzzer to run.
436 corpus_path: A path to a corpus (not necessarily in the sysroot) to run the
437 fuzzer on.
438 fuzz_args: Additional arguments to pass to the fuzzer when running it.
439 testcase_path: A path to a testcase (not necessarily in the sysroot) to run
440 the fuzzer on.
441 crash_expected: Is it normal for the fuzzer to crash on this run?
442 """
443 logging.info("Running fuzzer: %s", fuzzer)
444 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
445 fuzz_command = [fuzzer_sysroot_path.sysroot]
446 fuzz_command += fuzz_args.split()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800447
Alex Klein1699fab2022-09-08 08:46:06 -0600448 if testcase_path:
449 fuzz_command.append(testcase_path)
450 else:
451 LimitFuzzing(fuzz_command, corpus_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800452
Alex Klein1699fab2022-09-08 08:46:06 -0600453 if corpus_path:
454 fuzz_command.append(corpus_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800455
Alex Klein1699fab2022-09-08 08:46:06 -0600456 if crash_expected:
457 # Don't return nonzero when fuzzer OOMs, leaks, or timesout, since we don't
458 # want an exception in those cases. The user may be trying to reproduce
459 # those issues.
460 fuzz_command += ["-error_exitcode=0", "-timeout_exitcode=0"]
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800461
Alex Klein1699fab2022-09-08 08:46:06 -0600462 # We must set exitcode=0 or else the fuzzer will return nonzero on
463 # successful reproduction.
464 sanitizer_options = {"exitcode": "0"}
465 else:
466 sanitizer_options = {}
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800467
Alex Klein1699fab2022-09-08 08:46:06 -0600468 extra_env = GetFuzzExtraEnv(sanitizer_options)
469 RunSysrootCommand(
470 fuzz_command, extra_env=extra_env, debug_level=logging.INFO
471 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800472
473
474def MergeProfraw(fuzzer):
Alex Klein1699fab2022-09-08 08:46:06 -0600475 """Merges profraw file from a fuzzer and creates a profdata file.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800476
Alex Klein1699fab2022-09-08 08:46:06 -0600477 Args:
478 fuzzer: The fuzzer to merge the profraw file from.
479 """
480 profdata_path = GetProfdataPath(fuzzer)
481 command = [
482 "llvm-profdata",
483 "merge",
484 "-sparse",
485 DEFAULT_PROFRAW_PATH,
486 "-o",
487 profdata_path.sysroot,
488 ]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800489
Alex Klein1699fab2022-09-08 08:46:06 -0600490 RunSysrootCommand(command)
491 return profdata_path
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800492
493
494def GenerateCoverageReport(fuzzer, shared_libraries):
Alex Klein1699fab2022-09-08 08:46:06 -0600495 """Generates an HTML coverage report from a fuzzer run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800496
Alex Klein1699fab2022-09-08 08:46:06 -0600497 Args:
498 fuzzer: The fuzzer to generate the coverage report for.
499 shared_libraries: Libraries loaded dynamically by |fuzzer|.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800500
Alex Klein1699fab2022-09-08 08:46:06 -0600501 Returns:
502 The path of the coverage report.
503 """
504 fuzzer_path = GetFuzzerSysrootPath(fuzzer).chroot
505 command = ["llvm-cov", "show", "-object", fuzzer_path]
506 for library in shared_libraries:
507 command += ["-object", library]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800508
Alex Klein1699fab2022-09-08 08:46:06 -0600509 coverage_directory = GetCoverageDirectory(fuzzer)
510 command += [
511 "-format=html",
512 "-instr-profile=%s" % GetProfdataPath(fuzzer).chroot,
513 "-output-dir=%s" % coverage_directory.chroot,
514 ]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800515
Alex Klein1699fab2022-09-08 08:46:06 -0600516 # TODO(metzman): Investigate error messages printed by this command.
517 cros_build_lib.run(command, stderr=True, debug_level=logging.DEBUG)
518 return coverage_directory
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800519
520
521def GetSharedLibraries(binary_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600522 """Gets the shared libraries used by a binary.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800523
Alex Klein1699fab2022-09-08 08:46:06 -0600524 Gets the shared libraries used by the binary. Based on GetSharedLibraries from
525 src/tools/code_coverage/coverage_utils.py in Chromium.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800526
Alex Klein1699fab2022-09-08 08:46:06 -0600527 Args:
528 binary_path: The path to the binary we want to find the shared libraries of.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800529
Alex Klein1699fab2022-09-08 08:46:06 -0600530 Returns:
531 The shared libraries used by |binary_path|.
532 """
533 logging.info("Finding shared libraries for targets (if any).")
534 shared_libraries = []
535 elf_dict = lddtree.ParseELF(
536 binary_path.chroot, root=SysrootPath.path_to_sysroot
537 )
538 for shared_library in elf_dict["libs"].values():
539 shared_library_path = shared_library["path"]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800540
Alex Klein1699fab2022-09-08 08:46:06 -0600541 if shared_library_path in shared_libraries:
542 continue
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800543
Alex Klein1699fab2022-09-08 08:46:06 -0600544 assert os.path.exists(shared_library_path), (
545 'Shared library "%s" used by '
546 "the given target(s) does not "
547 "exist." % shared_library_path
548 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800549
Alex Klein1699fab2022-09-08 08:46:06 -0600550 if IsInstrumentedWithClangCoverage(shared_library_path):
551 # Do not add non-instrumented libraries. Otherwise, llvm-cov errors out.
552 shared_libraries.append(shared_library_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800553
Alex Klein1699fab2022-09-08 08:46:06 -0600554 logging.debug(
555 "Found shared libraries (%d): %s.",
556 len(shared_libraries),
557 shared_libraries,
558 )
559 logging.info("Finished finding shared libraries for targets.")
560 return shared_libraries
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800561
562
563def IsInstrumentedWithClangCoverage(binary_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600564 """Determines if a binary is instrumented with clang source based coverage.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800565
Alex Klein1699fab2022-09-08 08:46:06 -0600566 Args:
567 binary_path: The path of the binary (executable or library) we are checking
568 is instrumented with clang source based coverage.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800569
Alex Klein1699fab2022-09-08 08:46:06 -0600570 Returns:
571 True if the binary is instrumented with clang source based coverage.
572 """
573 with open(binary_path, "rb") as file_handle:
574 elf_file = ELFFile(file_handle)
575 return elf_file.get_section_by_name(b"__llvm_covmap") is not None
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800576
577
578def RunFuzzerAndGenerateCoverageReport(fuzzer, corpus, fuzz_args):
Alex Klein1699fab2022-09-08 08:46:06 -0600579 """Runs a fuzzer generates a coverage report and returns the report's path.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800580
Alex Klein1699fab2022-09-08 08:46:06 -0600581 Gets a coverage report for a fuzzer.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800582
Alex Klein1699fab2022-09-08 08:46:06 -0600583 Args:
584 fuzzer: The fuzzer to run and generate the coverage report for.
585 corpus: The path to a corpus to run the fuzzer on.
586 fuzz_args: Additional arguments to pass to the fuzzer.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800587
Alex Klein1699fab2022-09-08 08:46:06 -0600588 Returns:
589 The path to the coverage report.
590 """
591 corpus_path = CopyCorpusToSysroot(corpus)
592 if corpus_path:
593 corpus_path = corpus_path.sysroot
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800594
Alex Klein1699fab2022-09-08 08:46:06 -0600595 RunFuzzer(fuzzer, corpus_path=corpus_path, fuzz_args=fuzz_args)
596 MergeProfraw(fuzzer)
597 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
598 shared_libraries = GetSharedLibraries(fuzzer_sysroot_path)
599 return GenerateCoverageReport(fuzzer, shared_libraries)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800600
601
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800602def RunSysrootCommand(command, **kwargs):
Alex Klein1699fab2022-09-08 08:46:06 -0600603 """Runs command while chrooted into sysroot and returns the output.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800604
Alex Klein1699fab2022-09-08 08:46:06 -0600605 Args:
606 command: A command to run in the sysroot.
607 kwargs: Extra arguments to pass to cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800608
Alex Klein1699fab2022-09-08 08:46:06 -0600609 Returns:
610 The result of a call to cros_build_lib.sudo_run.
611 """
612 command = ["chroot", SysrootPath.path_to_sysroot] + command
613 return sudo_run(command, **kwargs)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800614
615
616def GetBuildExtraEnv(build_type):
Alex Klein1699fab2022-09-08 08:46:06 -0600617 """Gets the extra_env for building a package.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800618
Alex Klein1699fab2022-09-08 08:46:06 -0600619 Args:
620 build_type: The type of build we want to do.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800621
Alex Klein1699fab2022-09-08 08:46:06 -0600622 Returns:
623 The extra_env to use when building.
624 """
625 if build_type is None:
626 build_type = BuildType.ASAN
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800627
Alex Klein1699fab2022-09-08 08:46:06 -0600628 use_flags = os.environ.get("USE", "").split()
629 # Check that the user hasn't already set USE flags that we can set.
630 # No good way to iterate over an enum in python2.
631 for use_flag in BuildType.CHOICES:
632 if use_flag in use_flags:
633 logging.warning(
634 "%s in USE flags. Please use --build_type instead.", use_flag
635 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800636
Alex Klein1699fab2022-09-08 08:46:06 -0600637 # Set USE flags.
638 fuzzer_build_type = "fuzzer"
639 use_flags += [fuzzer_build_type, build_type]
640 features_flags = os.environ.get("FEATURES", "").split()
641 if build_type == BuildType.COVERAGE:
642 # We must use ASan when doing coverage builds.
643 use_flags.append(BuildType.ASAN)
644 # Use noclean so that a coverage report can be generated based on the source
645 # code.
646 features_flags.append("noclean")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800647
Alex Klein1699fab2022-09-08 08:46:06 -0600648 return {
649 "FEATURES": " ".join(features_flags),
650 "USE": " ".join(use_flags),
651 }
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800652
653
654def BuildPackage(package, board, build_type):
Alex Klein1699fab2022-09-08 08:46:06 -0600655 """Builds a package on a specified board.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800656
Alex Klein1699fab2022-09-08 08:46:06 -0600657 Args:
658 package: The package to build. Nothing is built if None.
659 board: The board to build the package on.
660 build_type: The type of the build to do (e.g. asan, msan, ubsan, coverage).
661 """
662 if package is None:
663 return
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800664
Alex Klein1699fab2022-09-08 08:46:06 -0600665 logging.info("Building %s using %s.", package, build_type)
666 extra_env = GetBuildExtraEnv(build_type)
667 command = [
668 "build_packages",
669 "--board",
670 board,
671 "--skip-chroot-upgrade",
672 package,
673 ]
674 # For msan builds, always use "--no-usepkg" since all package needs to be
675 # instrumented with msan.
676 if build_type == BuildType.MSAN:
677 command += ["--no-usepkg"]
Manoj Gupta5ca17652019-05-13 11:15:33 -0700678
Alex Klein1699fab2022-09-08 08:46:06 -0600679 # Print the output of the build command. Do this because it is familiar to
680 # devs and we don't want to leave them not knowing about the build's progress
681 # for a long time.
682 cros_build_lib.run(command, extra_env=extra_env)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800683
684
685def DownloadFuzzerCorpus(fuzzer, dest_directory=None):
Alex Klein1699fab2022-09-08 08:46:06 -0600686 """Downloads a corpus and returns its path.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800687
Alex Klein1699fab2022-09-08 08:46:06 -0600688 Downloads a corpus to a subdirectory of dest_directory if specified and
689 returns path on the filesystem of the corpus. Asks users to authenticate
690 if permission to read from bucket is denied.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800691
Alex Klein1699fab2022-09-08 08:46:06 -0600692 Args:
693 fuzzer: The name of the fuzzer who's corpus we want to download.
694 dest_directory: The directory to download the corpus to.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800695
Alex Klein1699fab2022-09-08 08:46:06 -0600696 Returns:
697 The path to the downloaded corpus.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800698
Alex Klein1699fab2022-09-08 08:46:06 -0600699 Raises:
700 gs.NoSuchKey: A corpus for the fuzzer doesn't exist.
701 gs.GSCommandError: The corpus failed to download for another reason.
702 """
703 if not fuzzer.startswith("chromeos_"):
704 # ClusterFuzz internally appends "chromeos_" to chromeos targets' names.
705 # Therefore we must do so in order to find the corpus.
706 fuzzer = "chromeos_%s" % fuzzer
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800707
Alex Klein1699fab2022-09-08 08:46:06 -0600708 if dest_directory is None:
709 dest_directory = GetScriptStoragePath(CORPUS_DIRECTORY_NAME).chroot
710 osutils.SafeMakedirsNonRoot(dest_directory)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800711
Alex Klein1699fab2022-09-08 08:46:06 -0600712 clusterfuzz_gcs_corpus_bucket = "chromeos-corpus"
713 suburl = "libfuzzer/%s" % fuzzer
714 gcs_path = gs.GetGsURL(
715 clusterfuzz_gcs_corpus_bucket,
716 for_gsutil=True,
717 public=False,
718 suburl=suburl,
719 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800720
Alex Klein1699fab2022-09-08 08:46:06 -0600721 dest_path = os.path.join(dest_directory, fuzzer)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800722
Alex Klein1699fab2022-09-08 08:46:06 -0600723 try:
724 logging.info("Downloading corpus to %s.", dest_path)
725 ctx = gs.GSContext()
726 ctx.Copy(
727 gcs_path,
728 dest_directory,
729 recursive=True,
730 parallel=True,
731 debug_level=logging.DEBUG,
732 )
733 logging.info("Finished downloading corpus.")
734 except gs.GSNoSuchKey as exception:
735 logging.error("Corpus for fuzzer: %s does not exist.", fuzzer)
736 raise exception
737 # Try to authenticate if we were denied permission to access the corpus.
738 except gs.GSCommandError as exception:
739 logging.error(
740 "gsutil failed to download the corpus. You may need to log in. See:\n"
741 "https://chromium.googlesource.com/chromiumos/docs/+/HEAD/gsutil.md"
742 "#setup\n"
743 "for instructions on doing this."
744 )
745 raise exception
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800746
Alex Klein1699fab2022-09-08 08:46:06 -0600747 return dest_path
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800748
749
750def Reproduce(fuzzer, testcase_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600751 """Runs a fuzzer in the sysroot on a testcase.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800752
Alex Klein1699fab2022-09-08 08:46:06 -0600753 Args:
754 fuzzer: The fuzzer to run.
755 testcase_path: The path (not necessarily in the sysroot) of the testcase to
756 run the fuzzer on.
757 """
758 testcase_sysroot_path = CopyTestcaseToSysroot(testcase_path).sysroot
759 RunFuzzer(fuzzer, testcase_path=testcase_sysroot_path, crash_expected=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800760
761
762def SetUpSysrootForFuzzing():
Alex Klein1699fab2022-09-08 08:46:06 -0600763 """Sets up the the sysroot for fuzzing
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800764
Alex Klein1699fab2022-09-08 08:46:06 -0600765 Prepares the sysroot for fuzzing. Idempotent.
766 """
767 logging.info("Setting up sysroot for fuzzing.")
768 # TODO(metzman): Don't create devices or mount /proc, use platform2_test.py
769 # instead.
770 # Mount /proc in sysroot and setup dev there because they are needed by
771 # sanitizers.
772 proc_manager = ProcManager()
773 proc_manager.Mount()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800774
Alex Klein1699fab2022-09-08 08:46:06 -0600775 # Setup devices in /dev that are needed by libFuzzer.
776 device_manager = DeviceManager()
777 device_manager.SetUp()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800778
Alex Klein1699fab2022-09-08 08:46:06 -0600779 # Set up asan_symbolize.py, llvm-symbolizer, and llvm-profdata in the
780 # sysroot so that fuzzer output (including stack traces) can be symbolized
781 # and so that coverage reports can be generated.
782 tool_manager = ToolManager()
783 tool_manager.Install()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800784
Alex Klein1699fab2022-09-08 08:46:06 -0600785 osutils.SafeMakedirsNonRoot(GetSysrootPath(SCRIPT_STORAGE_PATH))
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800786
787
788def CleanUpSysroot():
Alex Klein1699fab2022-09-08 08:46:06 -0600789 """Cleans up the the sysroot from SetUpSysrootForFuzzing.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800790
Alex Klein1699fab2022-09-08 08:46:06 -0600791 Undoes SetUpSysrootForFuzzing. Idempotent.
792 """
793 logging.info("Cleaning up the sysroot.")
794 proc_manager = ProcManager()
795 proc_manager.Unmount()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800796
Alex Klein1699fab2022-09-08 08:46:06 -0600797 device_manager = DeviceManager()
798 device_manager.CleanUp()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800799
Alex Klein1699fab2022-09-08 08:46:06 -0600800 tool_manager = ToolManager()
801 tool_manager.Uninstall()
802 osutils.RmDir(GetSysrootPath(SCRIPT_STORAGE_PATH), ignore_missing=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800803
804
805class ToolManager(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600806 """Class that installs or uninstalls fuzzing tools to/from the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800807
Alex Klein1699fab2022-09-08 08:46:06 -0600808 Install and Uninstall methods are idempotent. Both are safe to call at any
809 point.
810 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800811
Alex Klein1699fab2022-09-08 08:46:06 -0600812 # Path to asan_symbolize.py.
813 ASAN_SYMBOLIZE_PATH = os.path.join("/", "usr", "bin", "asan_symbolize.py")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800814
Alex Klein1699fab2022-09-08 08:46:06 -0600815 # List of LLVM binaries we must install in sysroot.
816 LLVM_BINARY_NAMES = ["gdbserver", "llvm-symbolizer", "llvm-profdata"]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800817
Alex Klein1699fab2022-09-08 08:46:06 -0600818 def __init__(self):
819 self.asan_symbolize_sysroot_path = GetSysrootPath(
820 self.ASAN_SYMBOLIZE_PATH
821 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800822
Alex Klein1699fab2022-09-08 08:46:06 -0600823 def Install(self):
824 """Installs tools to the sysroot."""
825 # Install asan_symbolize.py.
826 sudo_run(
827 ["cp", self.ASAN_SYMBOLIZE_PATH, self.asan_symbolize_sysroot_path]
828 )
829 # Install the LLVM binaries.
830 # TODO(metzman): Build these tools so that we don't mess up when board is
831 # for a different ISA.
832 for llvm_binary in self._GetLLVMBinaries():
833 llvm_binary.Install()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800834
Alex Klein1699fab2022-09-08 08:46:06 -0600835 def Uninstall(self):
836 """Uninstalls tools from the sysroot. Undoes Install."""
837 # Uninstall asan_symbolize.py.
838 osutils.SafeUnlink(self.asan_symbolize_sysroot_path, sudo=True)
839 # Uninstall the LLVM binaries.
840 for llvm_binary in self._GetLLVMBinaries():
841 llvm_binary.Uninstall()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800842
Alex Klein1699fab2022-09-08 08:46:06 -0600843 def _GetLLVMBinaries(self):
844 """Creates LllvmBinary objects for each binary name in LLVM_BINARY_NAMES."""
845 return [LlvmBinary(x) for x in self.LLVM_BINARY_NAMES]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800846
847
848class LlvmBinary(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600849 """Class for representing installing/uninstalling an LLVM binary in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800850
Alex Klein1699fab2022-09-08 08:46:06 -0600851 Install and Uninstall methods are idempotent. Both are safe to call at any
852 time.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800853 """
Manoj Guptafeb1b7a2019-02-20 11:04:05 -0800854
Alex Klein1699fab2022-09-08 08:46:06 -0600855 # Path to the lddtree chromite script.
856 LDDTREE_SCRIPT_PATH = os.path.join(constants.CHROMITE_BIN_DIR, "lddtree")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800857
Alex Klein1699fab2022-09-08 08:46:06 -0600858 def __init__(self, binary):
859 self.binary = binary
860 self.install_dir = GetSysrootPath(
861 os.path.join("/", "usr", "libexec", binary)
862 )
863 self.binary_dir_path = GetSysrootPath(os.path.join("/", "usr", "bin"))
864 self.binary_chroot_dest_path = os.path.join(
865 self.binary_dir_path, binary
866 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800867
Alex Klein1699fab2022-09-08 08:46:06 -0600868 def Uninstall(self):
869 """Removes an LLVM binary from sysroot. Undoes Install."""
870 osutils.RmDir(self.install_dir, ignore_missing=True, sudo=True)
871 osutils.SafeUnlink(self.binary_chroot_dest_path, sudo=True)
872
873 def Install(self):
874 """Installs (sets up) an LLVM binary in the sysroot.
875
876 Sets up an llvm binary in the sysroot so that it can be run there.
877 """
878 # Create a directory for installing |binary| and all of its dependencies in
879 # the sysroot.
880 binary_rel_path = ["usr", "bin", self.binary]
881 binary_chroot_path = os.path.join("/", *binary_rel_path)
882 if not os.path.exists(binary_chroot_path):
883 logging.warning(
884 "Cannot copy %s, file does not exist in chroot.",
885 binary_chroot_path,
886 )
887 logging.warning(
888 "Functionality provided by %s will be missing.",
889 binary_chroot_path,
890 )
891 return
892
893 osutils.SafeMakedirsNonRoot(self.install_dir)
894
895 # Copy the binary and everything needed to run it into the sysroot.
896 cmd = [
897 self.LDDTREE_SCRIPT_PATH,
898 "-v",
899 "--generate-wrappers",
900 "--root",
901 "/",
902 "--copy-to-tree",
903 self.install_dir,
904 binary_chroot_path,
905 ]
906 sudo_run(cmd)
907
908 # Create a symlink to the copy of the binary (we can't do lddtree in
909 # self.binary_dir_path). Note that symlink should be relative so that it
910 # will be valid when chrooted into the sysroot.
911 rel_path = os.path.relpath(self.install_dir, self.binary_dir_path)
912 link_path = os.path.join(rel_path, *binary_rel_path)
913 osutils.SafeSymlink(link_path, self.binary_chroot_dest_path, sudo=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800914
915
916class DeviceManager(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600917 """Class that creates or removes devices from /dev in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800918
Alex Klein1699fab2022-09-08 08:46:06 -0600919 SetUp and CleanUp methods are idempotent. Both are safe to call at any point.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800920 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800921
Alex Klein1699fab2022-09-08 08:46:06 -0600922 DEVICE_MKNOD_PARAMS = {
923 "null": (666, 3),
924 "random": (444, 8),
925 "urandom": (444, 9),
926 }
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800927
Alex Klein1699fab2022-09-08 08:46:06 -0600928 MKNOD_MAJOR = "1"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800929
Alex Klein1699fab2022-09-08 08:46:06 -0600930 def __init__(self):
931 self.dev_path_chroot = GetSysrootPath("/dev")
932
933 def _GetDevicePath(self, device_name):
934 """Returns the path of |device_name| in sysroot's /dev."""
935 return os.path.join(self.dev_path_chroot, device_name)
936
937 def SetUp(self):
938 """Sets up devices in the sysroot's /dev.
939
940 Creates /dev/null, /dev/random, and /dev/urandom. If they already exist then
941 recreates them.
942 """
943 self.CleanUp()
944 osutils.SafeMakedirsNonRoot(self.dev_path_chroot)
945 for device, mknod_params in self.DEVICE_MKNOD_PARAMS.items():
946 device_path = self._GetDevicePath(device)
947 self._MakeCharDevice(device_path, *mknod_params)
948
949 def CleanUp(self):
950 """Cleans up devices in the sysroot's /dev. Undoes SetUp.
951
952 Removes /dev/null, /dev/random, and /dev/urandom if they exist.
953 """
954 for device in self.DEVICE_MKNOD_PARAMS:
955 device_path = self._GetDevicePath(device)
956 if os.path.exists(device_path):
957 # Use -r since dev/null is sometimes a directory.
958 sudo_run(["rm", "-r", device_path])
959
960 def _MakeCharDevice(self, path, mode, minor):
961 """Make a character device."""
962 mode = str(mode)
963 minor = str(minor)
964 command = ["mknod", "-m", mode, path, "c", self.MKNOD_MAJOR, minor]
965 sudo_run(command)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800966
967
968class ProcManager(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600969 """Class that mounts or unmounts /proc in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800970
Alex Klein1699fab2022-09-08 08:46:06 -0600971 Mount and Unmount are idempotent. Both are safe to call at any point.
972 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800973
Alex Klein1699fab2022-09-08 08:46:06 -0600974 PROC_PATH = "/proc"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800975
Alex Klein1699fab2022-09-08 08:46:06 -0600976 def __init__(self):
977 self.proc_path_chroot = GetSysrootPath(self.PROC_PATH)
978 self.is_mounted = osutils.IsMounted(self.proc_path_chroot)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800979
Alex Klein1699fab2022-09-08 08:46:06 -0600980 def Unmount(self):
981 """Unmounts /proc in chroot. Undoes Mount."""
982 if not self.is_mounted:
983 return
984 osutils.UmountDir(self.proc_path_chroot, cleanup=False)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800985
Alex Klein1699fab2022-09-08 08:46:06 -0600986 def Mount(self):
987 """Mounts /proc in chroot. Remounts it if already mounted."""
988 self.Unmount()
989 osutils.MountDir(
990 self.PROC_PATH,
991 self.proc_path_chroot,
992 "proc",
993 debug_level=logging.DEBUG,
994 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800995
996
997def EnterSysrootShell():
Alex Klein1699fab2022-09-08 08:46:06 -0600998 """Spawns and gives user access to a bash shell in the sysroot."""
999 command = ["/bin/bash", "-i"]
1000 return RunSysrootCommand(
1001 command,
1002 extra_env=GetFuzzExtraEnv(),
1003 debug_level=logging.INFO,
1004 check=False,
1005 ).returncode
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001006
1007
1008def StripFuzzerPrefixes(fuzzer_name):
Alex Klein1699fab2022-09-08 08:46:06 -06001009 """Strip the prefix ClusterFuzz uses in case they are specified.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001010
Alex Klein1699fab2022-09-08 08:46:06 -06001011 Strip the prefixes used by ClusterFuzz if the users has included them by
1012 accident.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001013
Alex Klein1699fab2022-09-08 08:46:06 -06001014 Args:
1015 fuzzer_name: The fuzzer who's name may contain prefixes.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001016
Alex Klein1699fab2022-09-08 08:46:06 -06001017 Returns:
1018 The name of the fuzz target without prefixes.
1019 """
1020 initial_name = fuzzer_name
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001021
Alex Klein1699fab2022-09-08 08:46:06 -06001022 def StripPrefix(prefix):
1023 if fuzzer_name.startswith(prefix):
1024 return fuzzer_name[len(prefix) :]
1025 return fuzzer_name
1026
1027 clusterfuzz_prefixes = ["libFuzzer_", "chromeos_"]
1028
1029 for prefix in clusterfuzz_prefixes:
1030 fuzzer_name = StripPrefix(prefix)
1031
1032 if initial_name != fuzzer_name:
1033 logging.warning(
1034 "%s contains a prefix from ClusterFuzz (one or more of %s) that is not "
1035 "part of the fuzzer's name. Interpreting --fuzzer as %s.",
1036 initial_name,
1037 clusterfuzz_prefixes,
1038 fuzzer_name,
1039 )
1040
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001041 return fuzzer_name
1042
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001043
1044def ExecuteShellCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001045 """Executes the "shell" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001046
Alex Klein1699fab2022-09-08 08:46:06 -06001047 Sets up the sysroot for fuzzing and gives user access to a bash shell it
1048 spawns in the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001049
Alex Klein1699fab2022-09-08 08:46:06 -06001050 Returns:
1051 The exit code of the shell command.
1052 """
1053 SetUpSysrootForFuzzing()
1054 return EnterSysrootShell()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001055
1056
1057def ExecuteSetupCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001058 """Executes the "setup" command. Wrapper for SetUpSysrootForFuzzing.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001059
Alex Klein1699fab2022-09-08 08:46:06 -06001060 Sets up the sysroot for fuzzing.
1061 """
1062 SetUpSysrootForFuzzing()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001063
1064
1065def ExecuteCleanupCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001066 """Executes the "cleanup" command. Wrapper for CleanUpSysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001067
Alex Klein1699fab2022-09-08 08:46:06 -06001068 Undoes pre-fuzzing setup.
1069 """
1070 CleanUpSysroot()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001071
1072
1073def ExecuteCoverageCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001074 """Executes the "coverage" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001075
Alex Klein1699fab2022-09-08 08:46:06 -06001076 Executes the "coverage" command by optionally doing a coverage build of a
1077 package, optionally downloading the fuzzer's corpus, optionally copying it
1078 into the sysroot, running the fuzzer and then generating a coverage report
1079 for the user to view. Causes program to exit if fuzzer is not instrumented
1080 with source based coverage.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001081
Alex Klein1699fab2022-09-08 08:46:06 -06001082 Args:
1083 options: The parsed arguments passed to this program.
1084 """
1085 BuildPackage(options.package, options.board, BuildType.COVERAGE)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001086
Alex Klein1699fab2022-09-08 08:46:06 -06001087 fuzzer = StripFuzzerPrefixes(options.fuzzer)
1088 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
1089 if not IsInstrumentedWithClangCoverage(fuzzer_sysroot_path.chroot):
1090 # Don't run the fuzzer if it isn't instrumented with source based coverage.
1091 # Quit and let the user know how to build the fuzzer properly.
1092 cros_build_lib.Die(
1093 "%s is not instrumented with source based coverage.\nSpecify --package "
1094 'to do a coverage build or build with USE flag: "coverage".',
1095 fuzzer,
1096 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001097
Alex Klein1699fab2022-09-08 08:46:06 -06001098 corpus = options.corpus
1099 if options.download:
1100 corpus = DownloadFuzzerCorpus(options.fuzzer)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001101
Alex Klein1699fab2022-09-08 08:46:06 -06001102 # Set up sysroot for fuzzing.
1103 SetUpSysrootForFuzzing()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001104
Alex Klein1699fab2022-09-08 08:46:06 -06001105 coverage_report_path = RunFuzzerAndGenerateCoverageReport(
1106 fuzzer, corpus, options.fuzz_args
1107 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001108
Alex Klein1699fab2022-09-08 08:46:06 -06001109 # Get path on host so user can access it with their browser.
1110 # TODO(metzman): Add the ability to convert to host paths to path_util.
1111 external_trunk_path = os.getenv("EXTERNAL_TRUNK_PATH")
1112 coverage_report_host_path = os.path.join(
1113 external_trunk_path, "chroot", coverage_report_path.chroot[1:]
1114 )
1115 print(
1116 "Coverage report written to file://%s/index.html"
1117 % coverage_report_host_path
1118 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001119
1120
1121def ExecuteDownloadCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001122 """Executes the "download" command. Wrapper around DownloadFuzzerCorpus."""
1123 DownloadFuzzerCorpus(StripFuzzerPrefixes(options.fuzzer), options.directory)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001124
1125
1126def ExecuteReproduceCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001127 """Executes the "reproduce" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001128
Alex Klein1699fab2022-09-08 08:46:06 -06001129 Executes the "reproduce" command by Running a fuzzer on a testcase.
1130 May build the fuzzer before running.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001131
Alex Klein1699fab2022-09-08 08:46:06 -06001132 Args:
1133 options: The parsed arguments passed to this program.
1134 """
1135 if options.build_type and not options.package:
1136 raise Exception(
1137 "Cannot specify --build_type without specifying --package."
1138 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001139
Alex Klein1699fab2022-09-08 08:46:06 -06001140 # Verify that "msan-fuzzer" profile is being used with msan.
1141 # Check presence of "-fsanitize=memory" in CFLAGS.
1142 if options.build_type == BuildType.MSAN:
1143 cmd = ["portageq-%s" % options.board, "envvar", "CFLAGS"]
1144 cflags = cros_build_lib.run(
1145 cmd, capture_output=True, encoding="utf-8"
1146 ).stdout.splitlines()
1147 check_string = "-fsanitize=memory"
1148 if not any(check_string in s for s in cflags):
1149 logging.error(
1150 "-fsanitize=memory not found in CFLAGS. "
1151 'Use "setup_board --board=amd64-generic --profile=msan-fuzzer" '
1152 "for MSan Fuzzing Builds."
1153 )
1154 raise Exception("Incompatible profile used for msan fuzzing.")
Manoj Gupta5ca17652019-05-13 11:15:33 -07001155
Alex Klein1699fab2022-09-08 08:46:06 -06001156 BuildPackage(options.package, options.board, options.build_type)
1157 SetUpSysrootForFuzzing()
1158 Reproduce(StripFuzzerPrefixes(options.fuzzer), options.testcase)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001159
Manoj Guptae5e1e612019-10-21 12:39:57 -07001160
Manoj Guptaec08b812019-10-10 14:21:16 -07001161def InstallBaseDependencies(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001162 """Installs the base packages needed to chroot in board sysroot.
Manoj Guptaec08b812019-10-10 14:21:16 -07001163
Alex Klein1699fab2022-09-08 08:46:06 -06001164 Args:
1165 options: The parsed arguments passed to this program.
1166 """
1167 package = "virtual/implicit-system"
1168 if not portage_util.IsPackageInstalled(
1169 package, sysroot=SysrootPath.path_to_sysroot
1170 ):
1171 build_type = getattr(options, "build_type", None)
1172 BuildPackage(package, options.board, build_type)
Manoj Guptae5e1e612019-10-21 12:39:57 -07001173
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001174
1175def ParseArgs(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001176 """Parses program arguments.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001177
Alex Klein1699fab2022-09-08 08:46:06 -06001178 Args:
1179 argv: The program arguments we want to parse.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001180
Alex Klein1699fab2022-09-08 08:46:06 -06001181 Returns:
1182 An options object which will tell us which command to run and which options
1183 to use for that command.
1184 """
1185 parser = commandline.ArgumentParser(description=__doc__)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001186
Alex Klein1699fab2022-09-08 08:46:06 -06001187 parser.add_argument(
1188 "--board",
1189 default=cros_build_lib.GetDefaultBoard(),
1190 help="Board on which to run test.",
1191 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001192
Alex Klein1699fab2022-09-08 08:46:06 -06001193 subparsers = parser.add_subparsers(dest="command")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001194
Alex Klein1699fab2022-09-08 08:46:06 -06001195 subparsers.add_parser("cleanup", help="Undo setup command.")
1196 coverage_parser = subparsers.add_parser(
1197 "coverage", help="Get a coverage report for a fuzzer."
1198 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001199
Alex Klein1699fab2022-09-08 08:46:06 -06001200 coverage_parser.add_argument("--package", help="Package to build.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001201
Alex Klein1699fab2022-09-08 08:46:06 -06001202 corpus_parser = coverage_parser.add_mutually_exclusive_group()
1203 corpus_parser.add_argument("--corpus", help="Corpus to run fuzzer on.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001204
Alex Klein1699fab2022-09-08 08:46:06 -06001205 corpus_parser.add_argument(
1206 "--download",
1207 action="store_true",
1208 help="Generate coverage report based on corpus from ClusterFuzz.",
1209 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001210
Alex Klein1699fab2022-09-08 08:46:06 -06001211 coverage_parser.add_argument(
1212 "--fuzzer",
1213 required=True,
1214 help="The fuzz target to generate a coverage report for.",
1215 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001216
Alex Klein1699fab2022-09-08 08:46:06 -06001217 coverage_parser.add_argument(
1218 "--fuzz-args",
1219 default="",
1220 help="Arguments to pass libFuzzer. "
1221 "Please use an equals sign or parsing will fail "
1222 '(i.e. --fuzzer_args="-rss_limit_mb=2048 -print_funcs=1").',
1223 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001224
Alex Klein1699fab2022-09-08 08:46:06 -06001225 download_parser = subparsers.add_parser(
1226 "download", help="Download a corpus."
1227 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001228
Alex Klein1699fab2022-09-08 08:46:06 -06001229 download_parser.add_argument(
1230 "--directory", help="Path to directory to download the corpus to."
1231 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001232
Alex Klein1699fab2022-09-08 08:46:06 -06001233 download_parser.add_argument(
1234 "--fuzzer", required=True, help="Fuzzer to download the corpus for."
1235 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001236
Alex Klein1699fab2022-09-08 08:46:06 -06001237 reproduce_parser = subparsers.add_parser(
1238 "reproduce", help="Run a fuzzer on a testcase."
1239 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001240
Alex Klein1699fab2022-09-08 08:46:06 -06001241 reproduce_parser.add_argument(
1242 "--testcase", required=True, help="Path of testcase to run fuzzer on."
1243 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001244
Alex Klein1699fab2022-09-08 08:46:06 -06001245 reproduce_parser.add_argument(
1246 "--fuzzer", required=True, help="Fuzzer to reproduce the crash on."
1247 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001248
Alex Klein1699fab2022-09-08 08:46:06 -06001249 reproduce_parser.add_argument("--package", help="Package to build.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001250
Alex Klein1699fab2022-09-08 08:46:06 -06001251 reproduce_parser.add_argument(
1252 "--build-type",
1253 choices=BuildType.CHOICES,
1254 help="Type of build.",
1255 type=str.lower,
1256 ) # Ignore sanitizer case.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001257
Alex Klein1699fab2022-09-08 08:46:06 -06001258 subparsers.add_parser("setup", help="Set up the sysroot to test fuzzing.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001259
Alex Klein1699fab2022-09-08 08:46:06 -06001260 subparsers.add_parser(
1261 "shell",
1262 help="Set up sysroot for fuzzing and get a shell in the sysroot.",
1263 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001264
Alex Klein1699fab2022-09-08 08:46:06 -06001265 opts = parser.parse_args(argv)
1266 opts.Freeze()
1267 return opts
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001268
1269
1270def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001271 """Parses arguments and executes a command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001272
Alex Klein1699fab2022-09-08 08:46:06 -06001273 Args:
1274 argv: The prorgram arguments.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001275
Alex Klein1699fab2022-09-08 08:46:06 -06001276 Returns:
1277 0 on success. Non-zero on failure.
1278 """
1279 cros_build_lib.AssertInsideChroot()
1280 options = ParseArgs(argv)
1281 if options.board is None:
1282 logging.error('Please specify "--board" or set ".default_board".')
1283 return 1
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001284
Alex Klein1699fab2022-09-08 08:46:06 -06001285 SysrootPath.SetPathToSysroot(options.board)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001286
Alex Klein1699fab2022-09-08 08:46:06 -06001287 InstallBaseDependencies(options)
Manoj Guptaec08b812019-10-10 14:21:16 -07001288
Alex Klein1699fab2022-09-08 08:46:06 -06001289 if options.command == "cleanup":
1290 ExecuteCleanupCommand()
1291 elif options.command == "coverage":
1292 ExecuteCoverageCommand(options)
1293 elif options.command == "setup":
1294 ExecuteSetupCommand()
1295 elif options.command == "download":
1296 ExecuteDownloadCommand(options)
1297 elif options.command == "reproduce":
1298 ExecuteReproduceCommand(options)
1299 elif options.command == "shell":
1300 return ExecuteShellCommand()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001301
Alex Klein1699fab2022-09-08 08:46:06 -06001302 return 0