diff --git a/infra/cifuzz/run_fuzzers.py b/infra/cifuzz/run_fuzzers.py index ec752686e..d42ad23eb 100644 --- a/infra/cifuzz/run_fuzzers.py +++ b/infra/cifuzz/run_fuzzers.py @@ -170,6 +170,38 @@ class PruneTargetRunner(BaseFuzzTargetRunner): fuzz_target_obj.free_disk_if_needed() +NON_FUZZ_TARGETS_FOR_COVERAGE = { + 'llvm-symbolizer', + 'jazzer_agent_deploy.jar', + 'jazzer_driver', + 'jazzer_driver_with_sanitizer', +} + + +def is_coverage_fuzz_target(file_path): + """Returns whether |file_path| is a fuzz target binary for the purposes of a + coverage report. Inspired by infra/base-images/base-runner/coverage.""" + if not os.path.isfile(file_path): + return False + if not utils.is_executable(file_path): + return False + filename = os.path.basename(file_path) + return filename not in NON_FUZZ_TARGETS_FOR_COVERAGE + + +def get_coverage_fuzz_targets(out): + """Returns a list of fuzz targets in |out| for coverage.""" + # We only want fuzz targets from the root because during the coverage build, + # a lot of the image's filesystem is copied into /out for the purpose of + # generating coverage reports. + fuzz_targets = [] + for filename in os.listdir(out): + file_path = os.path.join(out, filename) + if is_coverage_fuzz_target(file_path): + fuzz_targets.append(file_path) + return fuzz_targets + + class CoverageTargetRunner(BaseFuzzTargetRunner): """Runner that runs the 'coverage' command.""" @@ -179,12 +211,7 @@ class CoverageTargetRunner(BaseFuzzTargetRunner): def get_fuzz_targets(self): """Returns fuzz targets in out directory.""" - # We only want fuzz targets from the root because during the coverage build, - # a lot of the image's filesystem is copied into /out for the purpose of - # generating coverage reports. - # TOOD(metzman): Figure out if top_level_only should be the only behavior - # for this function. - return utils.get_fuzz_targets(self.workspace.out, top_level_only=True) + return get_coverage_fuzz_targets(self.workspace.out) def run_fuzz_targets(self): """Generates a coverage report. Always returns False since it never finds diff --git a/infra/cifuzz/run_fuzzers_test.py b/infra/cifuzz/run_fuzzers_test.py index 873413850..3dc5de71f 100644 --- a/infra/cifuzz/run_fuzzers_test.py +++ b/infra/cifuzz/run_fuzzers_test.py @@ -14,8 +14,9 @@ """Tests for running fuzzers.""" import json import os -import sys import shutil +import stat +import sys import tempfile import unittest from unittest import mock @@ -303,6 +304,31 @@ class BatchFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase): self.assertEqual(mock_upload_crashes.call_count, 1) +class GetCoverageTargetsTest(unittest.TestCase): + """Tests for get_coverage_fuzz_targets.""" + + def test_get_fuzz_targets(self): + """Tests that get_coverage_fuzz_targets returns expected targets.""" + with tempfile.TemporaryDirectory() as temp_dir: + # Setup. + fuzz_target_path = os.path.join(temp_dir, 'fuzz-target') + with open(fuzz_target_path, 'w') as file_handle: + file_handle.write('') + fuzz_target_st = os.stat(fuzz_target_path) + os.chmod(fuzz_target_path, fuzz_target_st.st_mode | stat.S_IEXEC) + non_fuzz_target1 = os.path.join(temp_dir, 'non-fuzz-target1') + with open(non_fuzz_target1, 'w') as file_handle: + file_handle.write('LLVMFuzzerTestOneInput') + subdir = os.path.join(temp_dir, 'subdir') + os.mkdir(subdir) + non_fuzz_target2 = os.path.join(subdir, 'non-fuzz-target1') + with open(non_fuzz_target2, 'w') as file_handle: + file_handle.write('LLVMFuzzerTestOneInput') + + self.assertEqual(run_fuzzers.get_coverage_fuzz_targets(temp_dir), + [fuzz_target_path]) + + @unittest.skipIf(not os.getenv('INTEGRATION_TESTS'), 'INTEGRATION_TESTS=1 not set') class CoverageReportIntegrationTest(unittest.TestCase): diff --git a/infra/utils.py b/infra/utils.py index 3d1f078a6..661a773e7 100644 --- a/infra/utils.py +++ b/infra/utils.py @@ -94,12 +94,11 @@ def execute(command, return out, err, process.returncode -def get_fuzz_targets(path, top_level_only=False): +def get_fuzz_targets(path): """Gets fuzz targets in a directory. Args: path: A path to search for fuzz targets in. - top_level_only: If True, only search |path|, do not recurse into subdirs. Returns: A list of paths to fuzzers or an empty list if None. @@ -108,9 +107,6 @@ def get_fuzz_targets(path, top_level_only=False): return [] fuzz_target_paths = [] for root, _, fuzzers in os.walk(path): - if top_level_only and path != root: - continue - for fuzzer in fuzzers: file_path = os.path.join(root, fuzzer) if is_fuzz_target_local(file_path): @@ -134,6 +130,11 @@ def get_container_name(): return file_handle.read().strip() +def is_executable(file_path): + """Returns True if |file_path| is an exectuable.""" + return os.path.exists(file_path) and os.access(file_path, os.X_OK) + + def is_fuzz_target_local(file_path): """Returns whether |file_path| is a fuzz target binary (local path). Copied from clusterfuzz src/python/bot/fuzzers/utils.py @@ -154,7 +155,7 @@ def is_fuzz_target_local(file_path): # Ignore files with disallowed extensions (to prevent opening e.g. .zips). return False - if not os.path.exists(file_path) or not os.access(file_path, os.X_OK): + if not is_executable(file_path): return False if filename.endswith('_fuzzer'):