mirror of https://github.com/google/oss-fuzz.git
[CIFuzz] Separate build and run actions (#3336)
* Separating actions into build and run * Formatting * Formatting * Removing project-name from run action * Jonathan comments * Maxs comments pt.1 * Updating example_main.yml * Switch 1 0 to true false * Add base Dockerfile for CIFuzz * Adding license to dockerfile
This commit is contained in:
parent
69f75431fe
commit
5cefaac000
infra/cifuzz
|
@ -0,0 +1,24 @@
|
|||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
# Docker image to run CIFuzz in.
|
||||
|
||||
FROM gcr.io/oss-fuzz-base/cifuzz-base
|
||||
|
||||
# Copies your code file from action repository to the container
|
||||
COPY build_fuzzers_entrypoint.py /opt/build_fuzzers_entrypoint.py
|
||||
|
||||
# Python file to execute when the docker container starts up
|
||||
ENTRYPOINT ["python3", "/opt/entrypoint.py"]
|
|
@ -5,10 +5,6 @@ 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
|
||||
dry-run:
|
||||
description: 'If set, run the action without actually reporting a failure.'
|
||||
default: false
|
||||
|
@ -17,5 +13,4 @@ runs:
|
|||
image: 'Dockerfile'
|
||||
env:
|
||||
PROJECT_NAME: ${{ inputs.project-name }}
|
||||
FUZZ_SECONDS: ${{ inputs.fuzz-seconds }}
|
||||
DRY_RUN: ${{ inputs.dry-run}}
|
|
@ -28,24 +28,26 @@ logging.basicConfig(
|
|||
|
||||
|
||||
def main():
|
||||
"""Runs OSS-Fuzz project's fuzzers for CI tools.
|
||||
"""Build 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
|
||||
entrypoint of the Dockerfile in this directory. This action can be added to
|
||||
any OSS-Fuzz project's workflow that uses Github.
|
||||
|
||||
Note: The resulting clusterfuzz binaries of this build are placed in
|
||||
the directory: ${GITHUB_WORKSPACE}/out
|
||||
|
||||
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.
|
||||
GITHUB_REF: The pull request reference that triggered this script.
|
||||
GITHUB_EVENT_NAME: The name of the hook event that triggered this script.
|
||||
GITHUB_WORKSPACE: The shared volume directory where input artifacts are.
|
||||
|
||||
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'))
|
||||
pr_ref = os.environ.get('GITHUB_REF')
|
||||
commit_sha = os.environ.get('GITHUB_SHA')
|
||||
|
@ -58,16 +60,6 @@ def main():
|
|||
# The default return code when an error occurs.
|
||||
error_code = 1
|
||||
if dry_run:
|
||||
# A testcase file is required in order for CIFuzz to surface bugs.
|
||||
# If the file does not exist, the action will crash attempting to upload it.
|
||||
# The dry run needs this file because it is set to upload a test case both
|
||||
# on successful runs and on failures.
|
||||
out_dir = os.path.join(workspace, 'out')
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
file_handle = open(os.path.join(out_dir, 'testcase'), 'w')
|
||||
file_handle.write('No bugs detected.')
|
||||
file_handle.close()
|
||||
|
||||
# Sets the default return code on error to success.
|
||||
error_code = 0
|
||||
|
||||
|
@ -86,19 +78,6 @@ def main():
|
|||
logging.error('Error building fuzzers for project %s with pull request %s.',
|
||||
oss_fuzz_project_name, pr_ref)
|
||||
return error_code
|
||||
|
||||
# Run the specified project's fuzzers from the build.
|
||||
run_status, bug_found = cifuzz.run_fuzzers(oss_fuzz_project_name,
|
||||
fuzz_seconds, workspace)
|
||||
if not run_status:
|
||||
logging.error('Error occured while running fuzzers for project %s.',
|
||||
oss_fuzz_project_name)
|
||||
return error_code
|
||||
if bug_found:
|
||||
logging.info('Bug found.')
|
||||
if not dry_run:
|
||||
# Return 2 when a bug was found by a fuzzer causing the CI to fail.
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
# Docker image to run CIFuzz run fuzzers action in.
|
||||
|
||||
FROM gcr.io/oss-fuzz-base/cifuzz-base
|
||||
|
||||
# Copies your code file from action repository to the container
|
||||
COPY run_fuzzers_entrypoint.py /opt/run_fuzzers_entrypoint.py
|
||||
|
||||
# Python file to execute when the docker container starts up
|
||||
ENTRYPOINT ["python3", "/opt/run_fuzzers_entrypoint.py"]
|
|
@ -0,0 +1,17 @@
|
|||
# action.yml
|
||||
name: 'run-fuzzers'
|
||||
description: 'Runs fuzz target binaries for a specified length of time.'
|
||||
inputs:
|
||||
fuzz-seconds:
|
||||
description: 'The total time allotted for fuzzing in seconds.'
|
||||
required: true
|
||||
default: 600
|
||||
dry-run:
|
||||
description: 'If set, run the action without actually reporting a failure.'
|
||||
default: false
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
env:
|
||||
FUZZ_SECONDS: ${{ inputs.fuzz-seconds }}
|
||||
DRY_RUN: ${{ inputs.dry-run}}
|
|
@ -0,0 +1,92 @@
|
|||
# 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.
|
||||
"""Runs specific OSS-Fuzz project's fuzzers for CI tools."""
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
# pylint: disable=import-error
|
||||
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.
|
||||
This is the entrypoint for the run_fuzzers github action.
|
||||
This action can be added to any OSS-Fuzz project's workflow that uses Github.
|
||||
|
||||
NOTE: libfuzzer binaries must be located in the ${GITHUB_WORKSPACE}/out
|
||||
directory in order for this action to be used. This action will only fuzz the
|
||||
binary's that are located in that directory. It is reccomended that you add
|
||||
the build_fuzzers action preceding this one.
|
||||
|
||||
NOTE: Any crash report will be in the filepath:
|
||||
${GITHUB_WORKSPACE}/out/testcase
|
||||
This can be used in parallel with the upload-artifact action to surface the
|
||||
logs.
|
||||
|
||||
Required environment variables:
|
||||
FUZZ_SECONDS: The length of time in seconds that fuzzers are to be run.
|
||||
GITHUB_WORKSPACE: The shared volume directory where input artifacts are.
|
||||
DRY_RUN: If true, no failures will surface.
|
||||
|
||||
Returns:
|
||||
0 on success or 1 on Failure.
|
||||
"""
|
||||
fuzz_seconds = int(os.environ.get('FUZZ_SECONDS', 600))
|
||||
workspace = os.environ.get('GITHUB_WORKSPACE')
|
||||
|
||||
# Check if failures should not be reported.
|
||||
dry_run = (os.environ.get('DRY_RUN').lower() == 'true')
|
||||
|
||||
# The default return code when an error occurs.
|
||||
error_code = 1
|
||||
if dry_run:
|
||||
# A testcase file is required in order for CIFuzz to surface bugs.
|
||||
# If the file does not exist, the action will crash attempting to upload it.
|
||||
# The dry run needs this file because it is set to upload a test case both
|
||||
# on successful runs and on failures.
|
||||
out_dir = os.path.join(workspace, 'out')
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
file_handle = open(os.path.join(out_dir, 'testcase'), 'w')
|
||||
file_handle.write('No bugs detected.')
|
||||
file_handle.close()
|
||||
|
||||
# Sets the default return code on error to success.
|
||||
error_code = 0
|
||||
|
||||
if not workspace:
|
||||
logging.error('This script needs to be run in the Github action context.')
|
||||
return error_code
|
||||
# Run the specified project's fuzzers from the build.
|
||||
run_status, bug_found = cifuzz.run_fuzzers(fuzz_seconds, workspace)
|
||||
if not run_status:
|
||||
logging.error('Error occured while running in workspace %s.', workspace)
|
||||
return error_code
|
||||
if bug_found:
|
||||
logging.info('Bug found.')
|
||||
if not dry_run:
|
||||
# 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())
|
|
@ -13,7 +13,6 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
################################################################################
|
||||
# Docker image to run CIFuzz in.
|
||||
|
||||
FROM ubuntu:16.04
|
||||
|
||||
|
@ -36,9 +35,3 @@ RUN apt-get update && apt-get install docker-ce docker-ce-cli containerd.io -y
|
|||
|
||||
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
|
||||
|
||||
# Command to execute when the docker container starts up
|
||||
ENTRYPOINT ["python3", "/opt/entrypoint.py"]
|
|
@ -26,6 +26,7 @@ import sys
|
|||
import fuzz_target
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
# pylint: disable=import-error
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import build_specified_commit
|
||||
import helper
|
||||
|
@ -124,11 +125,10 @@ def build_fuzzers(project_name,
|
|||
return True
|
||||
|
||||
|
||||
def run_fuzzers(project_name, fuzz_seconds, workspace):
|
||||
def run_fuzzers(fuzz_seconds, workspace):
|
||||
"""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.
|
||||
workspace: The location in a shared volume to store a git repo and build
|
||||
artifacts.
|
||||
|
@ -156,8 +156,8 @@ def run_fuzzers(project_name, fuzz_seconds, workspace):
|
|||
|
||||
# Run fuzzers for alotted time.
|
||||
for fuzzer_path in fuzzer_paths:
|
||||
target = fuzz_target.FuzzTarget(project_name, fuzzer_path,
|
||||
fuzz_seconds_per_target, out_dir)
|
||||
target = fuzz_target.FuzzTarget(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)
|
||||
|
|
|
@ -124,7 +124,7 @@ class RunFuzzersIntegrationTest(unittest.TestCase):
|
|||
tmp_dir,
|
||||
commit_sha='0b95fe1039ed7c38fea1f97078316bfc1030c523'))
|
||||
self.assertTrue(os.path.exists(os.path.join(out_path, 'do_stuff_fuzzer')))
|
||||
run_success, bug_found = cifuzz.run_fuzzers(EXAMPLE_PROJECT, 5, tmp_dir)
|
||||
run_success, bug_found = cifuzz.run_fuzzers(5, tmp_dir)
|
||||
self.assertTrue(run_success)
|
||||
self.assertTrue(bug_found)
|
||||
|
||||
|
@ -133,7 +133,7 @@ class RunFuzzersIntegrationTest(unittest.TestCase):
|
|||
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, tmp_dir)
|
||||
run_success, bug_found = cifuzz.run_fuzzers(5, tmp_dir)
|
||||
self.assertFalse(run_success)
|
||||
self.assertFalse(bug_found)
|
||||
|
||||
|
@ -142,14 +142,13 @@ class RunFuzzersIntegrationTest(unittest.TestCase):
|
|||
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, tmp_dir)
|
||||
run_success, bug_found = cifuzz.run_fuzzers(0, tmp_dir)
|
||||
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')
|
||||
run_success, bug_found = cifuzz.run_fuzzers(5, 'not/a/valid/path')
|
||||
self.assertFalse(run_success)
|
||||
self.assertFalse(bug_found)
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
name: CIFuzz
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
Fuzzing:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Build Fuzzers
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||
with:
|
||||
project-name: 'example'
|
||||
dry-run: false
|
||||
- name: Run Fuzzers
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
||||
with:
|
||||
fuzz-time: 600
|
||||
dry-run: false
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v1
|
||||
if: failure()
|
||||
with:
|
||||
name: fuzzer_testcase
|
||||
path: ./out/testcase
|
|
@ -35,24 +35,21 @@ 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):
|
||||
def __init__(self, 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
|
||||
|
||||
|
|
Loading…
Reference in New Issue