blob: b14eb96e7f74ca6e91febf47110dbda3cf9ae882 [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 = [
Brian Norris45d42922023-08-01 14:56:42 -0700679 "cros",
680 "build-packages",
Alex Klein1699fab2022-09-08 08:46:06 -0600681 "--board",
682 board,
683 "--skip-chroot-upgrade",
684 package,
685 ]
686 # For msan builds, always use "--no-usepkg" since all package needs to be
687 # instrumented with msan.
688 if build_type == BuildType.MSAN:
689 command += ["--no-usepkg"]
Manoj Gupta5ca17652019-05-13 11:15:33 -0700690
Alex Klein1699fab2022-09-08 08:46:06 -0600691 # Print the output of the build command. Do this because it is familiar to
Alex Klein8b444532023-04-11 16:35:24 -0600692 # devs and we don't want to leave them not knowing about the build's
693 # progress for a long time.
Alex Klein1699fab2022-09-08 08:46:06 -0600694 cros_build_lib.run(command, extra_env=extra_env)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800695
696
697def DownloadFuzzerCorpus(fuzzer, dest_directory=None):
Alex Klein1699fab2022-09-08 08:46:06 -0600698 """Downloads a corpus and returns its path.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800699
Alex Klein1699fab2022-09-08 08:46:06 -0600700 Downloads a corpus to a subdirectory of dest_directory if specified and
701 returns path on the filesystem of the corpus. Asks users to authenticate
702 if permission to read from bucket is denied.
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800703
Alex Klein1699fab2022-09-08 08:46:06 -0600704 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600705 fuzzer: The name of the fuzzer whose corpus we want to download.
706 dest_directory: The directory to download the corpus to.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800707
Alex Klein1699fab2022-09-08 08:46:06 -0600708 Returns:
Alex Klein8b444532023-04-11 16:35:24 -0600709 The path to the downloaded corpus.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800710
Alex Klein1699fab2022-09-08 08:46:06 -0600711 Raises:
Alex Klein8b444532023-04-11 16:35:24 -0600712 gs.NoSuchKey: A corpus for the fuzzer doesn't exist.
713 gs.GSCommandError: The corpus failed to download for another reason.
Alex Klein1699fab2022-09-08 08:46:06 -0600714 """
715 if not fuzzer.startswith("chromeos_"):
716 # ClusterFuzz internally appends "chromeos_" to chromeos targets' names.
717 # Therefore we must do so in order to find the corpus.
718 fuzzer = "chromeos_%s" % fuzzer
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800719
Alex Klein1699fab2022-09-08 08:46:06 -0600720 if dest_directory is None:
721 dest_directory = GetScriptStoragePath(CORPUS_DIRECTORY_NAME).chroot
722 osutils.SafeMakedirsNonRoot(dest_directory)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800723
Alex Klein1699fab2022-09-08 08:46:06 -0600724 clusterfuzz_gcs_corpus_bucket = "chromeos-corpus"
725 suburl = "libfuzzer/%s" % fuzzer
726 gcs_path = gs.GetGsURL(
727 clusterfuzz_gcs_corpus_bucket,
728 for_gsutil=True,
729 public=False,
730 suburl=suburl,
731 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800732
Alex Klein1699fab2022-09-08 08:46:06 -0600733 dest_path = os.path.join(dest_directory, fuzzer)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800734
Alex Klein1699fab2022-09-08 08:46:06 -0600735 try:
736 logging.info("Downloading corpus to %s.", dest_path)
737 ctx = gs.GSContext()
738 ctx.Copy(
739 gcs_path,
740 dest_directory,
741 recursive=True,
742 parallel=True,
743 debug_level=logging.DEBUG,
744 )
745 logging.info("Finished downloading corpus.")
746 except gs.GSNoSuchKey as exception:
747 logging.error("Corpus for fuzzer: %s does not exist.", fuzzer)
748 raise exception
749 # Try to authenticate if we were denied permission to access the corpus.
750 except gs.GSCommandError as exception:
751 logging.error(
Alex Klein8b444532023-04-11 16:35:24 -0600752 "gsutil failed to download the corpus. You may need to log in. "
753 "See:\n"
Alex Klein1699fab2022-09-08 08:46:06 -0600754 "https://chromium.googlesource.com/chromiumos/docs/+/HEAD/gsutil.md"
755 "#setup\n"
756 "for instructions on doing this."
757 )
758 raise exception
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800759
Alex Klein1699fab2022-09-08 08:46:06 -0600760 return dest_path
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800761
762
763def Reproduce(fuzzer, testcase_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600764 """Runs a fuzzer in the sysroot on a testcase.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800765
Alex Klein1699fab2022-09-08 08:46:06 -0600766 Args:
Alex Klein8b444532023-04-11 16:35:24 -0600767 fuzzer: The fuzzer to run.
768 testcase_path: The path (not necessarily in the sysroot) of the testcase
769 to run the fuzzer on.
Alex Klein1699fab2022-09-08 08:46:06 -0600770 """
771 testcase_sysroot_path = CopyTestcaseToSysroot(testcase_path).sysroot
772 RunFuzzer(fuzzer, testcase_path=testcase_sysroot_path, crash_expected=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800773
774
775def SetUpSysrootForFuzzing():
Alex Klein8b444532023-04-11 16:35:24 -0600776 """Sets up the sysroot for fuzzing
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800777
Alex Klein1699fab2022-09-08 08:46:06 -0600778 Prepares the sysroot for fuzzing. Idempotent.
779 """
780 logging.info("Setting up sysroot for fuzzing.")
781 # TODO(metzman): Don't create devices or mount /proc, use platform2_test.py
782 # instead.
783 # Mount /proc in sysroot and setup dev there because they are needed by
784 # sanitizers.
785 proc_manager = ProcManager()
786 proc_manager.Mount()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800787
Alex Klein1699fab2022-09-08 08:46:06 -0600788 # Setup devices in /dev that are needed by libFuzzer.
789 device_manager = DeviceManager()
790 device_manager.SetUp()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800791
Alex Klein1699fab2022-09-08 08:46:06 -0600792 # Set up asan_symbolize.py, llvm-symbolizer, and llvm-profdata in the
793 # sysroot so that fuzzer output (including stack traces) can be symbolized
794 # and so that coverage reports can be generated.
795 tool_manager = ToolManager()
796 tool_manager.Install()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800797
Alex Klein1699fab2022-09-08 08:46:06 -0600798 osutils.SafeMakedirsNonRoot(GetSysrootPath(SCRIPT_STORAGE_PATH))
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800799
800
801def CleanUpSysroot():
Alex Klein1699fab2022-09-08 08:46:06 -0600802 """Cleans up the the sysroot from SetUpSysrootForFuzzing.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800803
Alex Klein1699fab2022-09-08 08:46:06 -0600804 Undoes SetUpSysrootForFuzzing. Idempotent.
805 """
806 logging.info("Cleaning up the sysroot.")
807 proc_manager = ProcManager()
808 proc_manager.Unmount()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800809
Alex Klein1699fab2022-09-08 08:46:06 -0600810 device_manager = DeviceManager()
811 device_manager.CleanUp()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800812
Alex Klein1699fab2022-09-08 08:46:06 -0600813 tool_manager = ToolManager()
814 tool_manager.Uninstall()
815 osutils.RmDir(GetSysrootPath(SCRIPT_STORAGE_PATH), ignore_missing=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800816
817
Alex Klein074f94f2023-06-22 10:32:06 -0600818class ToolManager:
Alex Klein1699fab2022-09-08 08:46:06 -0600819 """Class that installs or uninstalls fuzzing tools to/from the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800820
Alex Klein1699fab2022-09-08 08:46:06 -0600821 Install and Uninstall methods are idempotent. Both are safe to call at any
822 point.
823 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800824
Alex Klein1699fab2022-09-08 08:46:06 -0600825 # Path to asan_symbolize.py.
826 ASAN_SYMBOLIZE_PATH = os.path.join("/", "usr", "bin", "asan_symbolize.py")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800827
Alex Klein1699fab2022-09-08 08:46:06 -0600828 # List of LLVM binaries we must install in sysroot.
829 LLVM_BINARY_NAMES = ["gdbserver", "llvm-symbolizer", "llvm-profdata"]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800830
Alex Klein1699fab2022-09-08 08:46:06 -0600831 def __init__(self):
832 self.asan_symbolize_sysroot_path = GetSysrootPath(
833 self.ASAN_SYMBOLIZE_PATH
834 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800835
Alex Klein1699fab2022-09-08 08:46:06 -0600836 def Install(self):
837 """Installs tools to the sysroot."""
838 # Install asan_symbolize.py.
839 sudo_run(
840 ["cp", self.ASAN_SYMBOLIZE_PATH, self.asan_symbolize_sysroot_path]
841 )
842 # Install the LLVM binaries.
Alex Klein8b444532023-04-11 16:35:24 -0600843 # TODO(metzman): Build these tools so that we don't mess up when board
844 # is for a different ISA.
Alex Klein1699fab2022-09-08 08:46:06 -0600845 for llvm_binary in self._GetLLVMBinaries():
846 llvm_binary.Install()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800847
Alex Klein1699fab2022-09-08 08:46:06 -0600848 def Uninstall(self):
849 """Uninstalls tools from the sysroot. Undoes Install."""
850 # Uninstall asan_symbolize.py.
851 osutils.SafeUnlink(self.asan_symbolize_sysroot_path, sudo=True)
852 # Uninstall the LLVM binaries.
853 for llvm_binary in self._GetLLVMBinaries():
854 llvm_binary.Uninstall()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800855
Alex Klein1699fab2022-09-08 08:46:06 -0600856 def _GetLLVMBinaries(self):
Alex Klein8b444532023-04-11 16:35:24 -0600857 """Creates LlvmBinary objects for each binary in LLVM_BINARY_NAMES."""
Alex Klein1699fab2022-09-08 08:46:06 -0600858 return [LlvmBinary(x) for x in self.LLVM_BINARY_NAMES]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800859
860
Alex Klein074f94f2023-06-22 10:32:06 -0600861class LlvmBinary:
Alex Klein1699fab2022-09-08 08:46:06 -0600862 """Class for representing installing/uninstalling an LLVM binary in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800863
Alex Klein1699fab2022-09-08 08:46:06 -0600864 Install and Uninstall methods are idempotent. Both are safe to call at any
865 time.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800866 """
Manoj Guptafeb1b7a2019-02-20 11:04:05 -0800867
Alex Klein1699fab2022-09-08 08:46:06 -0600868 # Path to the lddtree chromite script.
Mike Frysinger164ec032023-03-27 16:15:14 -0400869 LDDTREE_SCRIPT_PATH = constants.CHROMITE_BIN_DIR / "lddtree"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800870
Alex Klein1699fab2022-09-08 08:46:06 -0600871 def __init__(self, binary):
872 self.binary = binary
873 self.install_dir = GetSysrootPath(
874 os.path.join("/", "usr", "libexec", binary)
875 )
876 self.binary_dir_path = GetSysrootPath(os.path.join("/", "usr", "bin"))
877 self.binary_chroot_dest_path = os.path.join(
878 self.binary_dir_path, binary
879 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800880
Alex Klein1699fab2022-09-08 08:46:06 -0600881 def Uninstall(self):
882 """Removes an LLVM binary from sysroot. Undoes Install."""
883 osutils.RmDir(self.install_dir, ignore_missing=True, sudo=True)
884 osutils.SafeUnlink(self.binary_chroot_dest_path, sudo=True)
885
886 def Install(self):
887 """Installs (sets up) an LLVM binary in the sysroot.
888
889 Sets up an llvm binary in the sysroot so that it can be run there.
890 """
Alex Klein8b444532023-04-11 16:35:24 -0600891 # Create a directory for installing |binary| and all of its dependencies
892 # in the sysroot.
Alex Klein1699fab2022-09-08 08:46:06 -0600893 binary_rel_path = ["usr", "bin", self.binary]
894 binary_chroot_path = os.path.join("/", *binary_rel_path)
895 if not os.path.exists(binary_chroot_path):
896 logging.warning(
897 "Cannot copy %s, file does not exist in chroot.",
898 binary_chroot_path,
899 )
900 logging.warning(
901 "Functionality provided by %s will be missing.",
902 binary_chroot_path,
903 )
904 return
905
906 osutils.SafeMakedirsNonRoot(self.install_dir)
907
908 # Copy the binary and everything needed to run it into the sysroot.
909 cmd = [
910 self.LDDTREE_SCRIPT_PATH,
911 "-v",
912 "--generate-wrappers",
913 "--root",
914 "/",
915 "--copy-to-tree",
916 self.install_dir,
917 binary_chroot_path,
918 ]
919 sudo_run(cmd)
920
921 # Create a symlink to the copy of the binary (we can't do lddtree in
922 # self.binary_dir_path). Note that symlink should be relative so that it
923 # will be valid when chrooted into the sysroot.
924 rel_path = os.path.relpath(self.install_dir, self.binary_dir_path)
925 link_path = os.path.join(rel_path, *binary_rel_path)
926 osutils.SafeSymlink(link_path, self.binary_chroot_dest_path, sudo=True)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800927
928
Alex Klein074f94f2023-06-22 10:32:06 -0600929class DeviceManager:
Alex Klein1699fab2022-09-08 08:46:06 -0600930 """Class that creates or removes devices from /dev in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800931
Alex Klein8b444532023-04-11 16:35:24 -0600932 SetUp and CleanUp methods are idempotent. Both are safe to call at any
933 point.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800934 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800935
Alex Klein1699fab2022-09-08 08:46:06 -0600936 DEVICE_MKNOD_PARAMS = {
937 "null": (666, 3),
938 "random": (444, 8),
939 "urandom": (444, 9),
940 }
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800941
Alex Klein1699fab2022-09-08 08:46:06 -0600942 MKNOD_MAJOR = "1"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800943
Alex Klein1699fab2022-09-08 08:46:06 -0600944 def __init__(self):
945 self.dev_path_chroot = GetSysrootPath("/dev")
946
947 def _GetDevicePath(self, device_name):
948 """Returns the path of |device_name| in sysroot's /dev."""
949 return os.path.join(self.dev_path_chroot, device_name)
950
951 def SetUp(self):
952 """Sets up devices in the sysroot's /dev.
953
Alex Klein8b444532023-04-11 16:35:24 -0600954 Creates /dev/null, /dev/random, and /dev/urandom. If they already exist
955 then recreates them.
Alex Klein1699fab2022-09-08 08:46:06 -0600956 """
957 self.CleanUp()
958 osutils.SafeMakedirsNonRoot(self.dev_path_chroot)
959 for device, mknod_params in self.DEVICE_MKNOD_PARAMS.items():
960 device_path = self._GetDevicePath(device)
961 self._MakeCharDevice(device_path, *mknod_params)
962
963 def CleanUp(self):
964 """Cleans up devices in the sysroot's /dev. Undoes SetUp.
965
966 Removes /dev/null, /dev/random, and /dev/urandom if they exist.
967 """
968 for device in self.DEVICE_MKNOD_PARAMS:
969 device_path = self._GetDevicePath(device)
970 if os.path.exists(device_path):
971 # Use -r since dev/null is sometimes a directory.
972 sudo_run(["rm", "-r", device_path])
973
974 def _MakeCharDevice(self, path, mode, minor):
975 """Make a character device."""
976 mode = str(mode)
977 minor = str(minor)
978 command = ["mknod", "-m", mode, path, "c", self.MKNOD_MAJOR, minor]
979 sudo_run(command)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800980
981
Alex Klein074f94f2023-06-22 10:32:06 -0600982class ProcManager:
Alex Klein1699fab2022-09-08 08:46:06 -0600983 """Class that mounts or unmounts /proc in sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800984
Alex Klein1699fab2022-09-08 08:46:06 -0600985 Mount and Unmount are idempotent. Both are safe to call at any point.
986 """
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800987
Alex Klein1699fab2022-09-08 08:46:06 -0600988 PROC_PATH = "/proc"
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800989
Alex Klein1699fab2022-09-08 08:46:06 -0600990 def __init__(self):
991 self.proc_path_chroot = GetSysrootPath(self.PROC_PATH)
992 self.is_mounted = osutils.IsMounted(self.proc_path_chroot)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800993
Alex Klein1699fab2022-09-08 08:46:06 -0600994 def Unmount(self):
995 """Unmounts /proc in chroot. Undoes Mount."""
996 if not self.is_mounted:
997 return
998 osutils.UmountDir(self.proc_path_chroot, cleanup=False)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800999
Alex Klein1699fab2022-09-08 08:46:06 -06001000 def Mount(self):
1001 """Mounts /proc in chroot. Remounts it if already mounted."""
1002 self.Unmount()
1003 osutils.MountDir(
1004 self.PROC_PATH,
1005 self.proc_path_chroot,
1006 "proc",
1007 debug_level=logging.DEBUG,
1008 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001009
1010
1011def EnterSysrootShell():
Alex Klein1699fab2022-09-08 08:46:06 -06001012 """Spawns and gives user access to a bash shell in the sysroot."""
1013 command = ["/bin/bash", "-i"]
1014 return RunSysrootCommand(
1015 command,
1016 extra_env=GetFuzzExtraEnv(),
1017 debug_level=logging.INFO,
1018 check=False,
1019 ).returncode
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001020
1021
1022def StripFuzzerPrefixes(fuzzer_name):
Alex Klein1699fab2022-09-08 08:46:06 -06001023 """Strip the prefix ClusterFuzz uses in case they are specified.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001024
Alex Klein1699fab2022-09-08 08:46:06 -06001025 Strip the prefixes used by ClusterFuzz if the users has included them by
1026 accident.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001027
Alex Klein1699fab2022-09-08 08:46:06 -06001028 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001029 fuzzer_name: The fuzzer whose name may contain prefixes.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001030
Alex Klein1699fab2022-09-08 08:46:06 -06001031 Returns:
Alex Klein8b444532023-04-11 16:35:24 -06001032 The name of the fuzz target without prefixes.
Alex Klein1699fab2022-09-08 08:46:06 -06001033 """
1034 initial_name = fuzzer_name
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001035
Alex Klein1699fab2022-09-08 08:46:06 -06001036 def StripPrefix(prefix):
1037 if fuzzer_name.startswith(prefix):
1038 return fuzzer_name[len(prefix) :]
1039 return fuzzer_name
1040
1041 clusterfuzz_prefixes = ["libFuzzer_", "chromeos_"]
1042
1043 for prefix in clusterfuzz_prefixes:
1044 fuzzer_name = StripPrefix(prefix)
1045
1046 if initial_name != fuzzer_name:
1047 logging.warning(
Alex Klein8b444532023-04-11 16:35:24 -06001048 "%s contains a prefix from ClusterFuzz (one or more of %s) that is "
1049 "not part of the fuzzer's name. Interpreting --fuzzer as %s.",
Alex Klein1699fab2022-09-08 08:46:06 -06001050 initial_name,
1051 clusterfuzz_prefixes,
1052 fuzzer_name,
1053 )
1054
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001055 return fuzzer_name
1056
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001057
1058def ExecuteShellCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001059 """Executes the "shell" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001060
Alex Klein1699fab2022-09-08 08:46:06 -06001061 Sets up the sysroot for fuzzing and gives user access to a bash shell it
1062 spawns in the sysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001063
Alex Klein1699fab2022-09-08 08:46:06 -06001064 Returns:
Alex Klein8b444532023-04-11 16:35:24 -06001065 The exit code of the shell command.
Alex Klein1699fab2022-09-08 08:46:06 -06001066 """
1067 SetUpSysrootForFuzzing()
1068 return EnterSysrootShell()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001069
1070
1071def ExecuteSetupCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001072 """Executes the "setup" command. Wrapper for SetUpSysrootForFuzzing.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001073
Alex Klein1699fab2022-09-08 08:46:06 -06001074 Sets up the sysroot for fuzzing.
1075 """
1076 SetUpSysrootForFuzzing()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001077
1078
1079def ExecuteCleanupCommand():
Alex Klein1699fab2022-09-08 08:46:06 -06001080 """Executes the "cleanup" command. Wrapper for CleanUpSysroot.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001081
Alex Klein1699fab2022-09-08 08:46:06 -06001082 Undoes pre-fuzzing setup.
1083 """
1084 CleanUpSysroot()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001085
1086
1087def ExecuteCoverageCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001088 """Executes the "coverage" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001089
Alex Klein1699fab2022-09-08 08:46:06 -06001090 Executes the "coverage" command by optionally doing a coverage build of a
1091 package, optionally downloading the fuzzer's corpus, optionally copying it
1092 into the sysroot, running the fuzzer and then generating a coverage report
1093 for the user to view. Causes program to exit if fuzzer is not instrumented
1094 with source based coverage.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001095
Alex Klein1699fab2022-09-08 08:46:06 -06001096 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001097 options: The parsed arguments passed to this program.
Alex Klein1699fab2022-09-08 08:46:06 -06001098 """
1099 BuildPackage(options.package, options.board, BuildType.COVERAGE)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001100
Alex Klein1699fab2022-09-08 08:46:06 -06001101 fuzzer = StripFuzzerPrefixes(options.fuzzer)
1102 fuzzer_sysroot_path = GetFuzzerSysrootPath(fuzzer)
1103 if not IsInstrumentedWithClangCoverage(fuzzer_sysroot_path.chroot):
Alex Klein8b444532023-04-11 16:35:24 -06001104 # Don't run the fuzzer if it isn't instrumented with source based
1105 # coverage. Quit and let the user know how to build the fuzzer properly.
Alex Klein1699fab2022-09-08 08:46:06 -06001106 cros_build_lib.Die(
Alex Klein8b444532023-04-11 16:35:24 -06001107 "%s is not instrumented with source based coverage.\nSpecify "
1108 "--package to do a coverage build or build with USE flag: "
1109 '"coverage".',
Alex Klein1699fab2022-09-08 08:46:06 -06001110 fuzzer,
1111 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001112
Alex Klein1699fab2022-09-08 08:46:06 -06001113 corpus = options.corpus
1114 if options.download:
1115 corpus = DownloadFuzzerCorpus(options.fuzzer)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001116
Alex Klein1699fab2022-09-08 08:46:06 -06001117 # Set up sysroot for fuzzing.
1118 SetUpSysrootForFuzzing()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001119
Alex Klein1699fab2022-09-08 08:46:06 -06001120 coverage_report_path = RunFuzzerAndGenerateCoverageReport(
1121 fuzzer, corpus, options.fuzz_args
1122 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001123
Alex Klein1699fab2022-09-08 08:46:06 -06001124 # Get path on host so user can access it with their browser.
1125 # TODO(metzman): Add the ability to convert to host paths to path_util.
1126 external_trunk_path = os.getenv("EXTERNAL_TRUNK_PATH")
1127 coverage_report_host_path = os.path.join(
1128 external_trunk_path, "chroot", coverage_report_path.chroot[1:]
1129 )
1130 print(
1131 "Coverage report written to file://%s/index.html"
1132 % coverage_report_host_path
1133 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001134
1135
1136def ExecuteDownloadCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001137 """Executes the "download" command. Wrapper around DownloadFuzzerCorpus."""
1138 DownloadFuzzerCorpus(StripFuzzerPrefixes(options.fuzzer), options.directory)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001139
1140
1141def ExecuteReproduceCommand(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001142 """Executes the "reproduce" command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001143
Alex Klein1699fab2022-09-08 08:46:06 -06001144 Executes the "reproduce" command by Running a fuzzer on a testcase.
1145 May build the fuzzer before running.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001146
Alex Klein1699fab2022-09-08 08:46:06 -06001147 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001148 options: The parsed arguments passed to this program.
Alex Klein1699fab2022-09-08 08:46:06 -06001149 """
1150 if options.build_type and not options.package:
1151 raise Exception(
1152 "Cannot specify --build_type without specifying --package."
1153 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001154
Alex Klein1699fab2022-09-08 08:46:06 -06001155 # Verify that "msan-fuzzer" profile is being used with msan.
1156 # Check presence of "-fsanitize=memory" in CFLAGS.
1157 if options.build_type == BuildType.MSAN:
1158 cmd = ["portageq-%s" % options.board, "envvar", "CFLAGS"]
1159 cflags = cros_build_lib.run(
1160 cmd, capture_output=True, encoding="utf-8"
1161 ).stdout.splitlines()
1162 check_string = "-fsanitize=memory"
1163 if not any(check_string in s for s in cflags):
1164 logging.error(
1165 "-fsanitize=memory not found in CFLAGS. "
1166 'Use "setup_board --board=amd64-generic --profile=msan-fuzzer" '
1167 "for MSan Fuzzing Builds."
1168 )
1169 raise Exception("Incompatible profile used for msan fuzzing.")
Manoj Gupta5ca17652019-05-13 11:15:33 -07001170
Alex Klein1699fab2022-09-08 08:46:06 -06001171 BuildPackage(options.package, options.board, options.build_type)
1172 SetUpSysrootForFuzzing()
1173 Reproduce(StripFuzzerPrefixes(options.fuzzer), options.testcase)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001174
Manoj Guptae5e1e612019-10-21 12:39:57 -07001175
Manoj Guptaec08b812019-10-10 14:21:16 -07001176def InstallBaseDependencies(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001177 """Installs the base packages needed to chroot in board sysroot.
Manoj Guptaec08b812019-10-10 14:21:16 -07001178
Alex Klein1699fab2022-09-08 08:46:06 -06001179 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001180 options: The parsed arguments passed to this program.
Alex Klein1699fab2022-09-08 08:46:06 -06001181 """
1182 package = "virtual/implicit-system"
1183 if not portage_util.IsPackageInstalled(
1184 package, sysroot=SysrootPath.path_to_sysroot
1185 ):
1186 build_type = getattr(options, "build_type", None)
1187 BuildPackage(package, options.board, build_type)
Manoj Guptae5e1e612019-10-21 12:39:57 -07001188
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001189
1190def ParseArgs(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001191 """Parses program arguments.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001192
Alex Klein1699fab2022-09-08 08:46:06 -06001193 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001194 argv: The program arguments we want to parse.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001195
Alex Klein1699fab2022-09-08 08:46:06 -06001196 Returns:
Alex Klein8b444532023-04-11 16:35:24 -06001197 An options object which will tell us which command to run and which
1198 options to use for that command.
Alex Klein1699fab2022-09-08 08:46:06 -06001199 """
1200 parser = commandline.ArgumentParser(description=__doc__)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001201
Alex Klein1699fab2022-09-08 08:46:06 -06001202 parser.add_argument(
1203 "--board",
1204 default=cros_build_lib.GetDefaultBoard(),
1205 help="Board on which to run test.",
1206 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001207
Alex Klein1699fab2022-09-08 08:46:06 -06001208 subparsers = parser.add_subparsers(dest="command")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001209
Alex Klein1699fab2022-09-08 08:46:06 -06001210 subparsers.add_parser("cleanup", help="Undo setup command.")
1211 coverage_parser = subparsers.add_parser(
1212 "coverage", help="Get a coverage report for a fuzzer."
1213 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001214
Alex Klein1699fab2022-09-08 08:46:06 -06001215 coverage_parser.add_argument("--package", help="Package to build.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001216
Alex Klein1699fab2022-09-08 08:46:06 -06001217 corpus_parser = coverage_parser.add_mutually_exclusive_group()
1218 corpus_parser.add_argument("--corpus", help="Corpus to run fuzzer on.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001219
Alex Klein1699fab2022-09-08 08:46:06 -06001220 corpus_parser.add_argument(
1221 "--download",
1222 action="store_true",
1223 help="Generate coverage report based on corpus from ClusterFuzz.",
1224 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001225
Alex Klein1699fab2022-09-08 08:46:06 -06001226 coverage_parser.add_argument(
1227 "--fuzzer",
1228 required=True,
1229 help="The fuzz target to generate a coverage report for.",
1230 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001231
Alex Klein1699fab2022-09-08 08:46:06 -06001232 coverage_parser.add_argument(
1233 "--fuzz-args",
1234 default="",
1235 help="Arguments to pass libFuzzer. "
1236 "Please use an equals sign or parsing will fail "
1237 '(i.e. --fuzzer_args="-rss_limit_mb=2048 -print_funcs=1").',
1238 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001239
Alex Klein1699fab2022-09-08 08:46:06 -06001240 download_parser = subparsers.add_parser(
1241 "download", help="Download a corpus."
1242 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001243
Alex Klein1699fab2022-09-08 08:46:06 -06001244 download_parser.add_argument(
1245 "--directory", help="Path to directory to download the corpus to."
1246 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001247
Alex Klein1699fab2022-09-08 08:46:06 -06001248 download_parser.add_argument(
1249 "--fuzzer", required=True, help="Fuzzer to download the corpus for."
1250 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001251
Alex Klein1699fab2022-09-08 08:46:06 -06001252 reproduce_parser = subparsers.add_parser(
1253 "reproduce", help="Run a fuzzer on a testcase."
1254 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001255
Alex Klein1699fab2022-09-08 08:46:06 -06001256 reproduce_parser.add_argument(
1257 "--testcase", required=True, help="Path of testcase to run fuzzer on."
1258 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001259
Alex Klein1699fab2022-09-08 08:46:06 -06001260 reproduce_parser.add_argument(
1261 "--fuzzer", required=True, help="Fuzzer to reproduce the crash on."
1262 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001263
Alex Klein1699fab2022-09-08 08:46:06 -06001264 reproduce_parser.add_argument("--package", help="Package to build.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001265
Alex Klein1699fab2022-09-08 08:46:06 -06001266 reproduce_parser.add_argument(
1267 "--build-type",
1268 choices=BuildType.CHOICES,
1269 help="Type of build.",
1270 type=str.lower,
1271 ) # Ignore sanitizer case.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001272
Alex Klein1699fab2022-09-08 08:46:06 -06001273 subparsers.add_parser("setup", help="Set up the sysroot to test fuzzing.")
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001274
Alex Klein1699fab2022-09-08 08:46:06 -06001275 subparsers.add_parser(
1276 "shell",
1277 help="Set up sysroot for fuzzing and get a shell in the sysroot.",
1278 )
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001279
Alex Klein1699fab2022-09-08 08:46:06 -06001280 opts = parser.parse_args(argv)
1281 opts.Freeze()
1282 return opts
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001283
1284
1285def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001286 """Parses arguments and executes a command.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001287
Alex Klein1699fab2022-09-08 08:46:06 -06001288 Args:
Alex Klein8b444532023-04-11 16:35:24 -06001289 argv: The program arguments.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001290
Alex Klein1699fab2022-09-08 08:46:06 -06001291 Returns:
Alex Klein8b444532023-04-11 16:35:24 -06001292 0 on success. Non-zero on failure.
Alex Klein1699fab2022-09-08 08:46:06 -06001293 """
1294 cros_build_lib.AssertInsideChroot()
1295 options = ParseArgs(argv)
1296 if options.board is None:
1297 logging.error('Please specify "--board" or set ".default_board".')
1298 return 1
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001299
Alex Klein1699fab2022-09-08 08:46:06 -06001300 SysrootPath.SetPathToSysroot(options.board)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001301
Alex Klein1699fab2022-09-08 08:46:06 -06001302 InstallBaseDependencies(options)
Manoj Guptaec08b812019-10-10 14:21:16 -07001303
Alex Klein1699fab2022-09-08 08:46:06 -06001304 if options.command == "cleanup":
1305 ExecuteCleanupCommand()
1306 elif options.command == "coverage":
1307 ExecuteCoverageCommand(options)
1308 elif options.command == "setup":
1309 ExecuteSetupCommand()
1310 elif options.command == "download":
1311 ExecuteDownloadCommand(options)
1312 elif options.command == "reproduce":
1313 ExecuteReproduceCommand(options)
1314 elif options.command == "shell":
1315 return ExecuteShellCommand()
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001316
Alex Klein1699fab2022-09-08 08:46:06 -06001317 return 0