blob: 66275779188bea38857b769e5b41dbd0a92c3e13 [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"
Alex Klein8b444532023-04-11 16:35:24 -060012 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.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080020
21reproduce: Runs the fuzzer specified by the "--fuzzer" option on a testcase
22 (path specified by the "--testcase" argument). Optionally does a build when
Alex Klein8b444532023-04-11 16:35:24 -060023 the "--package" option is used. The type of build can be specified using the
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080024 "--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
Alex Klein8b444532023-04-11 16:35:24 -060028 downloaded to can be specified using the "--directory" option.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080029
30shell: Sets up the sysroot for fuzzing and then chroots into the sysroot giving
Alex Klein8b444532023-04-11 16:35:24 -060031 you a shell that is ready to fuzz.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080032
33setup: Sets up the sysroot for fuzzing (done prior to doing "reproduce", "shell"
Alex Klein8b444532023-04-11 16:35:24 -060034 and "coverage" commands).
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080035
36cleanup: Undoes "setup".
37
38Note that cros_fuzz will print every shell command it runs if you set the
Alex Klein8b444532023-04-11 16:35:24 -060039log-level to debug ("--log-level debug"). Otherwise, it will print commands that
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080040fail.
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
Alex Klein8b444532023-04-11 16:35:24 -060085# data (when a corpus hasn't been provided and we aren't told to download one).
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080086MAX_TOTAL_TIME_DEFAULT_VALUE = 30
87
88
Alex Klein074f94f2023-06-22 10:32:06 -060089class BuildType:
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
Alex Klein074f94f2023-06-22 10:32:06 -0600102class SysrootPath:
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 Klein8b444532023-04-11 16:35:24 -0600108 For example, if we need to interact with the "/tmp" directory of the
109 sysroot, SysrootPath('/tmp').sysroot returns the path of the directory if we
110 are in 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:
Alex Klein8b444532023-04-11 16:35:24 -0600124 path: An absolute path representing something in the sysroot.
Alex Klein1699fab2022-09-08 08:46:06 -0600125 """
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:
Alex Klein8b444532023-04-11 16:35:24 -0600137 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:
Alex Klein8b444532023-04-11 16:35:24 -0600140 The path to the sysroot (the value of path_to_sysroot).
Alex Klein1699fab2022-09-08 08:46:06 -0600141 """
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:
Alex Klein8b444532023-04-11 16:35:24 -0600150 The path this object represents when chrooted into the sysroot.
Alex Klein1699fab2022-09-08 08:46:06 -0600151 """
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:
Alex Klein8b444532023-04-11 16:35:24 -0600162 The path this object represents when in the Chrome OS SDK .
Alex Klein1699fab2022-09-08 08:46:06 -0600163 """
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:
Alex Klein8b444532023-04-11 16:35:24 -0600171 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:
Alex Klein8b444532023-04-11 16:35:24 -0600174 True if path is within the sysroot's path in the chroot.
Alex Klein1699fab2022-09-08 08:46:06 -0600175 """
176 assert cls.path_to_sysroot
177 return path.startswith(cls.path_to_sysroot)
178
179 @classmethod
180 def FromChrootPathInSysroot(cls, path):
Alex Klein8b444532023-04-11 16:35:24 -0600181 """Converts a chroot-relative path in sysroot into sysroot-relative.
Alex Klein1699fab2022-09-08 08:46:06 -0600182
183 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600184 path: The chroot-relative path we are converting to sysroot
185 relative.
Alex Klein1699fab2022-09-08 08:46:06 -0600186
187 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600188 The sysroot relative version of |path|.
Alex Klein1699fab2022-09-08 08:46:06 -0600189 """
190 assert cls.IsPathInSysroot(path)
191 common_prefix = os.path.commonprefix([cls.path_to_sysroot, path])
192 return path[len(common_prefix) :]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800193
194
195def GetScriptStoragePath(relative_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600196 """Get the SysrootPath representing a script storage path.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800197
Alex Klein1699fab2022-09-08 08:46:06 -0600198 Get a path of a directory this script will store things in.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800199
Alex Klein1699fab2022-09-08 08:46:06 -0600200 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600201 relative_path: The path relative to the root of the script storage
202 directory.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800203
Alex Klein1699fab2022-09-08 08:46:06 -0600204 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600205 The SysrootPath representing absolute path of |relative_path| in the
206 script storage directory.
Alex Klein1699fab2022-09-08 08:46:06 -0600207 """
208 path = os.path.join(SCRIPT_STORAGE_PATH, relative_path)
209 return SysrootPath(path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800210
211
212def GetSysrootPath(path):
Alex Klein1699fab2022-09-08 08:46:06 -0600213 """Get the chroot-relative path of a path in the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800214
Alex Klein1699fab2022-09-08 08:46:06 -0600215 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600216 path: An absolute path in the sysroot that we will get the path in the
217 chroot for.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800218
Alex Klein1699fab2022-09-08 08:46:06 -0600219 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600220 The chroot-relative path of |path| in the sysroot.
Alex Klein1699fab2022-09-08 08:46:06 -0600221 """
222 return SysrootPath(path).chroot
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800223
224
225def GetCoverageDirectory(fuzzer):
Alex Klein1699fab2022-09-08 08:46:06 -0600226 """Get a coverage report directory for a fuzzer
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800227
Alex Klein1699fab2022-09-08 08:46:06 -0600228 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600229 fuzzer: The fuzzer to get the coverage report directory for.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800230
Alex Klein1699fab2022-09-08 08:46:06 -0600231 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600232 The location of the coverage report directory for the |fuzzer|.
Alex Klein1699fab2022-09-08 08:46:06 -0600233 """
234 relative_path = os.path.join(COVERAGE_REPORT_DIRECTORY_NAME, fuzzer)
235 return GetScriptStoragePath(relative_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800236
237
238def GetFuzzerSysrootPath(fuzzer):
Alex Klein1699fab2022-09-08 08:46:06 -0600239 """Get the path in the sysroot of a fuzzer.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800240
Alex Klein1699fab2022-09-08 08:46:06 -0600241 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600242 fuzzer: The fuzzer to get the path of.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800243
Alex Klein1699fab2022-09-08 08:46:06 -0600244 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600245 The path of |fuzzer| in the sysroot.
Alex Klein1699fab2022-09-08 08:46:06 -0600246 """
247 return SysrootPath(os.path.join("/", "usr", "libexec", "fuzzers", fuzzer))
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800248
249
250def GetProfdataPath(fuzzer):
Alex Klein1699fab2022-09-08 08:46:06 -0600251 """Get the profdata file of a fuzzer.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800252
Alex Klein1699fab2022-09-08 08:46:06 -0600253 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600254 fuzzer: The fuzzer to get the profdata file of.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800255
Alex Klein1699fab2022-09-08 08:46:06 -0600256 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600257 The path of the profdata file that should be used by |fuzzer|.
Alex Klein1699fab2022-09-08 08:46:06 -0600258 """
259 return GetScriptStoragePath("%s.profdata" % fuzzer)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800260
261
262def GetPathForCopy(parent_directory, chroot_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600263 """Returns a path in the script storage directory to copy chroot_path.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800264
Alex Klein1699fab2022-09-08 08:46:06 -0600265 Returns a SysrootPath representing the location where |chroot_path| should
Alex Klein8b444532023-04-11 16:35:24 -0600266 be copied. This path will be in the parent_directory which will be in the
267 script storage directory.
Alex Klein1699fab2022-09-08 08:46:06 -0600268 """
269 basename = os.path.basename(chroot_path)
270 return GetScriptStoragePath(os.path.join(parent_directory, basename))
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800271
272
273def CopyCorpusToSysroot(src_corpus_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600274 """Copies corpus into the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800275
Alex Klein8b444532023-04-11 16:35:24 -0600276 Copies corpus into the sysroot. Doesn't copy if corpus is already in
277 sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800278
Alex Klein1699fab2022-09-08 08:46:06 -0600279 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600280 src_corpus_path: A path (in the chroot) to a corpus that will be copied
281 into sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800282
Alex Klein1699fab2022-09-08 08:46:06 -0600283 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600284 The path in the sysroot that the corpus was copied to.
Alex Klein1699fab2022-09-08 08:46:06 -0600285 """
286 if src_corpus_path is None:
287 return None
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800288
Alex Klein1699fab2022-09-08 08:46:06 -0600289 if SysrootPath.IsPathInSysroot(src_corpus_path):
Alex Klein8b444532023-04-11 16:35:24 -0600290 # Don't copy if |src_testcase_path| is already in sysroot. Just return
291 # it in the format expected by the caller.
Alex Klein1699fab2022-09-08 08:46:06 -0600292 return SysrootPath(src_corpus_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800293
Alex Klein1699fab2022-09-08 08:46:06 -0600294 dest_corpus_path = GetPathForCopy(CORPUS_DIRECTORY_NAME, src_corpus_path)
295 osutils.RmDir(dest_corpus_path.chroot, ignore_missing=True)
296 shutil.copytree(src_corpus_path, dest_corpus_path.chroot)
297 return dest_corpus_path
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800298
299
300def CopyTestcaseToSysroot(src_testcase_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600301 """Copies a testcase into the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800302
Alex Klein1699fab2022-09-08 08:46:06 -0600303 Copies a testcase into the sysroot. Doesn't copy if testcase is already in
304 sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800305
Alex Klein1699fab2022-09-08 08:46:06 -0600306 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600307 src_testcase_path: A path (in the chroot) to a testcase that will be
308 copied into sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800309
Alex Klein1699fab2022-09-08 08:46:06 -0600310 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600311 The path in the sysroot that the testcase was copied to.
Alex Klein1699fab2022-09-08 08:46:06 -0600312 """
313 if SysrootPath.IsPathInSysroot(src_testcase_path):
Alex Klein8b444532023-04-11 16:35:24 -0600314 # Don't copy if |src_testcase_path| is already in sysroot. Just return
315 # it in the format expected by the caller.
Alex Klein1699fab2022-09-08 08:46:06 -0600316 return SysrootPath(src_testcase_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800317
Alex Klein1699fab2022-09-08 08:46:06 -0600318 dest_testcase_path = GetPathForCopy(
319 TESTCASE_DIRECTORY_NAME, src_testcase_path
320 )
321 osutils.SafeMakedirsNonRoot(os.path.dirname(dest_testcase_path.chroot))
322 osutils.SafeUnlink(dest_testcase_path.chroot)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800323
Alex Klein1699fab2022-09-08 08:46:06 -0600324 shutil.copy(src_testcase_path, dest_testcase_path.chroot)
325 return dest_testcase_path
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800326
327
Mike Frysinger45602c72019-09-22 02:15:11 -0400328def sudo_run(*args, **kwargs):
Alex Klein1699fab2022-09-08 08:46:06 -0600329 """Wrapper around cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800330
Alex Klein1699fab2022-09-08 08:46:06 -0600331 Wrapper that calls cros_build_lib.sudo_run but sets debug_level by
332 default.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800333
Alex Klein1699fab2022-09-08 08:46:06 -0600334 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600335 *args: Positional arguments to pass to cros_build_lib.sudo_run.
336 **kwargs: Keyword arguments to pass to cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800337
Alex Klein1699fab2022-09-08 08:46:06 -0600338 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600339 The value returned by calling cros_build_lib.sudo_run.
Alex Klein1699fab2022-09-08 08:46:06 -0600340 """
341 kwargs.setdefault("debug_level", logging.DEBUG)
342 return cros_build_lib.sudo_run(*args, **kwargs)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800343
344
345def GetLibFuzzerOption(option_name, option_value):
Alex Klein1699fab2022-09-08 08:46:06 -0600346 """Gets the libFuzzer command line option with the specified name and value.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800347
Alex Klein1699fab2022-09-08 08:46:06 -0600348 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600349 option_name: The name of the libFuzzer option.
350 option_value: The value of the libFuzzer option.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800351
Alex Klein1699fab2022-09-08 08:46:06 -0600352 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600353 The libFuzzer option composed of |option_name| and |option_value|.
Alex Klein1699fab2022-09-08 08:46:06 -0600354 """
355 return "-%s=%s" % (option_name, option_value)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800356
357
358def IsOptionLimit(option):
Alex Klein1699fab2022-09-08 08:46:06 -0600359 """Determines if fuzzer option limits fuzzing time."""
360 for limit_name in [MAX_TOTAL_TIME_OPTION_NAME, RUNS_OPTION_NAME]:
361 if option.startswith("-%s" % limit_name):
362 return True
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800363
Alex Klein1699fab2022-09-08 08:46:06 -0600364 return False
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800365
366
367def LimitFuzzing(fuzz_command, corpus):
Alex Klein1699fab2022-09-08 08:46:06 -0600368 """Limits how long fuzzing will go if unspecified.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800369
Alex Klein8b444532023-04-11 16:35:24 -0600370 Adds a reasonable limit on how much fuzzing will be done unless there
371 already is some kind of limit. Mutates fuzz_command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800372
Alex Klein1699fab2022-09-08 08:46:06 -0600373 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600374 fuzz_command: A command to run a fuzzer. Used to determine if a limit
375 needs to be set. Mutated if it is needed to specify a limit.
376 corpus: The corpus that will be passed to the fuzzer. If not None then
377 fuzzing is limited by running everything in the corpus once.
Alex Klein1699fab2022-09-08 08:46:06 -0600378 """
379 if any(IsOptionLimit(x) for x in fuzz_command[1:]):
380 # Don't do anything if there is already a limit.
381 return
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800382
Alex Klein1699fab2022-09-08 08:46:06 -0600383 if corpus:
384 # If there is a corpus, just run everything in the corpus once.
385 fuzz_command.append(
386 GetLibFuzzerOption(RUNS_OPTION_NAME, RUNS_DEFAULT_VALUE)
387 )
388 return
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800389
Alex Klein1699fab2022-09-08 08:46:06 -0600390 # Since there is no corpus, just fuzz for 30 seconds.
391 logging.info(
392 "Limiting fuzzing to %s seconds.", MAX_TOTAL_TIME_DEFAULT_VALUE
393 )
394 max_total_time_option = GetLibFuzzerOption(
395 MAX_TOTAL_TIME_OPTION_NAME, MAX_TOTAL_TIME_DEFAULT_VALUE
396 )
397 fuzz_command.append(max_total_time_option)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800398
399
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800400def GetFuzzExtraEnv(extra_options=None):
Alex Klein1699fab2022-09-08 08:46:06 -0600401 """Gets extra_env for fuzzing.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800402
Alex Klein8b444532023-04-11 16:35:24 -0600403 Gets environment variables and values for running libFuzzer. Sets defaults
404 and allows user to specify extra sanitizer options.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800405
Alex Klein1699fab2022-09-08 08:46:06 -0600406 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600407 extra_options: A dict containing sanitizer options to set in addition to
408 the defaults.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800409
Alex Klein1699fab2022-09-08 08:46:06 -0600410 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600411 A dict containing environment variables and their values that can be
412 used in the environment libFuzzer runs in.
Alex Klein1699fab2022-09-08 08:46:06 -0600413 """
414 if extra_options is None:
415 extra_options = {}
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800416
Alex Klein1699fab2022-09-08 08:46:06 -0600417 # log_path must be set because Chrome OS's patched compiler changes it.
418 # disable odr violation since many fuzzers hit it and it is also disabled on
419 # clusterfuzz.
Maksim Ivanovb4a6c4f2022-12-09 02:32:39 +0000420 # handle_sigtrap is useful for catching int3 in assertion checks in ChromeOS
421 # code.
422 options_dict = {
423 "log_path": "stderr",
424 "detect_odr_violation": "0",
425 "handle_sigtrap": "1",
426 }
Alex Klein1699fab2022-09-08 08:46:06 -0600427 options_dict.update(extra_options)
428 sanitizer_options = ":".join("%s=%s" % x for x in options_dict.items())
429 sanitizers = ("ASAN", "MSAN", "UBSAN")
430 return {x + "_OPTIONS": sanitizer_options for x in sanitizers}
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800431
432
Alex Klein1699fab2022-09-08 08:46:06 -0600433def RunFuzzer(
434 fuzzer,
435 corpus_path=None,
436 fuzz_args="",
437 testcase_path=None,
438 crash_expected=False,
439):
440 """Runs the fuzzer while chrooted into the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800441
Alex Klein1699fab2022-09-08 08:46:06 -0600442 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600443 fuzzer: The fuzzer to run.
444 corpus_path: A path to a corpus (not necessarily in the sysroot) to run
445 the fuzzer on.
446 fuzz_args: Additional arguments to pass to the fuzzer when running it.
447 testcase_path: A path to a testcase (not necessarily in the sysroot) to
448 run the fuzzer on.
449 crash_expected: Is it normal for the fuzzer to crash on this run?
Alex Klein1699fab2022-09-08 08:46:06 -0600450 """
451 logging.info("Running fuzzer: %s", fuzzer)
452 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
453 fuzz_command = [fuzzer_sysroot_path.sysroot]
454 fuzz_command += fuzz_args.split()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800455
Alex Klein1699fab2022-09-08 08:46:06 -0600456 if testcase_path:
457 fuzz_command.append(testcase_path)
458 else:
459 LimitFuzzing(fuzz_command, corpus_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800460
Alex Klein1699fab2022-09-08 08:46:06 -0600461 if corpus_path:
462 fuzz_command.append(corpus_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800463
Alex Klein1699fab2022-09-08 08:46:06 -0600464 if crash_expected:
Alex Klein8b444532023-04-11 16:35:24 -0600465 # Don't return nonzero when fuzzer OOMs, leaks, or timesout, since we
466 # don't want an exception in those cases. The user may be trying to
467 # reproduce those issues.
Alex Klein1699fab2022-09-08 08:46:06 -0600468 fuzz_command += ["-error_exitcode=0", "-timeout_exitcode=0"]
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800469
Alex Klein1699fab2022-09-08 08:46:06 -0600470 # We must set exitcode=0 or else the fuzzer will return nonzero on
471 # successful reproduction.
472 sanitizer_options = {"exitcode": "0"}
473 else:
474 sanitizer_options = {}
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800475
Alex Klein1699fab2022-09-08 08:46:06 -0600476 extra_env = GetFuzzExtraEnv(sanitizer_options)
477 RunSysrootCommand(
478 fuzz_command, extra_env=extra_env, debug_level=logging.INFO
479 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800480
481
482def MergeProfraw(fuzzer):
Alex Klein1699fab2022-09-08 08:46:06 -0600483 """Merges profraw file from a fuzzer and creates a profdata file.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800484
Alex Klein1699fab2022-09-08 08:46:06 -0600485 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600486 fuzzer: The fuzzer to merge the profraw file from.
Alex Klein1699fab2022-09-08 08:46:06 -0600487 """
488 profdata_path = GetProfdataPath(fuzzer)
489 command = [
490 "llvm-profdata",
491 "merge",
492 "-sparse",
493 DEFAULT_PROFRAW_PATH,
494 "-o",
495 profdata_path.sysroot,
496 ]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800497
Alex Klein1699fab2022-09-08 08:46:06 -0600498 RunSysrootCommand(command)
499 return profdata_path
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800500
501
502def GenerateCoverageReport(fuzzer, shared_libraries):
Alex Klein1699fab2022-09-08 08:46:06 -0600503 """Generates an HTML coverage report from a fuzzer run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800504
Alex Klein1699fab2022-09-08 08:46:06 -0600505 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600506 fuzzer: The fuzzer to generate the coverage report for.
507 shared_libraries: Libraries loaded dynamically by |fuzzer|.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800508
Alex Klein1699fab2022-09-08 08:46:06 -0600509 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600510 The path of the coverage report.
Alex Klein1699fab2022-09-08 08:46:06 -0600511 """
512 fuzzer_path = GetFuzzerSysrootPath(fuzzer).chroot
513 command = ["llvm-cov", "show", "-object", fuzzer_path]
514 for library in shared_libraries:
515 command += ["-object", library]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800516
Alex Klein1699fab2022-09-08 08:46:06 -0600517 coverage_directory = GetCoverageDirectory(fuzzer)
518 command += [
519 "-format=html",
520 "-instr-profile=%s" % GetProfdataPath(fuzzer).chroot,
521 "-output-dir=%s" % coverage_directory.chroot,
522 ]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800523
Alex Klein1699fab2022-09-08 08:46:06 -0600524 # TODO(metzman): Investigate error messages printed by this command.
525 cros_build_lib.run(command, stderr=True, debug_level=logging.DEBUG)
526 return coverage_directory
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800527
528
529def GetSharedLibraries(binary_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600530 """Gets the shared libraries used by a binary.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800531
Alex Klein8b444532023-04-11 16:35:24 -0600532 Gets the shared libraries used by the binary. Based on GetSharedLibraries
533 from src/tools/code_coverage/coverage_utils.py in Chromium.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600536 binary_path: The path to the binary we want to find the shared libraries
537 of.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800538
Alex Klein1699fab2022-09-08 08:46:06 -0600539 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600540 The shared libraries used by |binary_path|.
Alex Klein1699fab2022-09-08 08:46:06 -0600541 """
542 logging.info("Finding shared libraries for targets (if any).")
543 shared_libraries = []
544 elf_dict = lddtree.ParseELF(
545 binary_path.chroot, root=SysrootPath.path_to_sysroot
546 )
547 for shared_library in elf_dict["libs"].values():
548 shared_library_path = shared_library["path"]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800549
Alex Klein1699fab2022-09-08 08:46:06 -0600550 if shared_library_path in shared_libraries:
551 continue
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800552
Alex Klein1699fab2022-09-08 08:46:06 -0600553 assert os.path.exists(shared_library_path), (
554 'Shared library "%s" used by '
555 "the given target(s) does not "
556 "exist." % shared_library_path
557 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800558
Alex Klein1699fab2022-09-08 08:46:06 -0600559 if IsInstrumentedWithClangCoverage(shared_library_path):
Alex Klein8b444532023-04-11 16:35:24 -0600560 # Do not add non-instrumented libraries. Otherwise, llvm-cov errors
561 # out.
Alex Klein1699fab2022-09-08 08:46:06 -0600562 shared_libraries.append(shared_library_path)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800563
Alex Klein1699fab2022-09-08 08:46:06 -0600564 logging.debug(
565 "Found shared libraries (%d): %s.",
566 len(shared_libraries),
567 shared_libraries,
568 )
569 logging.info("Finished finding shared libraries for targets.")
570 return shared_libraries
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800571
572
573def IsInstrumentedWithClangCoverage(binary_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600574 """Determines if a binary is instrumented with clang source based coverage.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800575
Alex Klein1699fab2022-09-08 08:46:06 -0600576 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600577 binary_path: The path of the binary (executable or library) we are
578 checking is instrumented with clang source based coverage.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800579
Alex Klein1699fab2022-09-08 08:46:06 -0600580 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600581 True if the binary is instrumented with clang source based coverage.
Alex Klein1699fab2022-09-08 08:46:06 -0600582 """
583 with open(binary_path, "rb") as file_handle:
584 elf_file = ELFFile(file_handle)
585 return elf_file.get_section_by_name(b"__llvm_covmap") is not None
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800586
587
588def RunFuzzerAndGenerateCoverageReport(fuzzer, corpus, fuzz_args):
Alex Klein1699fab2022-09-08 08:46:06 -0600589 """Runs a fuzzer generates a coverage report and returns the report's path.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800590
Alex Klein1699fab2022-09-08 08:46:06 -0600591 Gets a coverage report for a fuzzer.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800592
Alex Klein1699fab2022-09-08 08:46:06 -0600593 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600594 fuzzer: The fuzzer to run and generate the coverage report for.
595 corpus: The path to a corpus to run the fuzzer on.
596 fuzz_args: Additional arguments to pass to the fuzzer.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800597
Alex Klein1699fab2022-09-08 08:46:06 -0600598 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600599 The path to the coverage report.
Alex Klein1699fab2022-09-08 08:46:06 -0600600 """
601 corpus_path = CopyCorpusToSysroot(corpus)
602 if corpus_path:
603 corpus_path = corpus_path.sysroot
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800604
Alex Klein1699fab2022-09-08 08:46:06 -0600605 RunFuzzer(fuzzer, corpus_path=corpus_path, fuzz_args=fuzz_args)
606 MergeProfraw(fuzzer)
607 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
608 shared_libraries = GetSharedLibraries(fuzzer_sysroot_path)
609 return GenerateCoverageReport(fuzzer, shared_libraries)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800610
611
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800612def RunSysrootCommand(command, **kwargs):
Alex Klein1699fab2022-09-08 08:46:06 -0600613 """Runs command while chrooted into sysroot and returns the output.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800614
Alex Klein1699fab2022-09-08 08:46:06 -0600615 Args:
Alex Klein361062b2023-04-05 09:45:28 -0600616 command: A command to run in the sysroot.
617 **kwargs: Extra arguments to pass to cros_build_lib.sudo_run.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800618
Alex Klein1699fab2022-09-08 08:46:06 -0600619 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600620 The result of a call to cros_build_lib.sudo_run.
Alex Klein1699fab2022-09-08 08:46:06 -0600621 """
622 command = ["chroot", SysrootPath.path_to_sysroot] + command
623 return sudo_run(command, **kwargs)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800624
625
626def GetBuildExtraEnv(build_type):
Alex Klein1699fab2022-09-08 08:46:06 -0600627 """Gets the extra_env for building a package.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800628
Alex Klein1699fab2022-09-08 08:46:06 -0600629 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600630 build_type: The type of build we want to do.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800631
Alex Klein1699fab2022-09-08 08:46:06 -0600632 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600633 The extra_env to use when building.
Alex Klein1699fab2022-09-08 08:46:06 -0600634 """
635 if build_type is None:
636 build_type = BuildType.ASAN
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800637
Alex Klein1699fab2022-09-08 08:46:06 -0600638 use_flags = os.environ.get("USE", "").split()
639 # Check that the user hasn't already set USE flags that we can set.
640 # No good way to iterate over an enum in python2.
641 for use_flag in BuildType.CHOICES:
642 if use_flag in use_flags:
643 logging.warning(
644 "%s in USE flags. Please use --build_type instead.", use_flag
645 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800646
Alex Klein1699fab2022-09-08 08:46:06 -0600647 # Set USE flags.
648 fuzzer_build_type = "fuzzer"
649 use_flags += [fuzzer_build_type, build_type]
650 features_flags = os.environ.get("FEATURES", "").split()
651 if build_type == BuildType.COVERAGE:
652 # We must use ASan when doing coverage builds.
653 use_flags.append(BuildType.ASAN)
Alex Klein8b444532023-04-11 16:35:24 -0600654 # Use noclean so that a coverage report can be generated based on the
655 # source code.
Alex Klein1699fab2022-09-08 08:46:06 -0600656 features_flags.append("noclean")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800657
Alex Klein1699fab2022-09-08 08:46:06 -0600658 return {
659 "FEATURES": " ".join(features_flags),
660 "USE": " ".join(use_flags),
661 }
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800662
663
664def BuildPackage(package, board, build_type):
Alex Klein1699fab2022-09-08 08:46:06 -0600665 """Builds a package on a specified board.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800666
Alex Klein1699fab2022-09-08 08:46:06 -0600667 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600668 package: The package to build. Nothing is built if None.
669 board: The board to build the package on.
670 build_type: The type of the build to do (e.g. asan, msan, ubsan,
671 coverage).
Alex Klein1699fab2022-09-08 08:46:06 -0600672 """
673 if package is None:
674 return
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800675
Alex Klein1699fab2022-09-08 08:46:06 -0600676 logging.info("Building %s using %s.", package, build_type)
677 extra_env = GetBuildExtraEnv(build_type)
678 command = [
679 "build_packages",
680 "--board",
681 board,
682 "--skip-chroot-upgrade",
683 package,
684 ]
685 # For msan builds, always use "--no-usepkg" since all package needs to be
686 # instrumented with msan.
687 if build_type == BuildType.MSAN:
688 command += ["--no-usepkg"]
Manoj Gupta5ca17652019-05-13 11:15:33 -0700689
Alex Klein1699fab2022-09-08 08:46:06 -0600690 # Print the output of the build command. Do this because it is familiar to
Alex Klein8b444532023-04-11 16:35:24 -0600691 # devs and we don't want to leave them not knowing about the build's
692 # progress for a long time.
Alex Klein1699fab2022-09-08 08:46:06 -0600693 cros_build_lib.run(command, extra_env=extra_env)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800694
695
696def DownloadFuzzerCorpus(fuzzer, dest_directory=None):
Alex Klein1699fab2022-09-08 08:46:06 -0600697 """Downloads a corpus and returns its path.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800698
Alex Klein1699fab2022-09-08 08:46:06 -0600699 Downloads a corpus to a subdirectory of dest_directory if specified and
700 returns path on the filesystem of the corpus. Asks users to authenticate
701 if permission to read from bucket is denied.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800702
Alex Klein1699fab2022-09-08 08:46:06 -0600703 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600704 fuzzer: The name of the fuzzer whose corpus we want to download.
705 dest_directory: The directory to download the corpus to.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800706
Alex Klein1699fab2022-09-08 08:46:06 -0600707 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600708 The path to the downloaded corpus.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800709
Alex Klein1699fab2022-09-08 08:46:06 -0600710 Raises:
Alex Klein8b444532023-04-11 16:35:24 -0600711 gs.NoSuchKey: A corpus for the fuzzer doesn't exist.
712 gs.GSCommandError: The corpus failed to download for another reason.
Alex Klein1699fab2022-09-08 08:46:06 -0600713 """
714 if not fuzzer.startswith("chromeos_"):
715 # ClusterFuzz internally appends "chromeos_" to chromeos targets' names.
716 # Therefore we must do so in order to find the corpus.
717 fuzzer = "chromeos_%s" % fuzzer
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800718
Alex Klein1699fab2022-09-08 08:46:06 -0600719 if dest_directory is None:
720 dest_directory = GetScriptStoragePath(CORPUS_DIRECTORY_NAME).chroot
721 osutils.SafeMakedirsNonRoot(dest_directory)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800722
Alex Klein1699fab2022-09-08 08:46:06 -0600723 clusterfuzz_gcs_corpus_bucket = "chromeos-corpus"
724 suburl = "libfuzzer/%s" % fuzzer
725 gcs_path = gs.GetGsURL(
726 clusterfuzz_gcs_corpus_bucket,
727 for_gsutil=True,
728 public=False,
729 suburl=suburl,
730 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800731
Alex Klein1699fab2022-09-08 08:46:06 -0600732 dest_path = os.path.join(dest_directory, fuzzer)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800733
Alex Klein1699fab2022-09-08 08:46:06 -0600734 try:
735 logging.info("Downloading corpus to %s.", dest_path)
736 ctx = gs.GSContext()
737 ctx.Copy(
738 gcs_path,
739 dest_directory,
740 recursive=True,
741 parallel=True,
742 debug_level=logging.DEBUG,
743 )
744 logging.info("Finished downloading corpus.")
745 except gs.GSNoSuchKey as exception:
746 logging.error("Corpus for fuzzer: %s does not exist.", fuzzer)
747 raise exception
748 # Try to authenticate if we were denied permission to access the corpus.
749 except gs.GSCommandError as exception:
750 logging.error(
Alex Klein8b444532023-04-11 16:35:24 -0600751 "gsutil failed to download the corpus. You may need to log in. "
752 "See:\n"
Alex Klein1699fab2022-09-08 08:46:06 -0600753 "https://chromium.googlesource.com/chromiumos/docs/+/HEAD/gsutil.md"
754 "#setup\n"
755 "for instructions on doing this."
756 )
757 raise exception
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800758
Alex Klein1699fab2022-09-08 08:46:06 -0600759 return dest_path
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800760
761
762def Reproduce(fuzzer, testcase_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600763 """Runs a fuzzer in the sysroot on a testcase.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800764
Alex Klein1699fab2022-09-08 08:46:06 -0600765 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600766 fuzzer: The fuzzer to run.
767 testcase_path: The path (not necessarily in the sysroot) of the testcase
768 to run the fuzzer on.
Alex Klein1699fab2022-09-08 08:46:06 -0600769 """
770 testcase_sysroot_path = CopyTestcaseToSysroot(testcase_path).sysroot
771 RunFuzzer(fuzzer, testcase_path=testcase_sysroot_path, crash_expected=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800772
773
774def SetUpSysrootForFuzzing():
Alex Klein8b444532023-04-11 16:35:24 -0600775 """Sets up the sysroot for fuzzing
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800776
Alex Klein1699fab2022-09-08 08:46:06 -0600777 Prepares the sysroot for fuzzing. Idempotent.
778 """
779 logging.info("Setting up sysroot for fuzzing.")
780 # TODO(metzman): Don't create devices or mount /proc, use platform2_test.py
781 # instead.
782 # Mount /proc in sysroot and setup dev there because they are needed by
783 # sanitizers.
784 proc_manager = ProcManager()
785 proc_manager.Mount()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800786
Alex Klein1699fab2022-09-08 08:46:06 -0600787 # Setup devices in /dev that are needed by libFuzzer.
788 device_manager = DeviceManager()
789 device_manager.SetUp()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800790
Alex Klein1699fab2022-09-08 08:46:06 -0600791 # Set up asan_symbolize.py, llvm-symbolizer, and llvm-profdata in the
792 # sysroot so that fuzzer output (including stack traces) can be symbolized
793 # and so that coverage reports can be generated.
794 tool_manager = ToolManager()
795 tool_manager.Install()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800796
Alex Klein1699fab2022-09-08 08:46:06 -0600797 osutils.SafeMakedirsNonRoot(GetSysrootPath(SCRIPT_STORAGE_PATH))
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800798
799
800def CleanUpSysroot():
Alex Klein1699fab2022-09-08 08:46:06 -0600801 """Cleans up the the sysroot from SetUpSysrootForFuzzing.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800802
Alex Klein1699fab2022-09-08 08:46:06 -0600803 Undoes SetUpSysrootForFuzzing. Idempotent.
804 """
805 logging.info("Cleaning up the sysroot.")
806 proc_manager = ProcManager()
807 proc_manager.Unmount()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800808
Alex Klein1699fab2022-09-08 08:46:06 -0600809 device_manager = DeviceManager()
810 device_manager.CleanUp()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800811
Alex Klein1699fab2022-09-08 08:46:06 -0600812 tool_manager = ToolManager()
813 tool_manager.Uninstall()
814 osutils.RmDir(GetSysrootPath(SCRIPT_STORAGE_PATH), ignore_missing=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800815
816
Alex Klein074f94f2023-06-22 10:32:06 -0600817class ToolManager:
Alex Klein1699fab2022-09-08 08:46:06 -0600818 """Class that installs or uninstalls fuzzing tools to/from the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800819
Alex Klein1699fab2022-09-08 08:46:06 -0600820 Install and Uninstall methods are idempotent. Both are safe to call at any
821 point.
822 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800823
Alex Klein1699fab2022-09-08 08:46:06 -0600824 # Path to asan_symbolize.py.
825 ASAN_SYMBOLIZE_PATH = os.path.join("/", "usr", "bin", "asan_symbolize.py")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800826
Alex Klein1699fab2022-09-08 08:46:06 -0600827 # List of LLVM binaries we must install in sysroot.
828 LLVM_BINARY_NAMES = ["gdbserver", "llvm-symbolizer", "llvm-profdata"]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800829
Alex Klein1699fab2022-09-08 08:46:06 -0600830 def __init__(self):
831 self.asan_symbolize_sysroot_path = GetSysrootPath(
832 self.ASAN_SYMBOLIZE_PATH
833 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800834
Alex Klein1699fab2022-09-08 08:46:06 -0600835 def Install(self):
836 """Installs tools to the sysroot."""
837 # Install asan_symbolize.py.
838 sudo_run(
839 ["cp", self.ASAN_SYMBOLIZE_PATH, self.asan_symbolize_sysroot_path]
840 )
841 # Install the LLVM binaries.
Alex Klein8b444532023-04-11 16:35:24 -0600842 # TODO(metzman): Build these tools so that we don't mess up when board
843 # is for a different ISA.
Alex Klein1699fab2022-09-08 08:46:06 -0600844 for llvm_binary in self._GetLLVMBinaries():
845 llvm_binary.Install()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800846
Alex Klein1699fab2022-09-08 08:46:06 -0600847 def Uninstall(self):
848 """Uninstalls tools from the sysroot. Undoes Install."""
849 # Uninstall asan_symbolize.py.
850 osutils.SafeUnlink(self.asan_symbolize_sysroot_path, sudo=True)
851 # Uninstall the LLVM binaries.
852 for llvm_binary in self._GetLLVMBinaries():
853 llvm_binary.Uninstall()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800854
Alex Klein1699fab2022-09-08 08:46:06 -0600855 def _GetLLVMBinaries(self):
Alex Klein8b444532023-04-11 16:35:24 -0600856 """Creates LlvmBinary objects for each binary in LLVM_BINARY_NAMES."""
Alex Klein1699fab2022-09-08 08:46:06 -0600857 return [LlvmBinary(x) for x in self.LLVM_BINARY_NAMES]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800858
859
Alex Klein074f94f2023-06-22 10:32:06 -0600860class LlvmBinary:
Alex Klein1699fab2022-09-08 08:46:06 -0600861 """Class for representing installing/uninstalling an LLVM binary in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800862
Alex Klein1699fab2022-09-08 08:46:06 -0600863 Install and Uninstall methods are idempotent. Both are safe to call at any
864 time.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800865 """
Manoj Guptafeb1b7a2019-02-20 11:04:05 -0800866
Alex Klein1699fab2022-09-08 08:46:06 -0600867 # Path to the lddtree chromite script.
Mike Frysinger164ec032023-03-27 16:15:14 -0400868 LDDTREE_SCRIPT_PATH = constants.CHROMITE_BIN_DIR / "lddtree"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800869
Alex Klein1699fab2022-09-08 08:46:06 -0600870 def __init__(self, binary):
871 self.binary = binary
872 self.install_dir = GetSysrootPath(
873 os.path.join("/", "usr", "libexec", binary)
874 )
875 self.binary_dir_path = GetSysrootPath(os.path.join("/", "usr", "bin"))
876 self.binary_chroot_dest_path = os.path.join(
877 self.binary_dir_path, binary
878 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800879
Alex Klein1699fab2022-09-08 08:46:06 -0600880 def Uninstall(self):
881 """Removes an LLVM binary from sysroot. Undoes Install."""
882 osutils.RmDir(self.install_dir, ignore_missing=True, sudo=True)
883 osutils.SafeUnlink(self.binary_chroot_dest_path, sudo=True)
884
885 def Install(self):
886 """Installs (sets up) an LLVM binary in the sysroot.
887
888 Sets up an llvm binary in the sysroot so that it can be run there.
889 """
Alex Klein8b444532023-04-11 16:35:24 -0600890 # Create a directory for installing |binary| and all of its dependencies
891 # in the sysroot.
Alex Klein1699fab2022-09-08 08:46:06 -0600892 binary_rel_path = ["usr", "bin", self.binary]
893 binary_chroot_path = os.path.join("/", *binary_rel_path)
894 if not os.path.exists(binary_chroot_path):
895 logging.warning(
896 "Cannot copy %s, file does not exist in chroot.",
897 binary_chroot_path,
898 )
899 logging.warning(
900 "Functionality provided by %s will be missing.",
901 binary_chroot_path,
902 )
903 return
904
905 osutils.SafeMakedirsNonRoot(self.install_dir)
906
907 # Copy the binary and everything needed to run it into the sysroot.
908 cmd = [
909 self.LDDTREE_SCRIPT_PATH,
910 "-v",
911 "--generate-wrappers",
912 "--root",
913 "/",
914 "--copy-to-tree",
915 self.install_dir,
916 binary_chroot_path,
917 ]
918 sudo_run(cmd)
919
920 # Create a symlink to the copy of the binary (we can't do lddtree in
921 # self.binary_dir_path). Note that symlink should be relative so that it
922 # will be valid when chrooted into the sysroot.
923 rel_path = os.path.relpath(self.install_dir, self.binary_dir_path)
924 link_path = os.path.join(rel_path, *binary_rel_path)
925 osutils.SafeSymlink(link_path, self.binary_chroot_dest_path, sudo=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800926
927
Alex Klein074f94f2023-06-22 10:32:06 -0600928class DeviceManager:
Alex Klein1699fab2022-09-08 08:46:06 -0600929 """Class that creates or removes devices from /dev in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800930
Alex Klein8b444532023-04-11 16:35:24 -0600931 SetUp and CleanUp methods are idempotent. Both are safe to call at any
932 point.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800933 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800934
Alex Klein1699fab2022-09-08 08:46:06 -0600935 DEVICE_MKNOD_PARAMS = {
936 "null": (666, 3),
937 "random": (444, 8),
938 "urandom": (444, 9),
939 }
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800940
Alex Klein1699fab2022-09-08 08:46:06 -0600941 MKNOD_MAJOR = "1"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800942
Alex Klein1699fab2022-09-08 08:46:06 -0600943 def __init__(self):
944 self.dev_path_chroot = GetSysrootPath("/dev")
945
946 def _GetDevicePath(self, device_name):
947 """Returns the path of |device_name| in sysroot's /dev."""
948 return os.path.join(self.dev_path_chroot, device_name)
949
950 def SetUp(self):
951 """Sets up devices in the sysroot's /dev.
952
Alex Klein8b444532023-04-11 16:35:24 -0600953 Creates /dev/null, /dev/random, and /dev/urandom. If they already exist
954 then recreates them.
Alex Klein1699fab2022-09-08 08:46:06 -0600955 """
956 self.CleanUp()
957 osutils.SafeMakedirsNonRoot(self.dev_path_chroot)
958 for device, mknod_params in self.DEVICE_MKNOD_PARAMS.items():
959 device_path = self._GetDevicePath(device)
960 self._MakeCharDevice(device_path, *mknod_params)
961
962 def CleanUp(self):
963 """Cleans up devices in the sysroot's /dev. Undoes SetUp.
964
965 Removes /dev/null, /dev/random, and /dev/urandom if they exist.
966 """
967 for device in self.DEVICE_MKNOD_PARAMS:
968 device_path = self._GetDevicePath(device)
969 if os.path.exists(device_path):
970 # Use -r since dev/null is sometimes a directory.
971 sudo_run(["rm", "-r", device_path])
972
973 def _MakeCharDevice(self, path, mode, minor):
974 """Make a character device."""
975 mode = str(mode)
976 minor = str(minor)
977 command = ["mknod", "-m", mode, path, "c", self.MKNOD_MAJOR, minor]
978 sudo_run(command)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800979
980
Alex Klein074f94f2023-06-22 10:32:06 -0600981class ProcManager:
Alex Klein1699fab2022-09-08 08:46:06 -0600982 """Class that mounts or unmounts /proc in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800983
Alex Klein1699fab2022-09-08 08:46:06 -0600984 Mount and Unmount are idempotent. Both are safe to call at any point.
985 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800986
Alex Klein1699fab2022-09-08 08:46:06 -0600987 PROC_PATH = "/proc"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800988
Alex Klein1699fab2022-09-08 08:46:06 -0600989 def __init__(self):
990 self.proc_path_chroot = GetSysrootPath(self.PROC_PATH)
991 self.is_mounted = osutils.IsMounted(self.proc_path_chroot)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800992
Alex Klein1699fab2022-09-08 08:46:06 -0600993 def Unmount(self):
994 """Unmounts /proc in chroot. Undoes Mount."""
995 if not self.is_mounted:
996 return
997 osutils.UmountDir(self.proc_path_chroot, cleanup=False)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800998
Alex Klein1699fab2022-09-08 08:46:06 -0600999 def Mount(self):
1000 """Mounts /proc in chroot. Remounts it if already mounted."""
1001 self.Unmount()
1002 osutils.MountDir(
1003 self.PROC_PATH,
1004 self.proc_path_chroot,
1005 "proc",
1006 debug_level=logging.DEBUG,
1007 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001008
1009
1010def EnterSysrootShell():
Alex Klein1699fab2022-09-08 08:46:06 -06001011 """Spawns and gives user access to a bash shell in the sysroot."""
1012 command = ["/bin/bash", "-i"]
1013 return RunSysrootCommand(
1014 command,
1015 extra_env=GetFuzzExtraEnv(),
1016 debug_level=logging.INFO,
1017 check=False,
1018 ).returncode
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001019
1020
1021def StripFuzzerPrefixes(fuzzer_name):
Alex Klein1699fab2022-09-08 08:46:06 -06001022 """Strip the prefix ClusterFuzz uses in case they are specified.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001023
Alex Klein1699fab2022-09-08 08:46:06 -06001024 Strip the prefixes used by ClusterFuzz if the users has included them by
1025 accident.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001026
Alex Klein1699fab2022-09-08 08:46:06 -06001027 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001028 fuzzer_name: The fuzzer whose name may contain prefixes.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001029
Alex Klein1699fab2022-09-08 08:46:06 -06001030 Returns:
Alex Klein8b444532023-04-11 16:35:24 -06001031 The name of the fuzz target without prefixes.
Alex Klein1699fab2022-09-08 08:46:06 -06001032 """
1033 initial_name = fuzzer_name
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001034
Alex Klein1699fab2022-09-08 08:46:06 -06001035 def StripPrefix(prefix):
1036 if fuzzer_name.startswith(prefix):
1037 return fuzzer_name[len(prefix) :]
1038 return fuzzer_name
1039
1040 clusterfuzz_prefixes = ["libFuzzer_", "chromeos_"]
1041
1042 for prefix in clusterfuzz_prefixes:
1043 fuzzer_name = StripPrefix(prefix)
1044
1045 if initial_name != fuzzer_name:
1046 logging.warning(
Alex Klein8b444532023-04-11 16:35:24 -06001047 "%s contains a prefix from ClusterFuzz (one or more of %s) that is "
1048 "not part of the fuzzer's name. Interpreting --fuzzer as %s.",
Alex Klein1699fab2022-09-08 08:46:06 -06001049 initial_name,
1050 clusterfuzz_prefixes,
1051 fuzzer_name,
1052 )
1053
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001054 return fuzzer_name
1055
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001056
1057def ExecuteShellCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001058 """Executes the "shell" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001059
Alex Klein1699fab2022-09-08 08:46:06 -06001060 Sets up the sysroot for fuzzing and gives user access to a bash shell it
1061 spawns in the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001062
Alex Klein1699fab2022-09-08 08:46:06 -06001063 Returns:
Alex Klein8b444532023-04-11 16:35:24 -06001064 The exit code of the shell command.
Alex Klein1699fab2022-09-08 08:46:06 -06001065 """
1066 SetUpSysrootForFuzzing()
1067 return EnterSysrootShell()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001068
1069
1070def ExecuteSetupCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001071 """Executes the "setup" command. Wrapper for SetUpSysrootForFuzzing.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001072
Alex Klein1699fab2022-09-08 08:46:06 -06001073 Sets up the sysroot for fuzzing.
1074 """
1075 SetUpSysrootForFuzzing()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001076
1077
1078def ExecuteCleanupCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001079 """Executes the "cleanup" command. Wrapper for CleanUpSysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001080
Alex Klein1699fab2022-09-08 08:46:06 -06001081 Undoes pre-fuzzing setup.
1082 """
1083 CleanUpSysroot()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001084
1085
1086def ExecuteCoverageCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001087 """Executes the "coverage" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001088
Alex Klein1699fab2022-09-08 08:46:06 -06001089 Executes the "coverage" command by optionally doing a coverage build of a
1090 package, optionally downloading the fuzzer's corpus, optionally copying it
1091 into the sysroot, running the fuzzer and then generating a coverage report
1092 for the user to view. Causes program to exit if fuzzer is not instrumented
1093 with source based coverage.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001094
Alex Klein1699fab2022-09-08 08:46:06 -06001095 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001096 options: The parsed arguments passed to this program.
Alex Klein1699fab2022-09-08 08:46:06 -06001097 """
1098 BuildPackage(options.package, options.board, BuildType.COVERAGE)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001099
Alex Klein1699fab2022-09-08 08:46:06 -06001100 fuzzer = StripFuzzerPrefixes(options.fuzzer)
1101 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
1102 if not IsInstrumentedWithClangCoverage(fuzzer_sysroot_path.chroot):
Alex Klein8b444532023-04-11 16:35:24 -06001103 # Don't run the fuzzer if it isn't instrumented with source based
1104 # coverage. Quit and let the user know how to build the fuzzer properly.
Alex Klein1699fab2022-09-08 08:46:06 -06001105 cros_build_lib.Die(
Alex Klein8b444532023-04-11 16:35:24 -06001106 "%s is not instrumented with source based coverage.\nSpecify "
1107 "--package to do a coverage build or build with USE flag: "
1108 '"coverage".',
Alex Klein1699fab2022-09-08 08:46:06 -06001109 fuzzer,
1110 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001111
Alex Klein1699fab2022-09-08 08:46:06 -06001112 corpus = options.corpus
1113 if options.download:
1114 corpus = DownloadFuzzerCorpus(options.fuzzer)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001115
Alex Klein1699fab2022-09-08 08:46:06 -06001116 # Set up sysroot for fuzzing.
1117 SetUpSysrootForFuzzing()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001118
Alex Klein1699fab2022-09-08 08:46:06 -06001119 coverage_report_path = RunFuzzerAndGenerateCoverageReport(
1120 fuzzer, corpus, options.fuzz_args
1121 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001122
Alex Klein1699fab2022-09-08 08:46:06 -06001123 # Get path on host so user can access it with their browser.
1124 # TODO(metzman): Add the ability to convert to host paths to path_util.
1125 external_trunk_path = os.getenv("EXTERNAL_TRUNK_PATH")
1126 coverage_report_host_path = os.path.join(
1127 external_trunk_path, "chroot", coverage_report_path.chroot[1:]
1128 )
1129 print(
1130 "Coverage report written to file://%s/index.html"
1131 % coverage_report_host_path
1132 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001133
1134
1135def ExecuteDownloadCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001136 """Executes the "download" command. Wrapper around DownloadFuzzerCorpus."""
1137 DownloadFuzzerCorpus(StripFuzzerPrefixes(options.fuzzer), options.directory)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001138
1139
1140def ExecuteReproduceCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001141 """Executes the "reproduce" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001142
Alex Klein1699fab2022-09-08 08:46:06 -06001143 Executes the "reproduce" command by Running a fuzzer on a testcase.
1144 May build the fuzzer before running.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001145
Alex Klein1699fab2022-09-08 08:46:06 -06001146 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001147 options: The parsed arguments passed to this program.
Alex Klein1699fab2022-09-08 08:46:06 -06001148 """
1149 if options.build_type and not options.package:
1150 raise Exception(
1151 "Cannot specify --build_type without specifying --package."
1152 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001153
Alex Klein1699fab2022-09-08 08:46:06 -06001154 # Verify that "msan-fuzzer" profile is being used with msan.
1155 # Check presence of "-fsanitize=memory" in CFLAGS.
1156 if options.build_type == BuildType.MSAN:
1157 cmd = ["portageq-%s" % options.board, "envvar", "CFLAGS"]
1158 cflags = cros_build_lib.run(
1159 cmd, capture_output=True, encoding="utf-8"
1160 ).stdout.splitlines()
1161 check_string = "-fsanitize=memory"
1162 if not any(check_string in s for s in cflags):
1163 logging.error(
1164 "-fsanitize=memory not found in CFLAGS. "
1165 'Use "setup_board --board=amd64-generic --profile=msan-fuzzer" '
1166 "for MSan Fuzzing Builds."
1167 )
1168 raise Exception("Incompatible profile used for msan fuzzing.")
Manoj Gupta5ca17652019-05-13 11:15:33 -07001169
Alex Klein1699fab2022-09-08 08:46:06 -06001170 BuildPackage(options.package, options.board, options.build_type)
1171 SetUpSysrootForFuzzing()
1172 Reproduce(StripFuzzerPrefixes(options.fuzzer), options.testcase)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001173
Manoj Guptae5e1e612019-10-21 12:39:57 -07001174
Manoj Guptaec08b812019-10-10 14:21:16 -07001175def InstallBaseDependencies(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001176 """Installs the base packages needed to chroot in board sysroot.
Manoj Guptaec08b812019-10-10 14:21:16 -07001177
Alex Klein1699fab2022-09-08 08:46:06 -06001178 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001179 options: The parsed arguments passed to this program.
Alex Klein1699fab2022-09-08 08:46:06 -06001180 """
1181 package = "virtual/implicit-system"
1182 if not portage_util.IsPackageInstalled(
1183 package, sysroot=SysrootPath.path_to_sysroot
1184 ):
1185 build_type = getattr(options, "build_type", None)
1186 BuildPackage(package, options.board, build_type)
Manoj Guptae5e1e612019-10-21 12:39:57 -07001187
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001188
1189def ParseArgs(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001190 """Parses program arguments.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001191
Alex Klein1699fab2022-09-08 08:46:06 -06001192 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001193 argv: The program arguments we want to parse.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001194
Alex Klein1699fab2022-09-08 08:46:06 -06001195 Returns:
Alex Klein8b444532023-04-11 16:35:24 -06001196 An options object which will tell us which command to run and which
1197 options to use for that command.
Alex Klein1699fab2022-09-08 08:46:06 -06001198 """
1199 parser = commandline.ArgumentParser(description=__doc__)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001200
Alex Klein1699fab2022-09-08 08:46:06 -06001201 parser.add_argument(
1202 "--board",
1203 default=cros_build_lib.GetDefaultBoard(),
1204 help="Board on which to run test.",
1205 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001206
Alex Klein1699fab2022-09-08 08:46:06 -06001207 subparsers = parser.add_subparsers(dest="command")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001208
Alex Klein1699fab2022-09-08 08:46:06 -06001209 subparsers.add_parser("cleanup", help="Undo setup command.")
1210 coverage_parser = subparsers.add_parser(
1211 "coverage", help="Get a coverage report for a fuzzer."
1212 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001213
Alex Klein1699fab2022-09-08 08:46:06 -06001214 coverage_parser.add_argument("--package", help="Package to build.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001215
Alex Klein1699fab2022-09-08 08:46:06 -06001216 corpus_parser = coverage_parser.add_mutually_exclusive_group()
1217 corpus_parser.add_argument("--corpus", help="Corpus to run fuzzer on.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001218
Alex Klein1699fab2022-09-08 08:46:06 -06001219 corpus_parser.add_argument(
1220 "--download",
1221 action="store_true",
1222 help="Generate coverage report based on corpus from ClusterFuzz.",
1223 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001224
Alex Klein1699fab2022-09-08 08:46:06 -06001225 coverage_parser.add_argument(
1226 "--fuzzer",
1227 required=True,
1228 help="The fuzz target to generate a coverage report for.",
1229 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001230
Alex Klein1699fab2022-09-08 08:46:06 -06001231 coverage_parser.add_argument(
1232 "--fuzz-args",
1233 default="",
1234 help="Arguments to pass libFuzzer. "
1235 "Please use an equals sign or parsing will fail "
1236 '(i.e. --fuzzer_args="-rss_limit_mb=2048 -print_funcs=1").',
1237 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001238
Alex Klein1699fab2022-09-08 08:46:06 -06001239 download_parser = subparsers.add_parser(
1240 "download", help="Download a corpus."
1241 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001242
Alex Klein1699fab2022-09-08 08:46:06 -06001243 download_parser.add_argument(
1244 "--directory", help="Path to directory to download the corpus to."
1245 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001246
Alex Klein1699fab2022-09-08 08:46:06 -06001247 download_parser.add_argument(
1248 "--fuzzer", required=True, help="Fuzzer to download the corpus for."
1249 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001250
Alex Klein1699fab2022-09-08 08:46:06 -06001251 reproduce_parser = subparsers.add_parser(
1252 "reproduce", help="Run a fuzzer on a testcase."
1253 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001254
Alex Klein1699fab2022-09-08 08:46:06 -06001255 reproduce_parser.add_argument(
1256 "--testcase", required=True, help="Path of testcase to run fuzzer on."
1257 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001258
Alex Klein1699fab2022-09-08 08:46:06 -06001259 reproduce_parser.add_argument(
1260 "--fuzzer", required=True, help="Fuzzer to reproduce the crash on."
1261 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001262
Alex Klein1699fab2022-09-08 08:46:06 -06001263 reproduce_parser.add_argument("--package", help="Package to build.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001264
Alex Klein1699fab2022-09-08 08:46:06 -06001265 reproduce_parser.add_argument(
1266 "--build-type",
1267 choices=BuildType.CHOICES,
1268 help="Type of build.",
1269 type=str.lower,
1270 ) # Ignore sanitizer case.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001271
Alex Klein1699fab2022-09-08 08:46:06 -06001272 subparsers.add_parser("setup", help="Set up the sysroot to test fuzzing.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001273
Alex Klein1699fab2022-09-08 08:46:06 -06001274 subparsers.add_parser(
1275 "shell",
1276 help="Set up sysroot for fuzzing and get a shell in the sysroot.",
1277 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001278
Alex Klein1699fab2022-09-08 08:46:06 -06001279 opts = parser.parse_args(argv)
1280 opts.Freeze()
1281 return opts
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001282
1283
1284def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001285 """Parses arguments and executes a command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001286
Alex Klein1699fab2022-09-08 08:46:06 -06001287 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001288 argv: The program arguments.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001289
Alex Klein1699fab2022-09-08 08:46:06 -06001290 Returns:
Alex Klein8b444532023-04-11 16:35:24 -06001291 0 on success. Non-zero on failure.
Alex Klein1699fab2022-09-08 08:46:06 -06001292 """
1293 cros_build_lib.AssertInsideChroot()
1294 options = ParseArgs(argv)
1295 if options.board is None:
1296 logging.error('Please specify "--board" or set ".default_board".')
1297 return 1
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001298
Alex Klein1699fab2022-09-08 08:46:06 -06001299 SysrootPath.SetPathToSysroot(options.board)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001300
Alex Klein1699fab2022-09-08 08:46:06 -06001301 InstallBaseDependencies(options)
Manoj Guptaec08b812019-10-10 14:21:16 -07001302
Alex Klein1699fab2022-09-08 08:46:06 -06001303 if options.command == "cleanup":
1304 ExecuteCleanupCommand()
1305 elif options.command == "coverage":
1306 ExecuteCoverageCommand(options)
1307 elif options.command == "setup":
1308 ExecuteSetupCommand()
1309 elif options.command == "download":
1310 ExecuteDownloadCommand(options)
1311 elif options.command == "reproduce":
1312 ExecuteReproduceCommand(options)
1313 elif options.command == "shell":
1314 return ExecuteShellCommand()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001315
Alex Klein1699fab2022-09-08 08:46:06 -06001316 return 0