aboutsummaryrefslogtreecommitdiff
path: root/infra/cifuzz
diff options
context:
space:
mode:
Diffstat (limited to 'infra/cifuzz')
-rw-r--r--infra/cifuzz/actions/build_fuzzers/action.yml11
-rw-r--r--infra/cifuzz/actions/run_fuzzers/action.yml6
-rw-r--r--infra/cifuzz/affected_fuzz_targets_test.py8
-rw-r--r--infra/cifuzz/build_fuzzers.py24
-rw-r--r--infra/cifuzz/build_fuzzers_entrypoint.py10
-rw-r--r--infra/cifuzz/build_fuzzers_test.py27
-rw-r--r--infra/cifuzz/cifuzz-base/Dockerfile24
-rw-r--r--infra/cifuzz/config_utils.py32
-rw-r--r--infra/cifuzz/config_utils_test.py29
-rw-r--r--infra/cifuzz/coverage.py2
-rw-r--r--infra/cifuzz/coverage_test.py52
-rw-r--r--infra/cifuzz/docker.py38
-rw-r--r--infra/cifuzz/environment.py54
-rw-r--r--infra/cifuzz/fuzz_target.py46
-rw-r--r--infra/cifuzz/fuzz_target_test.py3
-rw-r--r--infra/cifuzz/run_fuzzers.py20
-rw-r--r--infra/cifuzz/run_fuzzers_entrypoint.py19
-rw-r--r--infra/cifuzz/run_fuzzers_test.py32
-rw-r--r--infra/cifuzz/stack_parser.py22
-rw-r--r--infra/cifuzz/stack_parser_test.py33
-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-xinfra/cifuzz/test_data/memory/out/curl_fuzzer_memory (renamed from infra/cifuzz/test_files/memory/out/curl_fuzzer_memory)bin9768680 -> 9768680 bytes
-rw-r--r--infra/cifuzz/test_data/msan_crash_fuzzer_bug_summary.txt22
-rw-r--r--infra/cifuzz/test_data/msan_crash_fuzzer_output.txt39
-rwxr-xr-xinfra/cifuzz/test_data/out/example_crash_fuzzer (renamed from infra/cifuzz/test_files/out/example_crash_fuzzer)bin4375872 -> 4375872 bytes
-rwxr-xr-xinfra/cifuzz/test_data/out/example_nocrash_fuzzer (renamed from infra/cifuzz/test_files/out/example_nocrash_fuzzer)bin4376224 -> 4376224 bytes
-rwxr-xr-xinfra/cifuzz/test_data/undefined/out/curl_fuzzer_undefined (renamed from infra/cifuzz/test_files/undefined/out/curl_fuzzer_undefined)bin14401312 -> 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
index c602ce970..c602ce970 100755
--- a/infra/cifuzz/test_files/memory/out/curl_fuzzer_memory
+++ b/infra/cifuzz/test_data/memory/out/curl_fuzzer_memory
Binary files differ
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
index 704800dda..704800dda 100755
--- a/infra/cifuzz/test_files/out/example_crash_fuzzer
+++ b/infra/cifuzz/test_data/out/example_crash_fuzzer
Binary files differ
diff --git a/infra/cifuzz/test_files/out/example_nocrash_fuzzer b/infra/cifuzz/test_data/out/example_nocrash_fuzzer
index e4ff86042..e4ff86042 100755
--- a/infra/cifuzz/test_files/out/example_nocrash_fuzzer
+++ b/infra/cifuzz/test_data/out/example_nocrash_fuzzer
Binary files differ
diff --git a/infra/cifuzz/test_files/undefined/out/curl_fuzzer_undefined b/infra/cifuzz/test_data/undefined/out/curl_fuzzer_undefined
index 504cab108..504cab108 100755
--- a/infra/cifuzz/test_files/undefined/out/curl_fuzzer_undefined
+++ b/infra/cifuzz/test_data/undefined/out/curl_fuzzer_undefined
Binary files differ