aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeo Neat <leosneat@gmail.com>2020-01-29 11:03:43 -0800
committerGitHub <noreply@github.com>2020-01-29 11:03:43 -0800
commit8ffc6db00c83e5f75e92b3c4c63c1924597711a1 (patch)
tree7cca59f92af682000c57be2e0b8ad5e926e8701f
parent4dc4c0240f96105f2330a0fc1f5f321a6e796ddb (diff)
downloadoss-fuzz-8ffc6db00c83e5f75e92b3c4c63c1924597711a1.tar.gz
[Infra] CIFuzz pipeline complete. (#3281)
* Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Testing action build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working build * Working fuzzers with out error surface * Working fuzzers with out error surface * Working fuzzers with out error surface * Printing std err * Adding fuzzer timeout * Adding fuzzer timeout * Changing fuzzer timeout to fuzz time * Formatting and refactoring * Spelling in fuzz_target.py * Spelling in fuzz_target.py * Spelling in fuzz_target.py * Upload artifact fix * Upload artifact fix * Upload artifact fix * Upload artifact fix * Upload artifact fix * Upload artifact fix * Upload artifact fix * Refactoring error codes. * reverting helper.py * reverting helper.py * reverting helper.py * chaning method to static * moving cifuzz file * Jonathan changes * Oliver and Jonathan comments * Oliver and Jonathan comments * Oliver and Jonathan comments * Utils unit tests * Test formatting and documentation * Build fuzzer test added * Changed repo manager errors * Unit and integration tests complete * Jonathan comments pt.1 * Jonathan comments pt.1 * Jonathan comments pt.1 * adding cifuzz_test * Build fuzzer test completed * Run fuzzers test finished. * Removed SRC dependency * Jonathan comments pt.2 * Max comments pt.1 * Max comments pt.2 * removing log specified out stream * Max comments pt.3 * Adding OSS_FUZZ_HOME env var * Jonathan comments pt.3 * Formatting * Olivers comments * Jonathan comments
-rw-r--r--infra/base-images/base-builder/Dockerfile2
-rw-r--r--infra/base-images/base-builder/detect_repo.py20
-rw-r--r--infra/base-images/base-builder/detect_repo_test.py4
-rw-r--r--infra/bisector.py7
-rw-r--r--infra/build_specified_commit.py19
-rw-r--r--infra/build_specified_commit_test.py36
-rw-r--r--infra/cifuzz.py88
-rw-r--r--infra/cifuzz/actions/Dockerfile3
-rw-r--r--infra/cifuzz/actions/action.yml5
-rw-r--r--infra/cifuzz/actions/entrypoint.py82
-rw-r--r--infra/cifuzz/cifuzz.py152
-rw-r--r--infra/cifuzz/cifuzz_test.py156
-rw-r--r--infra/cifuzz/fuzz_target.py108
-rw-r--r--infra/repo_manager.py25
-rw-r--r--infra/repo_manager_test.py3
-rw-r--r--infra/utils.py94
-rw-r--r--infra/utils_test.py102
17 files changed, 734 insertions, 172 deletions
diff --git a/infra/base-images/base-builder/Dockerfile b/infra/base-images/base-builder/Dockerfile
index c34f140c6..0e22bafa2 100644
--- a/infra/base-images/base-builder/Dockerfile
+++ b/infra/base-images/base-builder/Dockerfile
@@ -104,7 +104,7 @@ RUN mkdir honggfuzz && \
COPY compile compile_afl compile_dataflow compile_libfuzzer compile_honggfuzz \
precompile_honggfuzz srcmap write_labels.py /usr/local/bin/
-COPY detect_repo.py $SRC/
+COPY detect_repo.py /src
RUN precompile_honggfuzz
diff --git a/infra/base-images/base-builder/detect_repo.py b/infra/base-images/base-builder/detect_repo.py
index f57947e7e..e4570ed76 100644
--- a/infra/base-images/base-builder/detect_repo.py
+++ b/infra/base-images/base-builder/detect_repo.py
@@ -40,11 +40,8 @@ def main():
parser = argparse.ArgumentParser(
description=
'Finds a specific git repo in an oss-fuzz project\'s docker file.')
- parser.add_argument(
- '--src_dir',
- help='The location of an oss-fuzz project\'s source directory.',
- required=True)
parser.add_argument('--repo_name', help='The name of the git repo.')
+ parser.add_argument('--src_dir', help='The location of the possible repo.')
parser.add_argument('--example_commit',
help='A commit SHA referencing the project\'s main repo.')
@@ -52,18 +49,23 @@ def main():
if not args.repo_name and not args.example_commit:
raise ValueError(
'Requires an example commit or a repo name to find repo location.')
- for single_dir in os.listdir(args.src_dir):
- full_path = os.path.join(args.src_dir, single_dir)
+ if args.src_dir:
+ src_dir = args.src_dir
+ else:
+ src_dir = os.environ.get('SRC', '/src')
+
+ for single_dir in os.listdir(src_dir):
+ full_path = os.path.join(src_dir, single_dir)
if not os.path.isdir(full_path):
continue
if args.example_commit and check_for_commit(full_path, args.example_commit):
- print('Detected repo:', get_repo(full_path), single_dir)
+ print('Detected repo:', get_repo(full_path), full_path)
return
if args.repo_name and check_for_repo_name(full_path, args.repo_name):
- print('Detected repo:', get_repo(full_path), single_dir)
+ print('Detected repo:', get_repo(full_path), full_path)
return
print('No git repos with specific commit: %s found in %s' %
- (args.example_commit, args.src_dir))
+ (args.example_commit, src_dir))
def get_repo(repo_path):
diff --git a/infra/base-images/base-builder/detect_repo_test.py b/infra/base-images/base-builder/detect_repo_test.py
index e9029b7ff..b15bedc6f 100644
--- a/infra/base-images/base-builder/detect_repo_test.py
+++ b/infra/base-images/base-builder/detect_repo_test.py
@@ -99,8 +99,10 @@ class DetectRepoTest(unittest.TestCase):
match = re.search(r'\bDetected repo: ([^ ]+) ([^ ]+)', out.rstrip())
if match and match.group(1) and match.group(2):
self.assertEqual(match.group(1), repo_origin)
+ self.assertEqual(match.group(2), os.path.join(tmp_dir, repo_name))
else:
self.assertIsNone(repo_origin)
+ self.assertIsNone(repo_name)
def check_commit_with_repo(self, repo_origin, repo_name, commit, tmp_dir):
"""Checks the detect repos main method for a specific set of inputs.
@@ -121,7 +123,7 @@ class DetectRepoTest(unittest.TestCase):
match = re.search(r'\bDetected repo: ([^ ]+) ([^ ]+)', out.rstrip())
if match and match.group(1) and match.group(2):
self.assertEqual(match.group(1), repo_origin)
- self.assertEqual(match.group(2), repo_name)
+ self.assertEqual(match.group(2), os.path.join(tmp_dir, repo_name))
else:
self.assertIsNone(repo_origin)
self.assertIsNone(repo_name)
diff --git a/infra/bisector.py b/infra/bisector.py
index de9fad71b..11ffd0189 100644
--- a/infra/bisector.py
+++ b/infra/bisector.py
@@ -31,20 +31,17 @@ This is done with the following steps:
"""
import argparse
-import os
import tempfile
import build_specified_commit
import helper
import repo_manager
+import utils
def main():
"""Finds the commit SHA where an error was initally introduced."""
- oss_fuzz_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
- if os.getcwd() != oss_fuzz_dir:
- print('Changing directory to OSS-Fuzz home directory')
- os.chdir(oss_fuzz_dir)
+ utils.chdir_to_root()
parser = argparse.ArgumentParser(
description='git bisection for finding introduction of bugs')
diff --git a/infra/build_specified_commit.py b/infra/build_specified_commit.py
index 3861f0a1a..a4f95eb67 100644
--- a/infra/build_specified_commit.py
+++ b/infra/build_specified_commit.py
@@ -21,8 +21,10 @@ import os
import collections
import re
import subprocess
+import sys
import helper
+import utils
BuildData = collections.namedtuple(
'BuildData', ['project_name', 'engine', 'sanitizer', 'architecture'])
@@ -50,7 +52,7 @@ def build_fuzzers_from_commit(commit, build_repo_manager, build_data):
'/src', build_repo_manager.repo_name))
-def detect_main_repo(project_name, repo_name=None, commit=None, src_dir='/src'):
+def detect_main_repo(project_name, repo_name=None, commit=None):
"""Checks a docker image for the main repo of an OSS-Fuzz project.
Note: The default is to use the repo name to detect the main repo.
@@ -62,20 +64,25 @@ def detect_main_repo(project_name, repo_name=None, commit=None, src_dir='/src'):
src_dir: The location of the projects source on the docker image.
Returns:
- The repo's origin, the repo's name.
+ The repo's origin, the repo's path.
"""
- # TODO: Add infra for non hardcoded '/src'.
+
if not repo_name and not commit:
print('Error: can not detect main repo without a repo_name or a commit.')
return None, None
if repo_name and commit:
print('Both repo name and commit specific. Using repo name for detection.')
- helper.build_image_impl(project_name)
+ # Change to oss-fuzz main directory so helper.py runs correctly.
+ utils.chdir_to_root()
+ if not helper.build_image_impl(project_name):
+ print('Error: building {} image failed.'.format(project_name),
+ file=sys.stderr)
+ return None, None
docker_image_name = 'gcr.io/oss-fuzz/' + project_name
command_to_run = [
'docker', 'run', '--rm', '-t', docker_image_name, 'python3',
- os.path.join(src_dir, 'detect_repo.py'), '--src_dir', src_dir
+ os.path.join('/src', 'detect_repo.py')
]
if repo_name:
command_to_run.extend(['--repo_name', repo_name])
@@ -111,5 +118,5 @@ def execute(command, location=None, check_result=False):
raise RuntimeError('Error: %s\n Command: %s\n Return code: %s\n Out: %s' %
(err, command, process.returncode, out))
if out is not None:
- out = out.decode('ascii')
+ out = out.decode('ascii').rstrip()
return out, process.returncode
diff --git a/infra/build_specified_commit_test.py b/infra/build_specified_commit_test.py
index 98e749955..14a3577f1 100644
--- a/infra/build_specified_commit_test.py
+++ b/infra/build_specified_commit_test.py
@@ -66,52 +66,52 @@ class BuildImageIntegrationTests(unittest.TestCase):
def test_detect_main_repo_from_commit(self):
"""Test the detect main repo function from build specific commit module."""
- repo_origin, repo_name = build_specified_commit.detect_main_repo(
+ repo_origin, repo_path = build_specified_commit.detect_main_repo(
'curl', commit='bc5d22c3dede2f04870c37aec9a50474c4b888ad')
self.assertEqual(repo_origin, 'https://github.com/curl/curl.git')
- self.assertEqual(repo_name, 'curl')
+ self.assertEqual(repo_path, '/src/curl')
- repo_origin, repo_name = build_specified_commit.detect_main_repo(
+ repo_origin, repo_path = build_specified_commit.detect_main_repo(
'usrsctp', commit='4886aaa49fb90e479226fcfc3241d74208908232')
self.assertEqual(repo_origin, 'https://github.com/weinrank/usrsctp')
- self.assertEqual(repo_name, 'usrsctp')
+ self.assertEqual(repo_path, '/src/usrsctp')
- repo_origin, repo_name = build_specified_commit.detect_main_repo(
+ repo_origin, repo_path = build_specified_commit.detect_main_repo(
'ndpi', commit='c4d476cc583a2ef1e9814134efa4fbf484564ed7')
self.assertEqual(repo_origin, 'https://github.com/ntop/nDPI.git')
- self.assertEqual(repo_name, 'ndpi')
+ self.assertEqual(repo_path, '/src/ndpi')
- repo_origin, repo_name = build_specified_commit.detect_main_repo(
+ repo_origin, repo_path = build_specified_commit.detect_main_repo(
'notproj', commit='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
self.assertIsNone(repo_origin)
- self.assertIsNone(repo_name)
+ self.assertIsNone(repo_path)
def test_detect_main_repo_from_name(self):
"""Test the detect main repo function from build specific commit module."""
- repo_origin, repo_name = build_specified_commit.detect_main_repo(
+ repo_origin, repo_path = build_specified_commit.detect_main_repo(
'curl', repo_name='curl')
self.assertEqual(repo_origin, 'https://github.com/curl/curl.git')
- self.assertEqual(repo_name, 'curl')
+ self.assertEqual(repo_path, '/src/curl')
- repo_origin, repo_name = build_specified_commit.detect_main_repo(
+ repo_origin, repo_path = build_specified_commit.detect_main_repo(
'yara', repo_name='yara')
self.assertEqual(repo_origin, 'https://github.com/VirusTotal/yara.git')
- self.assertEqual(repo_name, 'yara')
+ self.assertEqual(repo_path, '/src/yara')
- repo_origin, repo_name = build_specified_commit.detect_main_repo(
+ repo_origin, repo_path = build_specified_commit.detect_main_repo(
'usrsctp', repo_name='usrsctp')
self.assertEqual(repo_origin, 'https://github.com/weinrank/usrsctp')
- self.assertEqual(repo_name, 'usrsctp')
+ self.assertEqual(repo_path, '/src/usrsctp')
- repo_origin, repo_name = build_specified_commit.detect_main_repo(
+ repo_origin, repo_path = build_specified_commit.detect_main_repo(
'ndpi', repo_name='nDPI')
self.assertEqual(repo_origin, 'https://github.com/ntop/nDPI.git')
- self.assertEqual(repo_name, 'ndpi')
+ self.assertEqual(repo_path, '/src/ndpi')
- repo_origin, repo_name = build_specified_commit.detect_main_repo(
+ repo_origin, repo_path = build_specified_commit.detect_main_repo(
'notproj', repo_name='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
self.assertIsNone(repo_origin)
- self.assertIsNone(repo_name)
+ self.assertIsNone(repo_path)
if __name__ == '__main__':
diff --git a/infra/cifuzz.py b/infra/cifuzz.py
deleted file mode 100644
index 9dcb2b192..000000000
--- a/infra/cifuzz.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright 2020 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 used by CI tools in order to interact with fuzzers.
-This module helps CI tools do the following:
- 1. Build fuzzers.
- 2. Run fuzzers.
-Eventually it will be used to help CI tools determine which fuzzers to run.
-"""
-
-import argparse
-import os
-import tempfile
-
-import build_specified_commit
-import repo_manager
-import helper
-
-
-def main():
- """Connects Fuzzers with CI tools.
-
- Returns:
- True on success False on failure.
- """
- parser = argparse.ArgumentParser(
- description='Help CI tools manage specific fuzzers.')
-
- subparsers = parser.add_subparsers(dest='command')
- build_fuzzer_parser = subparsers.add_parser(
- 'build_fuzzers', help='Build an OSS-Fuzz projects fuzzers.')
- build_fuzzer_parser.add_argument('project_name')
- build_fuzzer_parser.add_argument('repo_name')
- build_fuzzer_parser.add_argument('commit_sha')
-
- run_fuzzer_parser = subparsers.add_parser(
- 'run_fuzzers', help='Run an OSS-Fuzz projects fuzzers.')
- run_fuzzer_parser.add_argument('project_name')
- args = parser.parse_args()
-
- # Change to oss-fuzz main directory so helper.py runs correctly.
- if os.getcwd() != helper.OSSFUZZ_DIR:
- os.chdir(helper.OSSFUZZ_DIR)
-
- if args.command == 'build_fuzzers':
- return build_fuzzers(args) == 0
- if args.command == 'run_fuzzer':
- print('Not implemented')
- return False
- print('Invalid argument option, use build_fuzzers or run_fuzzer.')
- return False
-
-
-def build_fuzzers(args):
- """Builds all of the fuzzers for a specific OSS-Fuzz project.
-
- Returns:
- True on success False on failure.
- """
-
- # TODO: Fix return value bubble to actually handle errors.
- with tempfile.TemporaryDirectory() as tmp_dir:
- inferred_url, repo_name = build_specified_commit.detect_main_repo(
- args.project_name, repo_name=args.repo_name)
- build_repo_manager = repo_manager.RepoManager(inferred_url,
- tmp_dir,
- repo_name=repo_name)
- build_data = build_specified_commit.BuildData(
- project_name=args.project_name,
- sanitizer='address',
- engine='libfuzzer',
- architecture='x86_64')
- return build_specified_commit.build_fuzzers_from_commit(
- args.commit_sha, build_repo_manager, build_data) == 0
-
-
-if __name__ == '__main__':
- main()
diff --git a/infra/cifuzz/actions/Dockerfile b/infra/cifuzz/actions/Dockerfile
index 7cd442184..fe69d00ce 100644
--- a/infra/cifuzz/actions/Dockerfile
+++ b/infra/cifuzz/actions/Dockerfile
@@ -34,7 +34,8 @@ RUN add-apt-repository \
RUN apt-get update && apt-get install docker-ce docker-ce-cli containerd.io -y
-RUN git clone -b ci-fuzz https://github.com/google/oss-fuzz.git /src/oss-fuzz
+ENV OSS_FUZZ_ROOT=/opt/oss-fuzz
+RUN git clone https://github.com/google/oss-fuzz.git ${OSS_FUZZ_ROOT}
# Copies your code file from action repository to the container
COPY entrypoint.py /opt/entrypoint.py
diff --git a/infra/cifuzz/actions/action.yml b/infra/cifuzz/actions/action.yml
index 11095fbf9..7af4bd493 100644
--- a/infra/cifuzz/actions/action.yml
+++ b/infra/cifuzz/actions/action.yml
@@ -5,8 +5,13 @@ inputs:
project-name:
description: 'Name of the corresponding OSS-Fuzz project.'
required: true
+ fuzz-seconds:
+ description: 'The total time allotted for fuzzing in seconds.'
+ required: true
+ default: 360
runs:
using: 'docker'
image: 'Dockerfile'
env:
PROJECT_NAME: ${{ inputs.project-name }}
+ FUZZ_SECONDS: ${{ inputs.fuzz-seconds }}
diff --git a/infra/cifuzz/actions/entrypoint.py b/infra/cifuzz/actions/entrypoint.py
index 2a0415336..5b07c81fb 100644
--- a/infra/cifuzz/actions/entrypoint.py
+++ b/infra/cifuzz/actions/entrypoint.py
@@ -12,46 +12,70 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Builds and runs specific OSS-Fuzz project's fuzzers for CI tools."""
-
+import logging
import os
-import subprocess
import sys
+# pylint: disable=wrong-import-position
+sys.path.append(os.path.join(os.environ['OSS_FUZZ_ROOT'], 'infra', 'cifuzz'))
+import cifuzz
+
+# TODO: Turn default logging to INFO when CIFuzz is stable
+logging.basicConfig(
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.DEBUG)
+
def main():
- """Runs OSS-Fuzz project's fuzzers for CI tools."""
- project_name = os.environ['OSS_FUZZ_PROJECT_NAME']
- repo_name = os.environ['GITHUB_REPOSITORY'].rsplit('/', 1)[-1]
- commit_sha = os.environ['GITHUB_SHA']
+ """Runs OSS-Fuzz project's fuzzers for CI tools.
+ This script is used to kick off the Github Actions CI tool. It is the
+ entrypoint of the Dockerfile in this directory. This action can be added to
+ any OSS-Fuzz project's workflow that uses Github.
+
+ Required environment variables:
+ PROJECT_NAME: The name of OSS-Fuzz project.
+ FUZZ_TIME: The length of time in seconds that fuzzers are to be run.
+ GITHUB_REPOSITORY: The name of the Github repo that called this script.
+ GITHUB_SHA: The commit SHA that triggered this script.
+
+ Returns:
+ 0 on success or 1 on Failure.
+ """
+ oss_fuzz_project_name = os.environ.get('PROJECT_NAME')
+ fuzz_seconds = int(os.environ.get('FUZZ_SECONDS', 360))
+ github_repo_name = os.path.basename(os.environ.get('GITHUB_REPOSITORY'))
+ commit_sha = os.environ.get('GITHUB_SHA')
+
+ # Get the shared volume directory and create required directorys.
+ workspace = os.environ.get('GITHUB_WORKSPACE')
+ if not workspace:
+ logging.error('This script needs to be run in the Github action context.')
+ return 1
+ git_workspace = os.path.join(workspace, 'storage')
+ os.makedirs(git_workspace, exist_ok=True)
+ out_dir = os.path.join(workspace, 'out')
+ os.makedirs(out_dir, exist_ok=True)
# Build the specified project's fuzzers from the current repo state.
- print('Building fuzzers\nproject: {0}\nrepo name: {1}\ncommit: {2}'.format(
- project_name, repo_name, commit_sha))
- command = [
- 'python3', '/src/oss-fuzz/infra/cifuzz.py', 'build_fuzzers', project_name,
- repo_name, commit_sha
- ]
- print('Running command: "{0}"'.format(' '.join(command)))
- try:
- subprocess.check_call(command)
- except subprocess.CalledProcessError as err:
- sys.stderr.write('Error building fuzzers: "{0}"'.format(str(err)))
- return err.returncode
+ if not cifuzz.build_fuzzers(oss_fuzz_project_name, github_repo_name,
+ commit_sha, git_workspace, out_dir):
+ logging.error('Error building fuzzers for project %s.',
+ oss_fuzz_project_name)
+ return 1
# Run the specified project's fuzzers from the build.
- command = [
- 'python3', '/src/oss-fuzz/infra/cifuzz.py', 'run_fuzzers', project_name
- ]
- print('Running command: "{0}"'.format(' '.join(command)))
- try:
- subprocess.check_call(command)
- except subprocess.CalledProcessError as err:
- sys.stderr.write('Error running fuzzers: "{0}"'.format(str(err)))
- return err.returncode
- print('Fuzzers ran successfully.')
+ run_status, bug_found = cifuzz.run_fuzzers(oss_fuzz_project_name,
+ fuzz_seconds, out_dir)
+ if not run_status:
+ logging.error('Error occured while running fuzzers for project %s.',
+ oss_fuzz_project_name)
+ return 1
+ if bug_found:
+ logging.info('Bug found.')
+ # Return 2 when a bug was found by a fuzzer causing the CI to fail.
+ return 2
return 0
if __name__ == '__main__':
-
sys.exit(main())
diff --git a/infra/cifuzz/cifuzz.py b/infra/cifuzz/cifuzz.py
new file mode 100644
index 000000000..81c99c82a
--- /dev/null
+++ b/infra/cifuzz/cifuzz.py
@@ -0,0 +1,152 @@
+# Copyright 2020 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 used by CI tools in order to interact with fuzzers.
+This module helps CI tools do the following:
+ 1. Build fuzzers.
+ 2. Run fuzzers.
+Eventually it will be used to help CI tools determine which fuzzers to run.
+"""
+
+import logging
+import os
+import shutil
+import sys
+
+import fuzz_target
+
+# pylint: disable=wrong-import-position
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+import build_specified_commit
+import helper
+import repo_manager
+import utils
+
+# TODO: Turn default logging to WARNING when CIFuzz is stable
+logging.basicConfig(
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.DEBUG)
+
+
+def build_fuzzers(project_name, project_repo_name, commit_sha, git_workspace,
+ out_dir):
+ """Builds all of the fuzzers for a specific OSS-Fuzz project.
+
+ Args:
+ project_name: The name of the OSS-Fuzz project being built.
+ project_repo_name: The name of the projects repo.
+ commit_sha: The commit SHA to be checked out and fuzzed.
+ git_workspace: The location in the shared volume to store git repos.
+ out_dir: The location in the shared volume to store output artifacts.
+
+ Returns:
+ True if build succeeded or False on failure.
+ """
+ if not os.path.exists(git_workspace):
+ logging.error('Invalid git workspace: %s.', format(git_workspace))
+ return False
+ if not os.path.exists(out_dir):
+ logging.error('Invalid out directory %s.', format(out_dir))
+ return False
+
+ inferred_url, oss_fuzz_repo_path = build_specified_commit.detect_main_repo(
+ project_name, repo_name=project_repo_name)
+ if not inferred_url or not oss_fuzz_repo_path:
+ logging.error('Could not detect repo from project %s.', project_name)
+ return False
+ src_in_docker = os.path.dirname(oss_fuzz_repo_path)
+ oss_fuzz_repo_name = os.path.basename(oss_fuzz_repo_path)
+
+ # Checkout projects repo in the shared volume.
+ build_repo_manager = repo_manager.RepoManager(inferred_url,
+ git_workspace,
+ repo_name=oss_fuzz_repo_name)
+ try:
+ build_repo_manager.checkout_commit(commit_sha)
+ except repo_manager.RepoManagerError:
+ logging.error('Specified commit does not exist.')
+ # NOTE: Remove return statement for testing.
+ return False
+
+ command = [
+ '--cap-add', 'SYS_PTRACE', '-e', 'FUZZING_ENGINE=libfuzzer', '-e',
+ 'SANITIZER=address', '-e', 'ARCHITECTURE=x86_64'
+ ]
+ container = utils.get_container_name()
+ if container:
+ command += ['-e', 'OUT=' + out_dir, '--volumes-from', container]
+ bash_command = 'rm -rf {0} && cp -r {1} {2} && compile'.format(
+ os.path.join(src_in_docker, oss_fuzz_repo_name, '*'),
+ os.path.join(git_workspace, oss_fuzz_repo_name), src_in_docker)
+ else:
+ command += [
+ '-e', 'OUT=' + '/out', '-v',
+ '%s:%s' % (os.path.join(git_workspace, oss_fuzz_repo_name),
+ os.path.join(src_in_docker, oss_fuzz_repo_name)), '-v',
+ '%s:%s' % (out_dir, '/out')
+ ]
+ bash_command = 'compile'
+
+ command.extend([
+ 'gcr.io/oss-fuzz/' + project_name,
+ '/bin/bash',
+ '-c',
+ ])
+ command.append(bash_command)
+
+ if helper.docker_run(command):
+ logging.error('Building fuzzers failed.')
+ return False
+ return True
+
+
+def run_fuzzers(project_name, fuzz_seconds, out_dir):
+ """Runs all fuzzers for a specific OSS-Fuzz project.
+
+ Args:
+ project_name: The name of the OSS-Fuzz project being built.
+ fuzz_seconds: The total time allotted for fuzzing.
+ out_dir: The location in the shared volume to store output artifacts.
+
+ Returns:
+ (True if run was successful, True if bug was found).
+ """
+ if not out_dir or not os.path.exists(out_dir):
+ logging.error('Unreachable out_dir argument %s.', format(out_dir))
+ return False, False
+
+ if not fuzz_seconds or fuzz_seconds < 1:
+ logging.error('Fuzz_seconds argument must be greater than 1, but was: %s.',
+ format(fuzz_seconds))
+ return False, False
+
+ fuzzer_paths = utils.get_fuzz_targets(out_dir)
+ if not fuzzer_paths:
+ logging.error('No fuzzers were found in out directory: %s.',
+ format(out_dir))
+ return False, False
+
+ fuzz_seconds_per_target = fuzz_seconds // len(fuzzer_paths)
+
+ for fuzzer_path in fuzzer_paths:
+ target = fuzz_target.FuzzTarget(project_name, fuzzer_path,
+ fuzz_seconds_per_target, out_dir)
+ test_case, stack_trace = target.fuzz()
+ if not test_case or not stack_trace:
+ logging.info('Fuzzer %s, finished running.', target.target_name)
+ else:
+ logging.info('Fuzzer %s, detected error: %s.', target.target_name,
+ stack_trace)
+ shutil.move(test_case, os.path.join(out_dir, 'testcase'))
+ return True, True
+ return True, False
diff --git a/infra/cifuzz/cifuzz_test.py b/infra/cifuzz/cifuzz_test.py
new file mode 100644
index 000000000..90c93a2da
--- /dev/null
+++ b/infra/cifuzz/cifuzz_test.py
@@ -0,0 +1,156 @@
+# Copyright 2020 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.
+"""Test the functionality of the cifuzz module's functions:
+1. Building fuzzers.
+2. Running fuzzers.
+"""
+
+import os
+import sys
+import tempfile
+import unittest
+
+# pylint: disable=wrong-import-position
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+import cifuzz
+
+# NOTE: This integration test relies on
+# https://github.com/google/oss-fuzz/tree/master/projects/example project
+EXAMPLE_PROJECT = 'example'
+
+
+class BuildFuzzersIntegrationTest(unittest.TestCase):
+ """Test build_fuzzers function in the utils module."""
+
+ def test_valid(self):
+ """Test building fuzzers with valid inputs."""
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ out_path = os.path.join(tmp_dir, 'out')
+ workspace_path = os.path.join(tmp_dir, 'workspace')
+ os.mkdir(out_path)
+ os.mkdir(workspace_path)
+ self.assertTrue(
+ cifuzz.build_fuzzers(EXAMPLE_PROJECT, 'oss-fuzz',
+ '0b95fe1039ed7c38fea1f97078316bfc1030c523',
+ workspace_path, out_path))
+ self.assertTrue(os.path.exists(os.path.join(out_path, 'do_stuff_fuzzer')))
+
+
+def test_invalid_project_name(self):
+ """Test building fuzzers with invalid project name."""
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ out_path = os.path.join(tmp_dir, 'out')
+ workspace_path = os.path.join(tmp_dir, 'workspace')
+ os.mkdir(out_path)
+ os.mkdir(workspace_path)
+ self.assertFalse(
+ cifuzz.build_fuzzers('not_a_valid_project', 'oss-fuzz',
+ '0b95fe1039ed7c38fea1f97078316bfc1030c523',
+ workspace_path, out_path))
+
+
+def test_invalid_repo_name(self):
+ """Test building fuzzers with invalid repo name."""
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ out_path = os.path.join(tmp_dir, 'out')
+ workspace_path = os.path.join(tmp_dir, 'workspace')
+ os.mkdir(out_path)
+ os.mkdir(workspace_path)
+ self.assertFalse(
+ cifuzz.build_fuzzers(EXAMPLE_PROJECT, 'not-real-repo',
+ '0b95fe1039ed7c38fea1f97078316bfc1030c523',
+ workspace_path, out_path))
+
+
+def test_invalid_commit_sha(self):
+ """Test building fuzzers with invalid commit SHA."""
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ out_path = os.path.join(tmp_dir, 'out')
+ workspace_path = os.path.join(tmp_dir, 'workspace')
+ os.mkdir(out_path)
+ os.mkdir(workspace_path)
+ self.assertFalse(
+ cifuzz.build_fuzzers(EXAMPLE_PROJECT, 'oss-fuzz', '', workspace_path,
+ out_path))
+
+
+def test_invalid_workspace(self):
+ """Test building fuzzers with invalid workspace."""
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ out_path = os.path.join(tmp_dir, 'out')
+ os.mkdir(out_path)
+ self.assertFalse(
+ cifuzz.build_fuzzers(EXAMPLE_PROJECT, 'oss-fuzz',
+ '0b95fe1039ed7c38fea1f97078316bfc1030c523',
+ 'not/a/dir', out_path))
+
+
+def test_invalid_out(self):
+ """Test building fuzzers with invalid out directory."""
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ workspace_path = os.path.join(tmp_dir, 'workspace')
+ os.mkdir(workspace_path)
+ self.assertFalse(
+ cifuzz.build_fuzzers(EXAMPLE_PROJECT, 'oss-fuzz',
+ '0b95fe1039ed7c38fea1f97078316bfc1030c523',
+ workspace_path, 'not/a/dir'))
+
+
+class RunFuzzersIntegrationTest(unittest.TestCase):
+ """Test build_fuzzers function in the utils module."""
+
+ def test_valid(self):
+ """Test run_fuzzers with a valid build."""
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ out_path = os.path.join(tmp_dir, 'out')
+ workspace_path = os.path.join(tmp_dir, 'workspace')
+ os.mkdir(out_path)
+ os.mkdir(workspace_path)
+ self.assertTrue(
+ cifuzz.build_fuzzers(EXAMPLE_PROJECT, 'oss-fuzz',
+ '0b95fe1039ed7c38fea1f97078316bfc1030c523',
+ workspace_path, out_path))
+ self.assertTrue(os.path.exists(os.path.join(out_path, 'do_stuff_fuzzer')))
+ run_success, bug_found = cifuzz.run_fuzzers(EXAMPLE_PROJECT, 5, out_path)
+ self.assertTrue(run_success)
+ self.assertTrue(bug_found)
+
+ def test_invlid_build(self):
+ """Test run_fuzzers with an invalid build."""
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ out_path = os.path.join(tmp_dir, 'out')
+ os.mkdir(out_path)
+ run_success, bug_found = cifuzz.run_fuzzers(EXAMPLE_PROJECT, 5, out_path)
+ self.assertFalse(run_success)
+ self.assertFalse(bug_found)
+
+ def test_invalid_fuzz_seconds(self):
+ """Tests run_fuzzers with an invalid fuzz seconds."""
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ out_path = os.path.join(tmp_dir, 'out')
+ os.mkdir(out_path)
+ run_success, bug_found = cifuzz.run_fuzzers(EXAMPLE_PROJECT, 0, out_path)
+ self.assertFalse(run_success)
+ self.assertFalse(bug_found)
+
+ def test_invalid_out_dir(self):
+ """Tests run_fuzzers with an invalid out directory."""
+ run_success, bug_found = cifuzz.run_fuzzers(EXAMPLE_PROJECT, 5,
+ 'not/a/valid/path')
+ self.assertFalse(run_success)
+ self.assertFalse(bug_found)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/infra/cifuzz/fuzz_target.py b/infra/cifuzz/fuzz_target.py
new file mode 100644
index 000000000..9272bd2c4
--- /dev/null
+++ b/infra/cifuzz/fuzz_target.py
@@ -0,0 +1,108 @@
+# Copyright 2020 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.
+"""A module to handle running a fuzz target for a specified amount of time."""
+import logging
+import os
+import re
+import subprocess
+import sys
+
+# pylint: disable=wrong-import-position
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+import utils
+
+# TODO: Turn default logging to WARNING when CIFuzz is stable
+logging.basicConfig(
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.DEBUG)
+
+
+class FuzzTarget:
+ """A class to manage a single fuzz target.
+
+ Attributes:
+ project_name: The name of the OSS-Fuzz project the target is associated.
+ target_name: The name of the fuzz target.
+ duration: The length of time in seconds that the target should run.
+ target_path: The location of the fuzz target binary.
+ """
+
+ def __init__(self, project_name, target_path, duration, out_dir):
+ """Represents a single fuzz target.
+
+ Args:
+ project_name: The OSS-Fuzz project of this target.
+ target_path: The location of the fuzz target binary.
+ duration: The length of time in seconds the target should run.
+ out_dir: The location of where the output from crashes should be stored.
+ """
+ self.target_name = os.path.basename(target_path)
+ self.duration = duration
+ self.project_name = project_name
+ self.target_path = target_path
+ self.out_dir = out_dir
+
+ def fuzz(self):
+ """Starts the fuzz target run for the length of time specified by duration.
+
+ Returns:
+ (test_case, stack trace) if found or (None, None) on timeout or error.
+ """
+ logging.info('Fuzzer %s, started.', self.target_name)
+ docker_container = utils.get_container_name()
+ command = ['docker', 'run', '--rm', '--privileged']
+ if docker_container:
+ command += [
+ '--volumes-from', docker_container, '-e', 'OUT=' + self.out_dir
+ ]
+ else:
+ command += ['-v', '%s:%s' % (self.out_dir, '/out')]
+
+ command += [
+ '-e', 'FUZZING_ENGINE=libfuzzer', '-e', 'SANITIZER=address', '-e',
+ 'RUN_FUZZER_MODE=interactive', 'gcr.io/oss-fuzz-base/base-runner',
+ 'bash', '-c', 'run_fuzzer {0}'.format(self.target_name)
+ ]
+ logging.info('Running command: %s', ' '.join(command))
+ process = subprocess.Popen(command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+
+ try:
+ _, err = process.communicate(timeout=self.duration)
+ except subprocess.TimeoutExpired:
+ logging.info('Fuzzer %s, finished with timeout.', self.target_name)
+ return None, None
+
+ logging.info('Fuzzer %s, ended before timeout.', self.target_name)
+ err_str = err.decode('ascii')
+ test_case = self.get_test_case(err_str)
+ if not test_case:
+ logging.error('No test case found in stack trace.', file=sys.stderr)
+ return None, None
+ return test_case, err_str
+
+ def get_test_case(self, error_string):
+ """Gets the file from a fuzzer run stack trace.
+
+ Args:
+ error_string: The stack trace string containing the error.
+
+ Returns:
+ The error test case or None if not found.
+ """
+ match = re.search(r'\bTest unit written to \.\/([^\s]+)', error_string)
+ if match:
+ return os.path.join(self.out_dir, match.group(1))
+ return None
diff --git a/infra/repo_manager.py b/infra/repo_manager.py
index b13b91fc5..6c010a478 100644
--- a/infra/repo_manager.py
+++ b/infra/repo_manager.py
@@ -31,7 +31,7 @@ class RepoManagerError(Exception):
"""Class to describe the exceptions in RepoManager."""
-class RepoManager(object):
+class RepoManager:
"""Class to manage git repos from python.
Attributes:
@@ -54,7 +54,7 @@ class RepoManager(object):
if repo_name:
self.repo_name = repo_name
else:
- self.repo_name = self.repo_url.split('/')[-1].strip('.git')
+ self.repo_name = os.path.basename(self.repo_url).strip('.git')
self.repo_dir = os.path.join(self.base_dir, self.repo_name)
self._clone()
@@ -68,8 +68,7 @@ class RepoManager(object):
os.makedirs(self.base_dir)
self.remove_repo()
out, err = build_specified_commit.execute(
- ['git', 'clone', self.repo_url],
- location=self.base_dir)
+ ['git', 'clone', self.repo_url, self.repo_name], location=self.base_dir)
if not self._is_git_repo():
raise RepoManagerError('%s is not a git repo' % self.repo_url)
@@ -98,7 +97,7 @@ class RepoManager(object):
# Handle the exception case, if empty string is passed execute will
# raise a ValueError
if not commit.rstrip():
- raise ValueError('An empty string is not a valid commit SHA')
+ raise RepoManagerError('An empty string is not a valid commit SHA')
_, err_code = build_specified_commit.execute(
['git', 'cat-file', '-e', commit], self.repo_dir)
@@ -111,8 +110,8 @@ class RepoManager(object):
The current active commit SHA
"""
out, _ = build_specified_commit.execute(['git', 'rev-parse', 'HEAD'],
- self.repo_dir,
- check_result=True)
+ self.repo_dir,
+ check_result=True)
return out.strip('\n')
def get_commit_list(self, old_commit, new_commit):
@@ -163,14 +162,14 @@ class RepoManager(object):
git_path = os.path.join(self.repo_dir, '.git', 'shallow')
if os.path.exists(git_path):
build_specified_commit.execute(['git', 'fetch', '--unshallow'],
- self.repo_dir,
- check_result=True)
+ self.repo_dir,
+ check_result=True)
build_specified_commit.execute(['git', 'checkout', '-f', commit],
- self.repo_dir,
- check_result=True)
+ self.repo_dir,
+ check_result=True)
build_specified_commit.execute(['git', 'clean', '-fxd'],
- self.repo_dir,
- check_result=True)
+ self.repo_dir,
+ check_result=True)
if self.get_current_commit() != commit:
raise RepoManagerError('Error checking out commit %s' % commit)
diff --git a/infra/repo_manager_test.py b/infra/repo_manager_test.py
index 1c271cf5b..79d1b40c8 100644
--- a/infra/repo_manager_test.py
+++ b/infra/repo_manager_test.py
@@ -48,7 +48,7 @@ class TestRepoManager(unittest.TestCase):
commit_to_test = '036ebac0134de3b72052a46f734e4ca81bb96055'
test_repo_manager.checkout_commit(commit_to_test)
self.assertEqual(commit_to_test, test_repo_manager.get_current_commit())
- with self.assertRaises(ValueError):
+ with self.assertRaises(repo_manager.RepoManagerError):
test_repo_manager.checkout_commit(' ')
with self.assertRaises(repo_manager.RepoManagerError):
test_repo_manager.checkout_commit(
@@ -75,6 +75,7 @@ class TestRepoManager(unittest.TestCase):
test_repo_manager.get_commit_list(new_commit, 'asdfasdf')
with self.assertRaises(repo_manager.RepoManagerError):
# Testing commits out of order
+ # pylint: disable=arguments-out-of-order
test_repo_manager.get_commit_list(new_commit, old_commit)
diff --git a/infra/utils.py b/infra/utils.py
new file mode 100644
index 000000000..c2820c38b
--- /dev/null
+++ b/infra/utils.py
@@ -0,0 +1,94 @@
+# Copyright 2020 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.
+"""Utilities for OSS-Fuzz infrastructure."""
+
+import os
+import re
+import stat
+
+import helper
+
+ALLOWED_FUZZ_TARGET_EXTENSIONS = ['', '.exe']
+FUZZ_TARGET_SEARCH_STRING = 'LLVMFuzzerTestOneInput'
+VALID_TARGET_NAME = re.compile(r'^[a-zA-Z0-9_-]+$')
+
+
+def chdir_to_root():
+ """Changes cwd to OSS-Fuzz root directory."""
+ # Change to oss-fuzz main directory so helper.py runs correctly.
+ if os.getcwd() != helper.OSSFUZZ_DIR:
+ os.chdir(helper.OSSFUZZ_DIR)
+
+
+def is_fuzz_target_local(file_path):
+ """Returns whether |file_path| is a fuzz target binary (local path).
+ Copied from clusterfuzz src/python/bot/fuzzers/utils.py
+ with slight modifications.
+ """
+ filename, file_extension = os.path.splitext(os.path.basename(file_path))
+ if not VALID_TARGET_NAME.match(filename):
+ # Check fuzz target has a valid name (without any special chars).
+ return False
+
+ if file_extension not in ALLOWED_FUZZ_TARGET_EXTENSIONS:
+ # Ignore files with disallowed extensions (to prevent opening e.g. .zips).
+ return False
+
+ if not os.path.exists(file_path) or not os.access(file_path, os.X_OK):
+ return False
+
+ if filename.endswith('_fuzzer'):
+ return True
+
+ if os.path.exists(file_path) and not stat.S_ISREG(os.stat(file_path).st_mode):
+ return False
+
+ with open(file_path, 'rb') as file_handle:
+ return file_handle.read().find(FUZZ_TARGET_SEARCH_STRING.encode()) != -1
+
+
+def get_fuzz_targets(path):
+ """Get list of fuzz targets in a directory.
+
+ Args:
+ path: A path to search for fuzz targets in.
+
+ Returns:
+ A list of paths to fuzzers or an empty list if None.
+ """
+ if not os.path.exists(path):
+ return []
+ fuzz_target_paths = []
+ for root, _, _ in os.walk(path):
+ for filename in os.listdir(path):
+ file_path = os.path.join(root, filename)
+ if is_fuzz_target_local(file_path):
+ fuzz_target_paths.append(file_path)
+
+ return fuzz_target_paths
+
+
+def get_container_name():
+ """Gets the name of the current docker container you are in.
+ /proc/self/cgroup can be used to check control groups e.g. Docker.
+ See: https://docs.docker.com/config/containers/runmetrics/ for more info.
+
+ Returns:
+ Container name or None if not in a container.
+ """
+ with open('/proc/self/cgroup') as file_handle:
+ if 'docker' not in file_handle.read():
+ return None
+ with open('/etc/hostname') as file_handle:
+ return file_handle.read().strip()
diff --git a/infra/utils_test.py b/infra/utils_test.py
new file mode 100644
index 000000000..ab3d216fb
--- /dev/null
+++ b/infra/utils_test.py
@@ -0,0 +1,102 @@
+# Copyright 2020 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.
+"""Test the functionality of the utils module's functions:
+1. is_fuzz_target_local
+2. get_fuzz_targets
+3. get_env_var
+"""
+
+import os
+import unittest
+
+import utils
+import helper
+
+EXAMPLE_PROJECT = 'example'
+
+
+class IsFuzzTargetLocalUnitTest(unittest.TestCase):
+ """Test is_fuzz_target_local function in the utils module."""
+
+ def test_invalid_filepath(self):
+ """Test the function with an invalid file path."""
+ is_local = utils.is_fuzz_target_local('not/a/real/file')
+ self.assertFalse(is_local)
+ is_local = utils.is_fuzz_target_local('')
+ self.assertFalse(is_local)
+ is_local = utils.is_fuzz_target_local(' ')
+ self.assertFalse(is_local)
+
+ def test_valid_filepath(self):
+ """Checks is_fuzz_target_local function with a valid filepath."""
+ utils.chdir_to_root()
+ helper.build_fuzzers_impl(EXAMPLE_PROJECT,
+ True,
+ 'libfuzzer',
+ 'address',
+ 'x86_64', [],
+ None,
+ no_cache=False,
+ mount_location=None)
+ is_local = utils.is_fuzz_target_local(
+ os.path.join(helper.OSSFUZZ_DIR, 'build', 'out', EXAMPLE_PROJECT,
+ 'do_stuff_fuzzer'))
+ self.assertTrue(is_local)
+ is_local = utils.is_fuzz_target_local(
+ os.path.join(helper.OSSFUZZ_DIR, 'build', 'out', EXAMPLE_PROJECT,
+ 'do_stuff_fuzzer.dict'))
+ self.assertFalse(is_local)
+
+
+class GetFuzzTargetsUnitTest(unittest.TestCase):
+ """Test get_fuzz_targets function in the utils module."""
+
+ def test_valid_filepath(self):
+ """Tests that fuzz targets can be retrieved once the fuzzers are built."""
+ utils.chdir_to_root()
+ helper.build_fuzzers_impl(EXAMPLE_PROJECT,
+ True,
+ 'libfuzzer',
+ 'address',
+ 'x86_64', [],
+ None,
+ no_cache=False,
+ mount_location=None)
+ fuzz_targets = utils.get_fuzz_targets(
+ os.path.join(helper.OSSFUZZ_DIR, 'build', 'out', EXAMPLE_PROJECT))
+ self.assertCountEqual(fuzz_targets, [
+ os.path.join(helper.OSSFUZZ_DIR, 'build', 'out', EXAMPLE_PROJECT,
+ 'do_stuff_fuzzer')
+ ])
+ fuzz_targets = utils.get_fuzz_targets(
+ os.path.join(helper.OSSFUZZ_DIR, 'infra'))
+ self.assertFalse(fuzz_targets)
+
+ def test_invalid_filepath(self):
+ """Tests what get_fuzz_targets return when invalid filepath is used."""
+ utils.chdir_to_root()
+ helper.build_fuzzers_impl(EXAMPLE_PROJECT,
+ True,
+ 'libfuzzer',
+ 'address',
+ 'x86_64', [],
+ None,
+ no_cache=False,
+ mount_location=None)
+ fuzz_targets = utils.get_fuzz_targets('not/a/valid/file/path')
+ self.assertFalse(fuzz_targets)
+
+
+if __name__ == '__main__':
+ unittest.main()