diff options
Diffstat (limited to 'infra/cifuzz')
-rw-r--r-- | infra/cifuzz/actions/build_fuzzers/action.yml | 11 | ||||
-rw-r--r-- | infra/cifuzz/actions/run_fuzzers/action.yml | 6 | ||||
-rw-r--r-- | infra/cifuzz/affected_fuzz_targets_test.py | 8 | ||||
-rw-r--r-- | infra/cifuzz/build_fuzzers.py | 24 | ||||
-rw-r--r-- | infra/cifuzz/build_fuzzers_entrypoint.py | 10 | ||||
-rw-r--r-- | infra/cifuzz/build_fuzzers_test.py | 27 | ||||
-rw-r--r-- | infra/cifuzz/cifuzz-base/Dockerfile | 24 | ||||
-rw-r--r-- | infra/cifuzz/config_utils.py | 32 | ||||
-rw-r--r-- | infra/cifuzz/config_utils_test.py | 29 | ||||
-rw-r--r-- | infra/cifuzz/coverage.py | 2 | ||||
-rw-r--r-- | infra/cifuzz/coverage_test.py | 52 | ||||
-rw-r--r-- | infra/cifuzz/docker.py | 38 | ||||
-rw-r--r-- | infra/cifuzz/environment.py | 54 | ||||
-rw-r--r-- | infra/cifuzz/fuzz_target.py | 46 | ||||
-rw-r--r-- | infra/cifuzz/fuzz_target_test.py | 3 | ||||
-rw-r--r-- | infra/cifuzz/run_fuzzers.py | 20 | ||||
-rw-r--r-- | infra/cifuzz/run_fuzzers_entrypoint.py | 19 | ||||
-rw-r--r-- | infra/cifuzz/run_fuzzers_test.py | 32 | ||||
-rw-r--r-- | infra/cifuzz/stack_parser.py | 22 | ||||
-rw-r--r-- | infra/cifuzz/stack_parser_test.py | 33 | ||||
-rw-r--r-- | infra/cifuzz/test_data/example_crash_fuzzer_bug_summary.txt (renamed from infra/cifuzz/test_files/bug_summary_example.txt) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/example_crash_fuzzer_output.txt (renamed from infra/cifuzz/test_files/example_crash_fuzzer_output.txt) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/example_curl_cov.json (renamed from infra/cifuzz/test_files/example_curl_cov.json) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/example_curl_file_list.json (renamed from infra/cifuzz/test_files/example_curl_file_list.json) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/example_curl_fuzzer_cov.json (renamed from infra/cifuzz/test_files/example_curl_fuzzer_cov.json) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/external-project/Makefile (renamed from infra/cifuzz/test_files/external-project/Makefile) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/external-project/do_stuff_fuzzer.cpp (renamed from infra/cifuzz/test_files/external-project/do_stuff_fuzzer.cpp) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/external-project/do_stuff_fuzzer.dict (renamed from infra/cifuzz/test_files/external-project/do_stuff_fuzzer.dict) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/external-project/my_api.cpp (renamed from infra/cifuzz/test_files/external-project/my_api.cpp) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/external-project/my_api.h (renamed from infra/cifuzz/test_files/external-project/my_api.h) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/external-project/oss-fuzz/Dockerfile (renamed from infra/cifuzz/test_files/external-project/oss-fuzz/Dockerfile) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/external-project/oss-fuzz/build.sh (renamed from infra/cifuzz/test_files/external-project/oss-fuzz/build.sh) | 0 | ||||
-rw-r--r-- | infra/cifuzz/test_data/external-project/standalone_fuzz_target_runner.cpp (renamed from infra/cifuzz/test_files/external-project/standalone_fuzz_target_runner.cpp) | 0 | ||||
-rwxr-xr-x | infra/cifuzz/test_data/memory/out/curl_fuzzer_memory (renamed from infra/cifuzz/test_files/memory/out/curl_fuzzer_memory) | bin | 9768680 -> 9768680 bytes | |||
-rw-r--r-- | infra/cifuzz/test_data/msan_crash_fuzzer_bug_summary.txt | 22 | ||||
-rw-r--r-- | infra/cifuzz/test_data/msan_crash_fuzzer_output.txt | 39 | ||||
-rwxr-xr-x | infra/cifuzz/test_data/out/example_crash_fuzzer (renamed from infra/cifuzz/test_files/out/example_crash_fuzzer) | bin | 4375872 -> 4375872 bytes | |||
-rwxr-xr-x | infra/cifuzz/test_data/out/example_nocrash_fuzzer (renamed from infra/cifuzz/test_files/out/example_nocrash_fuzzer) | bin | 4376224 -> 4376224 bytes | |||
-rwxr-xr-x | infra/cifuzz/test_data/undefined/out/curl_fuzzer_undefined (renamed from infra/cifuzz/test_files/undefined/out/curl_fuzzer_undefined) | bin | 14401312 -> 14401312 bytes |
39 files changed, 444 insertions, 109 deletions
diff --git a/infra/cifuzz/actions/build_fuzzers/action.yml b/infra/cifuzz/actions/build_fuzzers/action.yml index 2919db40e..835b7b430 100644 --- a/infra/cifuzz/actions/build_fuzzers/action.yml +++ b/infra/cifuzz/actions/build_fuzzers/action.yml @@ -5,6 +5,10 @@ inputs: oss-fuzz-project-name: description: 'Name of the corresponding OSS-Fuzz project.' required: true + language: + description: 'Programming language project is written in.' + required: false + default: 'c++' dry-run: description: 'If set, run the action without actually reporting a failure.' default: false @@ -20,13 +24,20 @@ inputs: build-integration-path: description: "The path to the the project's build integration." required: false + bad-build-check: + description: "Whether or not OSS-Fuzz's check for bad builds should be done." + required: false + default: true runs: using: 'docker' image: '../../../build_fuzzers.Dockerfile' env: OSS_FUZZ_PROJECT_NAME: ${{ inputs.oss-fuzz-project-name }} + LANGUAGE: ${{ inputs.language }} DRY_RUN: ${{ inputs.dry-run}} ALLOWED_BROKEN_TARGETS_PERCENTAGE: ${{ inputs.allowed-broken-targets-percentage}} SANITIZER: ${{ inputs.sanitizer }} PROJECT_SRC_PATH: ${{ inputs.project-src-path }} BUILD_INTEGRATION_PATH: ${{ inputs.build-integration-path }} + LOW_DISK_SPACE: 'True' + BAD_BUILD_CHECK: ${{ inputs.bad-build-check }} diff --git a/infra/cifuzz/actions/run_fuzzers/action.yml b/infra/cifuzz/actions/run_fuzzers/action.yml index 582133c74..d1c03c833 100644 --- a/infra/cifuzz/actions/run_fuzzers/action.yml +++ b/infra/cifuzz/actions/run_fuzzers/action.yml @@ -5,6 +5,10 @@ inputs: oss-fuzz-project-name: description: 'The OSS-Fuzz project name.' required: true + language: + description: 'Programming language project is written in.' + required: false + default: 'c++' fuzz-seconds: description: 'The total time allotted for fuzzing in seconds.' required: true @@ -31,6 +35,7 @@ runs: image: '../../../run_fuzzers.Dockerfile' env: OSS_FUZZ_PROJECT_NAME: ${{ inputs.oss-fuzz-project-name }} + LANGUAGE: ${{ inputs.language }} FUZZ_SECONDS: ${{ inputs.fuzz-seconds }} DRY_RUN: ${{ inputs.dry-run}} SANITIZER: ${{ inputs.sanitizer }} @@ -39,3 +44,4 @@ runs: # for running because we use it to distinguish OSS-Fuzz from non-OSS-Fuzz. # We should do something explicit instead. BUILD_INTEGRATION_PATH: ${{ inputs.build-integration-path }} + LOW_DISK_SPACE: 'True' diff --git a/infra/cifuzz/affected_fuzz_targets_test.py b/infra/cifuzz/affected_fuzz_targets_test.py index 72e6d266c..05f27c072 100644 --- a/infra/cifuzz/affected_fuzz_targets_test.py +++ b/infra/cifuzz/affected_fuzz_targets_test.py @@ -30,15 +30,15 @@ EXAMPLE_PROJECT = 'example' EXAMPLE_FILE_CHANGED = 'test.txt' -TEST_FILES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'test_files') +TEST_DATA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'test_data') class RemoveUnaffectedFuzzTargets(unittest.TestCase): """Tests remove_unaffected_fuzzers.""" - TEST_FUZZER_1 = os.path.join(TEST_FILES_PATH, 'out', 'example_crash_fuzzer') - TEST_FUZZER_2 = os.path.join(TEST_FILES_PATH, 'out', 'example_nocrash_fuzzer') + TEST_FUZZER_1 = os.path.join(TEST_DATA_PATH, 'out', 'example_crash_fuzzer') + TEST_FUZZER_2 = os.path.join(TEST_DATA_PATH, 'out', 'example_nocrash_fuzzer') # yapf: disable @parameterized.parameterized.expand([ diff --git a/infra/cifuzz/build_fuzzers.py b/infra/cifuzz/build_fuzzers.py index a4342a413..78180b52b 100644 --- a/infra/cifuzz/build_fuzzers.py +++ b/infra/cifuzz/build_fuzzers.py @@ -20,6 +20,7 @@ import sys import affected_fuzz_targets import continuous_integration +import docker # pylint: disable=wrong-import-position,import-error sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -77,7 +78,8 @@ class Builder: # pylint: disable=too-many-instance-attributes def build_fuzzers(self): """Moves the source code we want to fuzz into the project builder and builds the fuzzers from that source code. Returns True on success.""" - docker_args = get_common_docker_args(self.config.sanitizer) + docker_args = get_common_docker_args(self.config.sanitizer, + self.config.language) container = utils.get_container_name() if container: @@ -93,7 +95,7 @@ class Builder: # pylint: disable=too-many-instance-attributes self.handle_msan_prebuild(container) docker_args.extend([ - 'gcr.io/oss-fuzz/' + self.config.project_name, + docker.get_project_image_name(self.config.project_name), '/bin/bash', '-c', ]) @@ -118,8 +120,7 @@ class Builder: # pylint: disable=too-many-instance-attributes helper.docker_run([ '--volumes-from', container, '-e', 'WORK={work_dir}'.format(work_dir=self.work_dir), - 'gcr.io/oss-fuzz-base/base-sanitizer-libs-builder', 'patch_build.py', - '/out' + docker.MSAN_LIBS_BUILDER_TAG, 'patch_build.py', '/out' ]) def handle_msan_prebuild(self, container): @@ -127,8 +128,8 @@ class Builder: # pylint: disable=too-many-instance-attributes returns docker arguments to use that directory for MSAN libs.""" logging.info('Copying MSAN libs.') helper.docker_run([ - '--volumes-from', container, 'gcr.io/oss-fuzz-base/msan-libs-builder', - 'bash', '-c', 'cp -r /msan {work_dir}'.format(work_dir=self.work_dir) + '--volumes-from', container, docker.MSAN_LIBS_BUILDER_TAG, 'bash', '-c', + 'cp -r /msan {work_dir}'.format(work_dir=self.work_dir) ]) def build(self): @@ -185,7 +186,7 @@ def build_fuzzers(config): return builder.build() -def get_common_docker_args(sanitizer): +def get_common_docker_args(sanitizer, language): """Returns a list of common docker arguments.""" return [ '--cap-add', @@ -199,12 +200,13 @@ def get_common_docker_args(sanitizer): '-e', 'CIFUZZ=True', '-e', - 'FUZZING_LANGUAGE=c++', # FIXME: Add proper support. + 'FUZZING_LANGUAGE=' + language, ] def check_fuzzer_build(out_dir, - sanitizer='address', + sanitizer, + language, allowed_broken_targets_percentage=None): """Checks the integrity of the built fuzzers. @@ -222,7 +224,7 @@ def check_fuzzer_build(out_dir, logging.error('No fuzzers found in out directory: %s.', out_dir) return False - command = get_common_docker_args(sanitizer) + command = get_common_docker_args(sanitizer, language) if allowed_broken_targets_percentage is not None: command += [ @@ -236,7 +238,7 @@ def check_fuzzer_build(out_dir, command += ['-e', 'OUT=' + out_dir, '--volumes-from', container] else: command += ['-v', '%s:/out' % out_dir] - command.extend(['-t', 'gcr.io/oss-fuzz-base/base-runner', 'test_all.py']) + command.extend(['-t', docker.BASE_RUNNER_TAG, 'test_all.py']) exit_code = helper.docker_run(command) logging.info('check fuzzer build exit code: %d', exit_code) if exit_code: diff --git a/infra/cifuzz/build_fuzzers_entrypoint.py b/infra/cifuzz/build_fuzzers_entrypoint.py index 9c4b98215..04f562068 100644 --- a/infra/cifuzz/build_fuzzers_entrypoint.py +++ b/infra/cifuzz/build_fuzzers_entrypoint.py @@ -72,10 +72,16 @@ def main(): return returncode out_dir = os.path.join(config.workspace, 'out') + + if not config.bad_build_check: + # If we've gotten to this point and we don't need to do bad_build_check, + # then the build has succeeded. + returncode = 0 # yapf: disable - if build_fuzzers.check_fuzzer_build( + elif build_fuzzers.check_fuzzer_build( out_dir, - sanitizer=config.sanitizer, + config.sanitizer, + config.language, allowed_broken_targets_percentage=config.allowed_broken_targets_percentage ): # yapf: enable diff --git a/infra/cifuzz/build_fuzzers_test.py b/infra/cifuzz/build_fuzzers_test.py index 2d27356d2..298778867 100644 --- a/infra/cifuzz/build_fuzzers_test.py +++ b/infra/cifuzz/build_fuzzers_test.py @@ -36,9 +36,9 @@ import test_helpers # https://github.com/google/oss-fuzz/tree/master/projects/example project. EXAMPLE_PROJECT = 'example' -# Location of files used for testing. -TEST_FILES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'test_files') +# Location of data used for testing. +TEST_DATA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'test_data') # An example fuzzer that triggers an crash. # Binary is a copy of the example project's do_stuff_fuzzer and can be @@ -251,10 +251,13 @@ class BuildFuzzersIntegrationTest(unittest.TestCase): class CheckFuzzerBuildTest(unittest.TestCase): """Tests the check_fuzzer_build function in the cifuzz module.""" + SANITIZER = 'address' + LANGUAGE = 'c++' + def setUp(self): self.tmp_dir_obj = tempfile.TemporaryDirectory() self.test_files_path = os.path.join(self.tmp_dir_obj.name, 'test_files') - shutil.copytree(TEST_FILES_PATH, self.test_files_path) + shutil.copytree(TEST_DATA_PATH, self.test_files_path) def tearDown(self): self.tmp_dir_obj.cleanup() @@ -262,23 +265,31 @@ class CheckFuzzerBuildTest(unittest.TestCase): def test_correct_fuzzer_build(self): """Checks check_fuzzer_build function returns True for valid fuzzers.""" test_fuzzer_dir = os.path.join(self.test_files_path, 'out') - self.assertTrue(build_fuzzers.check_fuzzer_build(test_fuzzer_dir)) + self.assertTrue( + build_fuzzers.check_fuzzer_build(test_fuzzer_dir, self.SANITIZER, + self.LANGUAGE)) def test_not_a_valid_fuzz_path(self): """Tests that False is returned when a bad path is given.""" - self.assertFalse(build_fuzzers.check_fuzzer_build('not/a/valid/path')) + self.assertFalse( + build_fuzzers.check_fuzzer_build('not/a/valid/path', self.SANITIZER, + self.LANGUAGE)) def test_not_a_valid_fuzzer(self): """Checks a directory that exists but does not have fuzzers is False.""" - self.assertFalse(build_fuzzers.check_fuzzer_build(self.test_files_path)) + self.assertFalse( + build_fuzzers.check_fuzzer_build(self.test_files_path, self.SANITIZER, + self.LANGUAGE)) @mock.patch('helper.docker_run') def test_allow_broken_fuzz_targets_percentage(self, mocked_docker_run): """Tests that ALLOWED_BROKEN_TARGETS_PERCENTAGE is set when running docker if passed to check_fuzzer_build.""" mocked_docker_run.return_value = 0 - test_fuzzer_dir = os.path.join(TEST_FILES_PATH, 'out') + test_fuzzer_dir = os.path.join(TEST_DATA_PATH, 'out') build_fuzzers.check_fuzzer_build(test_fuzzer_dir, + self.SANITIZER, + self.LANGUAGE, allowed_broken_targets_percentage='0') self.assertIn('-e ALLOWED_BROKEN_TARGETS_PERCENTAGE=0', ' '.join(mocked_docker_run.call_args[0][0])) diff --git a/infra/cifuzz/cifuzz-base/Dockerfile b/infra/cifuzz/cifuzz-base/Dockerfile index 0aee3b2cf..e0599dbbe 100644 --- a/infra/cifuzz/cifuzz-base/Dockerfile +++ b/infra/cifuzz/cifuzz-base/Dockerfile @@ -14,25 +14,19 @@ # ################################################################################ +# Don't bother with a slimmer base image. +# When we pull base-builder to build project builder image we need to pull +# ubuntu:16.04 anyway. So in the long run we probably would waste time if +# we pulled something like alpine here instead. FROM ubuntu:16.04 -RUN apt-get update && apt-get install -y git \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg2 \ - software-properties-common \ - python3 +RUN apt-get update && \ + apt-get install ca-certificates wget python3 git-core --no-install-recommends -y && \ + wget https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/docker-ce-cli_20.10.5~3-0~ubuntu-xenial_amd64.deb -O /tmp/docker-ce.deb && \ + dpkg -i /tmp/docker-ce.deb && rm /tmp/docker-ce.deb && \ + apt-get remove wget -y --purge -RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && apt-key fingerprint 0EBFCD88 -RUN add-apt-repository \ - "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ - xenial \ - stable" - -RUN apt-get update && apt-get install docker-ce docker-ce-cli containerd.io -y - ENV OSS_FUZZ_ROOT=/opt/oss-fuzz ADD . ${OSS_FUZZ_ROOT} RUN rm -rf ${OSS_FUZZ_ROOT}/infra
\ No newline at end of file diff --git a/infra/cifuzz/config_utils.py b/infra/cifuzz/config_utils.py index fd1871497..ad2cd36c6 100644 --- a/infra/cifuzz/config_utils.py +++ b/infra/cifuzz/config_utils.py @@ -18,14 +18,16 @@ import enum import os import json +import environment + def _get_project_repo_name(): - return os.path.basename(os.getenv('GITHUB_REPOSITORY', '')) + return os.path.basename(environment.get('GITHUB_REPOSITORY', '')) def _get_pr_ref(event): if event == 'pull_request': - return os.getenv('GITHUB_REF') + return environment.get('GITHUB_REF') return None @@ -40,7 +42,7 @@ def _get_project_name(): def _is_dry_run(): """Returns True if configured to do a dry run.""" - return os.getenv('DRY_RUN', 'false').lower() == 'true' + return environment.get_bool('DRY_RUN', 'false') def get_project_src_path(workspace): @@ -62,6 +64,19 @@ def get_project_src_path(workspace): return os.path.join(workspace, path) +DEFAULT_LANGUAGE = 'c++' + + +def _get_language(): + """Returns the project language.""" + # Get language from environment. We took this approach because the convenience + # given to OSS-Fuzz users by not making them specify the language again (and + # getting it from the project.yaml) is outweighed by the complexity in + # implementing this. A lot of the complexity comes from our unittests not + # setting a proper projet at this point. + return os.getenv('LANGUAGE', DEFAULT_LANGUAGE) + + # pylint: disable=too-few-public-methods,too-many-instance-attributes @@ -81,14 +96,22 @@ class BaseConfig: self.dry_run = _is_dry_run() self.sanitizer = _get_sanitizer() self.build_integration_path = os.getenv('BUILD_INTEGRATION_PATH') + self.language = _get_language() event_path = os.getenv('GITHUB_EVENT_PATH') self.is_github = bool(event_path) logging.debug('Is github: %s.', self.is_github) + # TODO(metzman): Parse env like we do in ClusterFuzz. + self.low_disk_space = environment.get('LOW_DISK_SPACE', False) + + @property + def is_internal(self): + """Returns True if this is an OSS-Fuzz project.""" + return not self.build_integration_path @property def platform(self): """Returns the platform CIFuzz is runnning on.""" - if self.build_integration_path: + if not self.is_internal: return self.Platform.EXTERNAL_GITHUB if self.is_github: return self.Platform.INTERNAL_GITHUB @@ -149,6 +172,7 @@ class BuildFuzzersConfig(BaseConfig): self.allowed_broken_targets_percentage = os.getenv( 'ALLOWED_BROKEN_TARGETS_PERCENTAGE') + self.bad_build_check = environment.get_bool('BAD_BUILD_CHECK', 'true') # TODO(metzman): Use better system for interpreting env vars. What if env # var is set to '0'? diff --git a/infra/cifuzz/config_utils_test.py b/infra/cifuzz/config_utils_test.py index 71e7450fa..6f87bd4c5 100644 --- a/infra/cifuzz/config_utils_test.py +++ b/infra/cifuzz/config_utils_test.py @@ -13,19 +13,38 @@ # limitations under the License. """Module for getting the configuration CIFuzz needs to run.""" import os -import sys import unittest import config_utils - -# pylint: disable=wrong-import-position,import-error -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - import test_helpers # pylint: disable=no-self-use +class BaseConfigTest(unittest.TestCase): + """Tests for BaseConfig.""" + + def setUp(self): + test_helpers.patch_environ(self) + + def _create_config(self): + return config_utils.BuildFuzzersConfig() + + def test_language_default(self): + """Tests that the correct default language is set.""" + os.environ['BUILD_INTEGRATION_PATH'] = '/path' + config = self._create_config() + self.assertEqual(config.language, 'c++') + + def test_language(self): + """Tests that the correct language is set.""" + os.environ['BUILD_INTEGRATION_PATH'] = '/path' + language = 'python' + os.environ['LANGUAGE'] = language + config = self._create_config() + self.assertEqual(config.language, language) + + class BuildFuzzersConfigTest(unittest.TestCase): """Tests for BuildFuzzersConfig.""" diff --git a/infra/cifuzz/coverage.py b/infra/cifuzz/coverage.py index b5c6fbf1a..9a179c59d 100644 --- a/infra/cifuzz/coverage.py +++ b/infra/cifuzz/coverage.py @@ -115,7 +115,7 @@ def _get_latest_cov_report_info(project_name): LATEST_REPORT_INFO_PATH, project_name + '.json') latest_cov_info = get_json_from_url(latest_report_info_url) - if not latest_cov_info is None: + if latest_cov_info is None: logging.error('Could not get the coverage report json from url: %s.', latest_report_info_url) return None diff --git a/infra/cifuzz/coverage_test.py b/infra/cifuzz/coverage_test.py index 57120f5f5..1b24d798c 100644 --- a/infra/cifuzz/coverage_test.py +++ b/infra/cifuzz/coverage_test.py @@ -21,8 +21,8 @@ import coverage # pylint: disable=protected-access -TEST_FILES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'test_files') +TEST_DATA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'test_data') PROJECT_NAME = 'curl' REPO_PATH = '/src/curl' @@ -31,7 +31,7 @@ PROJECT_COV_JSON_FILENAME = 'example_curl_cov.json' FUZZ_TARGET_COV_JSON_FILENAME = 'example_curl_fuzzer_cov.json' INVALID_TARGET = 'not-a-fuzz-target' -with open(os.path.join(TEST_FILES_PATH, +with open(os.path.join(TEST_DATA_PATH, PROJECT_COV_JSON_FILENAME),) as cov_file_handle: PROJECT_COV_INFO = json.loads(cov_file_handle.read()) @@ -39,19 +39,28 @@ with open(os.path.join(TEST_FILES_PATH, class GetFuzzerStatsDirUrlTest(unittest.TestCase): """Tests _get_fuzzer_stats_dir_url.""" - @mock.patch('coverage.get_json_from_url', return_value={}) + @mock.patch('coverage.get_json_from_url', + return_value={ + 'fuzzer_stats_dir': + 'gs://oss-fuzz-coverage/systemd/fuzzer_stats/20210303' + }) def test_get_valid_project(self, mocked_get_json_from_url): """Tests that a project's coverage report can be downloaded and parsed. NOTE: This test relies on the PROJECT_NAME repo's coverage report. The "example" project was not used because it has no coverage reports. """ - coverage._get_fuzzer_stats_dir_url(PROJECT_NAME) + result = coverage._get_fuzzer_stats_dir_url(PROJECT_NAME) (url,), _ = mocked_get_json_from_url.call_args self.assertEqual( 'https://storage.googleapis.com/oss-fuzz-coverage/' 'latest_report_info/curl.json', url) + expected_result = ( + 'https://storage.googleapis.com/oss-fuzz-coverage/systemd/fuzzer_stats/' + '20210303') + self.assertEqual(result, expected_result) + def test_get_invalid_project(self): """Tests that passing a bad project returns None.""" self.assertIsNone(coverage._get_fuzzer_stats_dir_url('not-a-proj')) @@ -98,7 +107,7 @@ class GetFilesCoveredByTargetTest(unittest.TestCase): def test_valid_target(self): """Tests that covered files can be retrieved from a coverage report.""" - with open(os.path.join(TEST_FILES_PATH, + with open(os.path.join(TEST_DATA_PATH, FUZZ_TARGET_COV_JSON_FILENAME),) as file_handle: fuzzer_cov_info = json.loads(file_handle.read()) @@ -106,7 +115,7 @@ class GetFilesCoveredByTargetTest(unittest.TestCase): return_value=fuzzer_cov_info): file_list = self.coverage_getter.get_files_covered_by_target(FUZZ_TARGET) - curl_files_list_path = os.path.join(TEST_FILES_PATH, + curl_files_list_path = os.path.join(TEST_DATA_PATH, 'example_curl_file_list.json') with open(curl_files_list_path) as file_handle: expected_file_list = json.loads(file_handle.read()) @@ -152,5 +161,34 @@ class IsFileCoveredTest(unittest.TestCase): self.assertFalse(coverage.is_file_covered(file_coverage)) +class GetLatestCovReportInfo(unittest.TestCase): + """Tests that _get_latest_cov_report_info works as intended.""" + + PROJECT = 'project' + LATEST_REPORT_INFO_URL = ('https://storage.googleapis.com/oss-fuzz-coverage/' + 'latest_report_info/project.json') + + @mock.patch('logging.error') + @mock.patch('coverage.get_json_from_url', return_value={'coverage': 1}) + def test_get_latest_cov_report_info(self, mocked_get_json_from_url, + mocked_error): + """Tests that _get_latest_cov_report_info works as intended.""" + result = coverage._get_latest_cov_report_info(self.PROJECT) + self.assertEqual(result, {'coverage': 1}) + mocked_error.assert_not_called() + mocked_get_json_from_url.assert_called_with(self.LATEST_REPORT_INFO_URL) + + @mock.patch('logging.error') + @mock.patch('coverage.get_json_from_url', return_value=None) + def test_get_latest_cov_report_info_fail(self, _, mocked_error): + """Tests that _get_latest_cov_report_info works as intended when we can't + get latest report info.""" + result = coverage._get_latest_cov_report_info('project') + self.assertIsNone(result) + mocked_error.assert_called_with( + 'Could not get the coverage report json from url: %s.', + self.LATEST_REPORT_INFO_URL) + + if __name__ == '__main__': unittest.main() diff --git a/infra/cifuzz/docker.py b/infra/cifuzz/docker.py new file mode 100644 index 000000000..eb993e28d --- /dev/null +++ b/infra/cifuzz/docker.py @@ -0,0 +1,38 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Module for dealing with docker.""" +import os +import sys + +# pylint: disable=wrong-import-position,import-error +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import utils + +BASE_BUILDER_TAG = 'gcr.io/oss-fuzz-base/base-builder' +BASE_RUNNER_TAG = 'gcr.io/oss-fuzz-base/base-runner' +MSAN_LIBS_BUILDER_TAG = 'gcr.io/oss-fuzz-base/msan-libs-builder' +PROJECT_TAG_PREFIX = 'gcr.io/oss-fuzz/' + + +def get_project_image_name(project): + """Returns the name of the project builder image for |project_name|.""" + return PROJECT_TAG_PREFIX + project + + +def delete_images(images): + """Deletes |images|.""" + command = ['docker', 'rmi', '-f'] + images + utils.execute(command) + utils.execute(['docker', 'builder', 'prune', '-f']) diff --git a/infra/cifuzz/environment.py b/infra/cifuzz/environment.py new file mode 100644 index 000000000..4cc0f846b --- /dev/null +++ b/infra/cifuzz/environment.py @@ -0,0 +1,54 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Module for dealing with env vars.""" + +import ast +import os + + +def _eval_value(value_string): + """Returns evaluated value.""" + try: + return ast.literal_eval(value_string) + except: # pylint: disable=bare-except + # String fallback. + return value_string + + +def get(env_var, default_value=None): + """Returns an environment variable value.""" + value_string = os.getenv(env_var) + if value_string is None: + return default_value + + return _eval_value(value_string) + + +def get_bool(env_var, default_value=None): + """Returns a boolean environment variable value. This is needed because a lot + of CIFuzz users specified 'false' for dry-run. So we need to special case + this.""" + value = get(env_var, default_value) + if not isinstance(value, str): + return bool(value) + + lower_value = value.lower() + allowed_values = {'true', 'false'} + if lower_value not in allowed_values: + raise Exception(('Bool env var {env_var} value {value} is invalid. ' + 'Must be one of {allowed_values}').format( + env_var=env_var, + value=value, + allowed_values=allowed_values)) + return lower_value == 'true' diff --git a/infra/cifuzz/fuzz_target.py b/infra/cifuzz/fuzz_target.py index 7bccfa4e1..c623bf60d 100644 --- a/infra/cifuzz/fuzz_target.py +++ b/infra/cifuzz/fuzz_target.py @@ -16,10 +16,13 @@ import collections import logging import os import re +import shutil import stat import subprocess import sys +import docker + # pylint: disable=wrong-import-position,import-error sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import utils @@ -28,6 +31,8 @@ logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG) +# Use a fixed seed for determinism. Use len_control=0 since we don't have enough +# time fuzzing for len_control to make sense (probably). LIBFUZZER_OPTIONS = '-seed=1337 -len_control=0' # The number of reproduce attempts for a crash. @@ -78,6 +83,7 @@ class FuzzTarget: self.out_dir = out_dir self.clusterfuzz_deployment = clusterfuzz_deployment self.config = config + self.latest_corpus_path = None def fuzz(self): """Starts the fuzz target run for the length of time specified by duration. @@ -98,8 +104,7 @@ class FuzzTarget: command += [ '-e', 'FUZZING_ENGINE=libfuzzer', '-e', 'SANITIZER=' + self.config.sanitizer, '-e', 'CIFUZZ=True', '-e', - 'RUN_FUZZER_MODE=interactive', 'gcr.io/oss-fuzz-base/base-runner', - 'bash', '-c' + 'RUN_FUZZER_MODE=interactive', docker.BASE_RUNNER_TAG, 'bash', '-c' ] run_fuzzer_command = 'run_fuzzer {fuzz_target} {options}'.format( @@ -107,10 +112,10 @@ class FuzzTarget: options=LIBFUZZER_OPTIONS + ' -max_total_time=' + str(self.duration)) # If corpus can be downloaded use it for fuzzing. - latest_corpus_path = self.clusterfuzz_deployment.download_corpus( + self.latest_corpus_path = self.clusterfuzz_deployment.download_corpus( self.target_name, self.out_dir) - if latest_corpus_path: - run_fuzzer_command = run_fuzzer_command + ' ' + latest_corpus_path + if self.latest_corpus_path: + run_fuzzer_command = run_fuzzer_command + ' ' + self.latest_corpus_path command.append(run_fuzzer_command) logging.info('Running command: %s', ' '.join(command)) @@ -136,10 +141,37 @@ class FuzzTarget: if not testcase: logging.error(b'No testcase found in stacktrace: %s.', stderr) return FuzzResult(None, None) + + utils.binary_print(b'Fuzzer: %s. Detected bug:\n%s' % + (self.target_name.encode(), stderr)) if self.is_crash_reportable(testcase): + # We found a bug in the fuzz target and we will report it. return FuzzResult(testcase, stderr) + + # We found a bug but we won't report it. return FuzzResult(None, None) + def free_disk_if_needed(self): + """Deletes things that are no longer needed from fuzzing this fuzz target to + save disk space if needed.""" + if not self.config.low_disk_space: + return + logging.info( + 'Deleting corpus, seed corpus and fuzz target of %s to save disk.', + self.target_name) + + # Delete the seed corpus, corpus, and fuzz target. + if self.latest_corpus_path and os.path.exists(self.latest_corpus_path): + # Use ignore_errors=True to fix + # https://github.com/google/oss-fuzz/issues/5383. + shutil.rmtree(self.latest_corpus_path, ignore_errors=True) + + os.remove(self.target_path) + target_seed_corpus_path = self.target_path + '_seed_corpus.zip' + if os.path.exists(target_seed_corpus_path): + os.remove(target_seed_corpus_path) + logging.info('Done deleting.') + def is_reproducible(self, testcase, target_path): """Checks if the testcase reproduces. @@ -176,8 +208,7 @@ class FuzzTarget: ] command += [ - '-t', 'gcr.io/oss-fuzz-base/base-runner', 'reproduce', self.target_name, - '-runs=100' + '-t', docker.BASE_RUNNER_TAG, 'reproduce', self.target_name, '-runs=100' ] logging.info('Running reproduce command: %s.', ' '.join(command)) @@ -246,7 +277,6 @@ class FuzzTarget: logging.info('The crash is reproducible. The crash doesn\'t reproduce ' 'on old builds. This code change probably introduced the ' 'crash.') - return True logging.info('The crash is reproducible on old builds ' diff --git a/infra/cifuzz/fuzz_target_test.py b/infra/cifuzz/fuzz_target_test.py index 8a506fa59..8bec234dc 100644 --- a/infra/cifuzz/fuzz_target_test.py +++ b/infra/cifuzz/fuzz_target_test.py @@ -148,8 +148,7 @@ class GetTestCaseTest(unittest.TestCase): def test_valid_error_string(self): """Tests that get_testcase returns the correct testcase give an error.""" testcase_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'test_files', - 'example_crash_fuzzer_output.txt') + 'test_data', 'example_crash_fuzzer_output.txt') with open(testcase_path, 'rb') as test_fuzz_output: parsed_testcase = self.test_target.get_testcase(test_fuzz_output.read()) self.assertEqual( diff --git a/infra/cifuzz/run_fuzzers.py b/infra/cifuzz/run_fuzzers.py index 2a2a89e5f..513cfb6fa 100644 --- a/infra/cifuzz/run_fuzzers.py +++ b/infra/cifuzz/run_fuzzers.py @@ -91,7 +91,9 @@ class BaseFuzzTargetRunner: """Fuzzes with |fuzz_target_obj| and returns the result.""" # TODO(metzman): Make children implement this so that the batch runner can # do things differently. - return fuzz_target_obj.fuzz() + result = fuzz_target_obj.fuzz() + fuzz_target_obj.free_disk_if_needed() + return result @property def quit_on_bug_found(self): @@ -100,9 +102,12 @@ class BaseFuzzTargetRunner: raise NotImplementedError('Child class must implement method') def get_fuzz_target_artifact(self, target, artifact_name): - """Returns the path of a fuzzing |artifact| named |artifact_name| for - |target|.""" - artifact_name = target.target_name + '-' + artifact_name + """Returns the path of a fuzzing artifact named |artifact_name| for + |fuzz_target|.""" + artifact_name = '{target_name}-{sanitizer}-{artifact_name}'.format( + target_name=target.target_name, + sanitizer=self.config.sanitizer, + artifact_name=artifact_name) return os.path.join(self.artifacts_dir, artifact_name) def create_fuzz_target_obj(self, target_path, run_seconds): @@ -140,12 +145,9 @@ class BaseFuzzTargetRunner: target.target_name) continue - # We found a bug in the fuzz target. - utils.binary_print(b'Fuzzer: %s. Detected bug:\n%s' % - (target.target_name.encode(), result.stacktrace)) - # TODO(metzman): Do this with filestore. - testcase_artifact_path = self.get_fuzz_target_artifact(target, 'testcase') + testcase_artifact_path = self.get_fuzz_target_artifact( + target, os.path.basename(result.testcase)) shutil.move(result.testcase, testcase_artifact_path) bug_summary_artifact_path = self.get_fuzz_target_artifact( target, 'bug-summary.txt') diff --git a/infra/cifuzz/run_fuzzers_entrypoint.py b/infra/cifuzz/run_fuzzers_entrypoint.py index f810e38f8..46e208dc0 100644 --- a/infra/cifuzz/run_fuzzers_entrypoint.py +++ b/infra/cifuzz/run_fuzzers_entrypoint.py @@ -11,11 +11,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Runs specific OSS-Fuzz project's fuzzers for CI tools.""" +"""Runs a specific OSS-Fuzz project's fuzzers for CI tools.""" import logging import sys import config_utils +import docker import run_fuzzers # pylint: disable=c-extension-no-member @@ -26,6 +27,21 @@ logging.basicConfig( level=logging.DEBUG) +def delete_unneeded_docker_images(config): + """Deletes unneeded docker images if running in an environment with low + disk space.""" + if not config.low_disk_space: + return + logging.info('Deleting builder docker images to save disk space.') + project_image = docker.get_project_image_name(config.project_name) + images = [ + project_image, + docker.BASE_RUNNER_TAG, + docker.MSAN_LIBS_BUILDER_TAG, + ] + docker.delete_images(images) + + def main(): """Runs OSS-Fuzz project's fuzzers for CI tools. This is the entrypoint for the run_fuzzers github action. @@ -62,6 +78,7 @@ def main(): logging.error('This script needs to be run within Github actions.') return returncode + delete_unneeded_docker_images(config) # Run the specified project's fuzzers from the build. result = run_fuzzers.run_fuzzers(config) if result == run_fuzzers.RunFuzzersResult.ERROR: diff --git a/infra/cifuzz/run_fuzzers_test.py b/infra/cifuzz/run_fuzzers_test.py index 847ddf399..b2659903c 100644 --- a/infra/cifuzz/run_fuzzers_test.py +++ b/infra/cifuzz/run_fuzzers_test.py @@ -37,13 +37,13 @@ import test_helpers EXAMPLE_PROJECT = 'example' # Location of files used for testing. -TEST_FILES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'test_files') +TEST_DATA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'test_data') -MEMORY_FUZZER_DIR = os.path.join(TEST_FILES_PATH, 'memory') +MEMORY_FUZZER_DIR = os.path.join(TEST_DATA_PATH, 'memory') MEMORY_FUZZER = 'curl_fuzzer_memory' -UNDEFINED_FUZZER_DIR = os.path.join(TEST_FILES_PATH, 'undefined') +UNDEFINED_FUZZER_DIR = os.path.join(TEST_DATA_PATH, 'undefined') UNDEFINED_FUZZER = 'curl_fuzzer_undefined' FUZZ_SECONDS = 10 @@ -227,7 +227,8 @@ class BaseFuzzTargetRunnerTest(unittest.TestCase): target.target_name = target_name fuzz_target_artifact = runner.get_fuzz_target_artifact( target, artifact_name) - expected_fuzz_target_artifact = 'artifacts-dir/target_name-artifact-name' + expected_fuzz_target_artifact = ( + 'artifacts-dir/target_name-address-artifact-name') self.assertEqual(fuzz_target_artifact, expected_fuzz_target_artifact) @@ -263,7 +264,7 @@ class CiFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase): magic_mock.target_name = 'target1' mocked_create_fuzz_target_obj.return_value = magic_mock self.assertTrue(runner.run_fuzz_targets()) - self.assertIn('target1-testcase', os.listdir(runner.artifacts_dir)) + self.assertIn('target1-address-testcase', os.listdir(runner.artifacts_dir)) self.assertEqual(mocked_run_fuzz_target.call_count, 1) @@ -279,7 +280,7 @@ class BatchFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase): def test_run_fuzz_targets_quits(self, mocked_create_fuzz_target_obj, mocked_run_fuzz_target, mocked_get_fuzz_targets): - """Tests that run_fuzz_targets quits on the first crash it finds.""" + """Tests that run_fuzz_targets doesn't quit on the first crash it finds.""" workspace = 'workspace' out_path = os.path.join(workspace, 'out') self.fs.create_dir(out_path) @@ -290,8 +291,8 @@ class BatchFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase): mocked_get_fuzz_targets.return_value = ['target1', 'target2'] runner.initialize() - testcase1 = os.path.join(workspace, 'testcase1') - testcase2 = os.path.join(workspace, 'testcase2') + testcase1 = os.path.join(workspace, 'testcase-aaa') + testcase2 = os.path.join(workspace, 'testcase-bbb') self.fs.create_file(testcase1) self.fs.create_file(testcase2) stacktrace = b'stacktrace' @@ -312,7 +313,8 @@ class BatchFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase): magic_mock.target_name = 'target1' mocked_create_fuzz_target_obj.return_value = magic_mock self.assertTrue(runner.run_fuzz_targets()) - self.assertIn('target1-testcase', os.listdir(runner.artifacts_dir)) + self.assertIn('target1-address-testcase-aaa', + os.listdir(runner.artifacts_dir)) self.assertEqual(mocked_run_fuzz_target.call_count, 2) @@ -333,7 +335,7 @@ class RunAddressFuzzersIntegrationTest(RunFuzzerIntegrationTestMixin, side_effect=[True, False]): with tempfile.TemporaryDirectory() as tmp_dir: workspace = os.path.join(tmp_dir, 'workspace') - shutil.copytree(TEST_FILES_PATH, workspace) + shutil.copytree(TEST_DATA_PATH, workspace) config = _create_config(fuzz_seconds=FUZZ_SECONDS, workspace=workspace, project_name=EXAMPLE_PROJECT) @@ -349,17 +351,17 @@ class RunAddressFuzzersIntegrationTest(RunFuzzerIntegrationTestMixin, def test_old_bug_found(self, _): """Tests run_fuzzers with a bug found in OSS-Fuzz before.""" config = _create_config(fuzz_seconds=FUZZ_SECONDS, - workspace=TEST_FILES_PATH, + workspace=TEST_DATA_PATH, project_name=EXAMPLE_PROJECT) with tempfile.TemporaryDirectory() as tmp_dir: workspace = os.path.join(tmp_dir, 'workspace') - shutil.copytree(TEST_FILES_PATH, workspace) + shutil.copytree(TEST_DATA_PATH, workspace) config = _create_config(fuzz_seconds=FUZZ_SECONDS, - workspace=TEST_FILES_PATH, + workspace=TEST_DATA_PATH, project_name=EXAMPLE_PROJECT) result = run_fuzzers.run_fuzzers(config) self.assertEqual(result, run_fuzzers.RunFuzzersResult.NO_BUG_FOUND) - build_dir = os.path.join(TEST_FILES_PATH, 'out', self.BUILD_DIR_NAME) + build_dir = os.path.join(TEST_DATA_PATH, 'out', self.BUILD_DIR_NAME) self.assertTrue(os.path.exists(build_dir)) self.assertNotEqual(0, len(os.listdir(build_dir))) diff --git a/infra/cifuzz/stack_parser.py b/infra/cifuzz/stack_parser.py index 0077caae9..69c44bc2e 100644 --- a/infra/cifuzz/stack_parser.py +++ b/infra/cifuzz/stack_parser.py @@ -13,6 +13,8 @@ # limitations under the License. """Module for parsing stacks from fuzz targets.""" +import logging + # From clusterfuzz: src/python/crash_analysis/crash_analyzer.py # Used to get the beginning of the stacktrace. STACKTRACE_TOOL_MARKERS = [ @@ -51,25 +53,33 @@ def parse_fuzzer_output(fuzzer_output, parsed_output_file_path): parsed_output_file_path: The location to store the parsed output. """ # Get index of key file points. + begin_stack = None for marker in STACKTRACE_TOOL_MARKERS: marker_index = fuzzer_output.find(marker) - if marker_index: + if marker_index != -1: begin_stack = marker_index break - end_stack = -1 + if begin_stack is None: + logging.error( + b'Could not find a begin stack marker (%s) in fuzzer output:\n%s', + STACKTRACE_TOOL_MARKERS, fuzzer_output) + return + + end_stack = None for marker in STACKTRACE_END_MARKERS: marker_index = fuzzer_output.find(marker) - if marker_index: + if marker_index != -1: end_stack = marker_index + len(marker) break - if begin_stack is None or end_stack is None: + if end_stack is None: + logging.error( + b'Could not find an end stack marker (%s) in fuzzer output:\n%s', + STACKTRACE_END_MARKERS, fuzzer_output) return summary_str = fuzzer_output[begin_stack:end_stack] - if not summary_str: - return # Write sections of fuzzer output to specific files. with open(parsed_output_file_path, 'ab') as summary_handle: diff --git a/infra/cifuzz/stack_parser_test.py b/infra/cifuzz/stack_parser_test.py index 9b05710fc..faf601fd5 100644 --- a/infra/cifuzz/stack_parser_test.py +++ b/infra/cifuzz/stack_parser_test.py @@ -14,7 +14,9 @@ """Tests for stack_parser.""" import os import unittest +from unittest import mock +import parameterized from pyfakefs import fake_filesystem_unittest import stack_parser @@ -23,9 +25,9 @@ import stack_parser # https://github.com/google/oss-fuzz/tree/master/projects/example project. EXAMPLE_PROJECT = 'example' -# Location of files used for testing. -TEST_FILES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'test_files') +# Location of data used for testing. +TEST_DATA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'test_data') class ParseOutputTest(fake_filesystem_unittest.TestCase): @@ -33,33 +35,42 @@ class ParseOutputTest(fake_filesystem_unittest.TestCase): def setUp(self): self.setUpPyfakefs() + self.maxDiff = None # pylint: disable=invalid-name - def test_parse_valid_output(self): + @parameterized.parameterized.expand([('example_crash_fuzzer_output.txt', + 'example_crash_fuzzer_bug_summary.txt'), + ('msan_crash_fuzzer_output.txt', + 'msan_crash_fuzzer_bug_summary.txt')]) + def test_parse_valid_output(self, fuzzer_output_file, bug_summary_file): """Checks that the parse fuzzer output can correctly parse output.""" # Read the fuzzer output from disk. - fuzzer_output_path = os.path.join(TEST_FILES_PATH, - 'example_crash_fuzzer_output.txt') + fuzzer_output_path = os.path.join(TEST_DATA_PATH, fuzzer_output_file) self.fs.add_real_file(fuzzer_output_path) with open(fuzzer_output_path, 'rb') as fuzzer_output_handle: fuzzer_output = fuzzer_output_handle.read() bug_summary_path = '/bug-summary.txt' - stack_parser.parse_fuzzer_output(fuzzer_output, bug_summary_path) + with mock.patch('logging.info') as mocked_info: + stack_parser.parse_fuzzer_output(fuzzer_output, bug_summary_path) + mocked_info.assert_not_called() + with open(bug_summary_path) as bug_summary_handle: bug_summary = bug_summary_handle.read() # Compare the bug to the expected one. - expected_bug_summary_path = os.path.join(TEST_FILES_PATH, - 'bug_summary_example.txt') + expected_bug_summary_path = os.path.join(TEST_DATA_PATH, bug_summary_file) self.fs.add_real_file(expected_bug_summary_path) with open(expected_bug_summary_path) as expected_bug_summary_handle: expected_bug_summary = expected_bug_summary_handle.read() + self.assertEqual(expected_bug_summary, bug_summary) def test_parse_invalid_output(self): """Checks that no files are created when an invalid input was given.""" artifact_path = '/bug-summary.txt' - stack_parser.parse_fuzzer_output(b'not a valid output_string', - artifact_path) + with mock.patch('logging.error') as mocked_error: + stack_parser.parse_fuzzer_output(b'not a valid output_string', + artifact_path) + assert mocked_error.call_count self.assertFalse(os.path.exists(artifact_path)) diff --git a/infra/cifuzz/test_files/bug_summary_example.txt b/infra/cifuzz/test_data/example_crash_fuzzer_bug_summary.txt index 8caebad0c..8caebad0c 100644 --- a/infra/cifuzz/test_files/bug_summary_example.txt +++ b/infra/cifuzz/test_data/example_crash_fuzzer_bug_summary.txt diff --git a/infra/cifuzz/test_files/example_crash_fuzzer_output.txt b/infra/cifuzz/test_data/example_crash_fuzzer_output.txt index d316f5f40..d316f5f40 100644 --- a/infra/cifuzz/test_files/example_crash_fuzzer_output.txt +++ b/infra/cifuzz/test_data/example_crash_fuzzer_output.txt diff --git a/infra/cifuzz/test_files/example_curl_cov.json b/infra/cifuzz/test_data/example_curl_cov.json index 0936102fd..0936102fd 100644 --- a/infra/cifuzz/test_files/example_curl_cov.json +++ b/infra/cifuzz/test_data/example_curl_cov.json diff --git a/infra/cifuzz/test_files/example_curl_file_list.json b/infra/cifuzz/test_data/example_curl_file_list.json index 0ed1965c5..0ed1965c5 100644 --- a/infra/cifuzz/test_files/example_curl_file_list.json +++ b/infra/cifuzz/test_data/example_curl_file_list.json diff --git a/infra/cifuzz/test_files/example_curl_fuzzer_cov.json b/infra/cifuzz/test_data/example_curl_fuzzer_cov.json index 6f8c2498c..6f8c2498c 100644 --- a/infra/cifuzz/test_files/example_curl_fuzzer_cov.json +++ b/infra/cifuzz/test_data/example_curl_fuzzer_cov.json diff --git a/infra/cifuzz/test_files/external-project/Makefile b/infra/cifuzz/test_data/external-project/Makefile index 2c1773776..2c1773776 100644 --- a/infra/cifuzz/test_files/external-project/Makefile +++ b/infra/cifuzz/test_data/external-project/Makefile diff --git a/infra/cifuzz/test_files/external-project/do_stuff_fuzzer.cpp b/infra/cifuzz/test_data/external-project/do_stuff_fuzzer.cpp index 71fa8cae2..71fa8cae2 100644 --- a/infra/cifuzz/test_files/external-project/do_stuff_fuzzer.cpp +++ b/infra/cifuzz/test_data/external-project/do_stuff_fuzzer.cpp diff --git a/infra/cifuzz/test_files/external-project/do_stuff_fuzzer.dict b/infra/cifuzz/test_data/external-project/do_stuff_fuzzer.dict index 224679bf4..224679bf4 100644 --- a/infra/cifuzz/test_files/external-project/do_stuff_fuzzer.dict +++ b/infra/cifuzz/test_data/external-project/do_stuff_fuzzer.dict diff --git a/infra/cifuzz/test_files/external-project/my_api.cpp b/infra/cifuzz/test_data/external-project/my_api.cpp index 9a2c1bc1c..9a2c1bc1c 100644 --- a/infra/cifuzz/test_files/external-project/my_api.cpp +++ b/infra/cifuzz/test_data/external-project/my_api.cpp diff --git a/infra/cifuzz/test_files/external-project/my_api.h b/infra/cifuzz/test_data/external-project/my_api.h index 325aa15cc..325aa15cc 100644 --- a/infra/cifuzz/test_files/external-project/my_api.h +++ b/infra/cifuzz/test_data/external-project/my_api.h diff --git a/infra/cifuzz/test_files/external-project/oss-fuzz/Dockerfile b/infra/cifuzz/test_data/external-project/oss-fuzz/Dockerfile index e9dc33031..e9dc33031 100644 --- a/infra/cifuzz/test_files/external-project/oss-fuzz/Dockerfile +++ b/infra/cifuzz/test_data/external-project/oss-fuzz/Dockerfile diff --git a/infra/cifuzz/test_files/external-project/oss-fuzz/build.sh b/infra/cifuzz/test_data/external-project/oss-fuzz/build.sh index 2c52ef90f..2c52ef90f 100644 --- a/infra/cifuzz/test_files/external-project/oss-fuzz/build.sh +++ b/infra/cifuzz/test_data/external-project/oss-fuzz/build.sh diff --git a/infra/cifuzz/test_files/external-project/standalone_fuzz_target_runner.cpp b/infra/cifuzz/test_data/external-project/standalone_fuzz_target_runner.cpp index 38a0454f0..38a0454f0 100644 --- a/infra/cifuzz/test_files/external-project/standalone_fuzz_target_runner.cpp +++ b/infra/cifuzz/test_data/external-project/standalone_fuzz_target_runner.cpp diff --git a/infra/cifuzz/test_files/memory/out/curl_fuzzer_memory b/infra/cifuzz/test_data/memory/out/curl_fuzzer_memory Binary files differindex c602ce970..c602ce970 100755 --- a/infra/cifuzz/test_files/memory/out/curl_fuzzer_memory +++ b/infra/cifuzz/test_data/memory/out/curl_fuzzer_memory diff --git a/infra/cifuzz/test_data/msan_crash_fuzzer_bug_summary.txt b/infra/cifuzz/test_data/msan_crash_fuzzer_bug_summary.txt new file mode 100644 index 000000000..b55e9c6b7 --- /dev/null +++ b/infra/cifuzz/test_data/msan_crash_fuzzer_bug_summary.txt @@ -0,0 +1,22 @@ +MemorySanitizer: use-of-uninitialized-value +#0 0x52675f in LLVMFuzzerTestOneInput /src/cifuzz-example/do_stuff_fuzzer.cpp:13:7 +#1 0x45a431 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:599:15 +#2 0x45ba46 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:792:3 +#3 0x45bed9 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:845:3 +#4 0x44a4bc in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:906:6 +#5 0x474432 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10 +#6 0x7eff5562683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f) +#7 0x41eab8 in _start (out/do_stuff_fuzzer+0x41eab8) + +DEDUP_TOKEN: LLVMFuzzerTestOneInput--fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long)--fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) +Uninitialized value was created by a heap allocation +#0 0x4d57ad in malloc /src/llvm-project/compiler-rt/lib/msan/msan_interceptors.cpp:901:3 +#1 0x437c07 in operator new(unsigned long) (out/do_stuff_fuzzer+0x437c07) +#2 0x45ba46 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:792:3 +#3 0x45bed9 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:845:3 +#4 0x44a4bc in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:906:6 +#5 0x474432 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10 +#6 0x7eff5562683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f) +DEDUP_TOKEN: malloc--operator new(unsigned long)--fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) + +SUMMARY:
\ No newline at end of file diff --git a/infra/cifuzz/test_data/msan_crash_fuzzer_output.txt b/infra/cifuzz/test_data/msan_crash_fuzzer_output.txt new file mode 100644 index 000000000..c803bfb1c --- /dev/null +++ b/infra/cifuzz/test_data/msan_crash_fuzzer_output.txt @@ -0,0 +1,39 @@ +Dictionary: 3 entries +INFO: Running with entropic power schedule (0xFF, 100). +INFO: Seed: 1337 +INFO: Loaded 1 modules (184 inline 8-bit counters): 184 [0x829300, 0x8293b8), +INFO: Loaded 1 PC tables (184 PCs): 184 [0x5dc910,0x5dd490), +INFO: 5 files found in /tmp/do_stuff_fuzzer_corpus +INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes +==13==WARNING: MemorySanitizer: use-of-uninitialized-value +#0 0x52675f in LLVMFuzzerTestOneInput /src/cifuzz-example/do_stuff_fuzzer.cpp:13:7 +#1 0x45a431 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:599:15 +#2 0x45ba46 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:792:3 +#3 0x45bed9 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:845:3 +#4 0x44a4bc in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:906:6 +#5 0x474432 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10 +#6 0x7eff5562683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f) +#7 0x41eab8 in _start (out/do_stuff_fuzzer+0x41eab8) + +DEDUP_TOKEN: LLVMFuzzerTestOneInput--fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long)--fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) +Uninitialized value was created by a heap allocation +#0 0x4d57ad in malloc /src/llvm-project/compiler-rt/lib/msan/msan_interceptors.cpp:901:3 +#1 0x437c07 in operator new(unsigned long) (out/do_stuff_fuzzer+0x437c07) +#2 0x45ba46 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:792:3 +#3 0x45bed9 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:845:3 +#4 0x44a4bc in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:906:6 +#5 0x474432 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10 +#6 0x7eff5562683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f) +DEDUP_TOKEN: malloc--operator new(unsigned long)--fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) + +SUMMARY: MemorySanitizer: use-of-uninitialized-value /src/cifuzz-example/do_stuff_fuzzer.cpp:13:7 in LLVMFuzzerTestOneInput +Unique heap origins: 65 +Stack depot allocated bytes: 4424 +Unique origin histories: 29 +History depot allocated bytes: 696 +Exiting +MS: 0 ; base unit: 0000000000000000000000000000000000000000 + + +artifact_prefix='./'; Test unit written to ./crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 +Base64: diff --git a/infra/cifuzz/test_files/out/example_crash_fuzzer b/infra/cifuzz/test_data/out/example_crash_fuzzer Binary files differindex 704800dda..704800dda 100755 --- a/infra/cifuzz/test_files/out/example_crash_fuzzer +++ b/infra/cifuzz/test_data/out/example_crash_fuzzer diff --git a/infra/cifuzz/test_files/out/example_nocrash_fuzzer b/infra/cifuzz/test_data/out/example_nocrash_fuzzer Binary files differindex e4ff86042..e4ff86042 100755 --- a/infra/cifuzz/test_files/out/example_nocrash_fuzzer +++ b/infra/cifuzz/test_data/out/example_nocrash_fuzzer diff --git a/infra/cifuzz/test_files/undefined/out/curl_fuzzer_undefined b/infra/cifuzz/test_data/undefined/out/curl_fuzzer_undefined Binary files differindex 504cab108..504cab108 100755 --- a/infra/cifuzz/test_files/undefined/out/curl_fuzzer_undefined +++ b/infra/cifuzz/test_data/undefined/out/curl_fuzzer_undefined |