[ClusterFuzzLite] Fix fuzz target search for coverage (#6799)

Coverage uses a different mechanism for determining if a file
is a fuzz target: It considers any executables in the top level
of /out as fuzz targets.

Fixes #6768
This commit is contained in:
jonathanmetzman 2021-11-09 07:49:21 -05:00 committed by GitHub
parent 482a8e5314
commit fb856de70b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 67 additions and 13 deletions

View File

@ -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

View File

@ -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):

View File

@ -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'):