aboutsummaryrefslogtreecommitdiff
path: root/infra/cifuzz
diff options
context:
space:
mode:
authorjonathanmetzman <31354670+jonathanmetzman@users.noreply.github.com>2021-03-12 07:27:07 -0800
committerGitHub <noreply@github.com>2021-03-12 07:27:07 -0800
commit3465403f3066d41641109aab7d2ab52b5b8ea603 (patch)
treeb9f86191eeed5d4a9bafd389d201495c5b7308e9 /infra/cifuzz
parent5a00fd347ee44eec4737c322619a2c39c084db15 (diff)
downloadoss-fuzz-3465403f3066d41641109aab7d2ab52b5b8ea603.tar.gz
[CIFuzz] Add functionality to save diskspace (#5342)
* [CIFuzz] Add functionality to save diskspace. Add a LOW_DISK_SPACE env/config var. When this is specified (always true for Github actions) run_fuzzers will delete base-builder and the project builder image before fuzzing. After it finishes fuzzing with a target, it will also delete the targets, its seed corpus and its corpus. Related: #4879
Diffstat (limited to 'infra/cifuzz')
-rw-r--r--infra/cifuzz/actions/build_fuzzers/action.yml1
-rw-r--r--infra/cifuzz/actions/run_fuzzers/action.yml1
-rw-r--r--infra/cifuzz/build_fuzzers.py12
-rw-r--r--infra/cifuzz/config_utils.py10
-rw-r--r--infra/cifuzz/docker.py38
-rw-r--r--infra/cifuzz/environment.py54
-rw-r--r--infra/cifuzz/fuzz_target.py37
-rw-r--r--infra/cifuzz/run_fuzzers.py4
-rw-r--r--infra/cifuzz/run_fuzzers_entrypoint.py19
9 files changed, 158 insertions, 18 deletions
diff --git a/infra/cifuzz/actions/build_fuzzers/action.yml b/infra/cifuzz/actions/build_fuzzers/action.yml
index 35ff010b3..a8a81ac02 100644
--- a/infra/cifuzz/actions/build_fuzzers/action.yml
+++ b/infra/cifuzz/actions/build_fuzzers/action.yml
@@ -35,3 +35,4 @@ runs:
SANITIZER: ${{ inputs.sanitizer }}
PROJECT_SRC_PATH: ${{ inputs.project-src-path }}
BUILD_INTEGRATION_PATH: ${{ inputs.build-integration-path }}
+ LOW_DISK_SPACE: 'True'
diff --git a/infra/cifuzz/actions/run_fuzzers/action.yml b/infra/cifuzz/actions/run_fuzzers/action.yml
index aec899608..d1c03c833 100644
--- a/infra/cifuzz/actions/run_fuzzers/action.yml
+++ b/infra/cifuzz/actions/run_fuzzers/action.yml
@@ -44,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/build_fuzzers.py b/infra/cifuzz/build_fuzzers.py
index 2dae6c986..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__))))
@@ -94,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',
])
@@ -119,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):
@@ -128,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):
@@ -238,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/config_utils.py b/infra/cifuzz/config_utils.py
index 7ee3444b3..9cf018bfd 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):
@@ -98,6 +100,8 @@ class BaseConfig:
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):
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..4bdd17969 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))
@@ -140,6 +145,25 @@ class FuzzTarget:
return FuzzResult(testcase, stderr)
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):
+ shutil.rmtree(self.latest_corpus_path)
+
+ 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 +200,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))
diff --git a/infra/cifuzz/run_fuzzers.py b/infra/cifuzz/run_fuzzers.py
index de2914f19..419a5169f 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):
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: