From 2fa71e3c7f87497dc31a9b236175c2173756fc99 Mon Sep 17 00:00:00 2001 From: Dongge Liu Date: Fri, 16 Sep 2022 09:25:49 +1000 Subject: [PATCH] Centipede's CI build, trial build, and build tests (#8422) Adding CI build, trial build, and build tests. Co-authored-by: Oliver Chang --- infra/base-images/base-runner/bad_build_check | 11 +- infra/base-images/base-runner/run_fuzzer | 12 +- infra/build/functions/build_lib.py | 2 +- infra/build/functions/build_project.py | 11 + infra/build/functions/build_project_test.py | 36 ++ .../expected_centipede_build_steps.json | 308 ++++++++++++++++++ infra/build/functions/trial_build.py | 2 +- infra/ci/build_test.py | 30 +- infra/helper.py | 12 +- 9 files changed, 407 insertions(+), 17 deletions(-) create mode 100644 infra/build/functions/test_data/expected_centipede_build_steps.json diff --git a/infra/base-images/base-runner/bad_build_check b/infra/base-images/base-runner/bad_build_check index 5c78071e8..5156f17c4 100755 --- a/infra/base-images/base-runner/bad_build_check +++ b/infra/base-images/base-runner/bad_build_check @@ -114,7 +114,16 @@ function check_engine { cat $FUZZER_OUTPUT return 1 fi - return 0 + elif [[ "$FUZZING_ENGINE" == centipede && "$SANITIZER" == "none" ]]; then + # Only perform run test on unsanitized binary for now. + # TODO(Dongge): Support run test for sanitized binaries as well. + timeout --preserve-status -s INT 20s run_fuzzer $FUZZER_NAME &>$FUZZER_OUTPUT + CHECK_PASSED=$(egrep "\[0] begin-fuzz: ft: 0 cov: 0" -c $FUZZER_OUTPUT) + if (( $CHECK_PASSED == 0 )); then + echo "BAD BUILD: fuzzing $FUZZER with centipede failed." + cat $FUZZER_OUTPUT + return 1 + fi fi # TODO: add checks for other fuzzing engines if possible. diff --git a/infra/base-images/base-runner/run_fuzzer b/infra/base-images/base-runner/run_fuzzer index 7eb5d46d3..3b1bdc9c2 100755 --- a/infra/base-images/base-runner/run_fuzzer +++ b/infra/base-images/base-runner/run_fuzzer @@ -72,6 +72,16 @@ function get_dictionary() { fi } +function get_extra_binaries() { + [[ "$FUZZING_ENGINE" != "centipede" ]] && return + + extra_binaries="$OUT/**_${SANITIZER}/${FUZZER}" + if compgen -G "$extra_binaries" >> /dev/null; then + printf -- "--extra_binaries %s" \""$extra_binaries\"" + fi + +} + rm -rf $FUZZER_OUT && mkdir -p $FUZZER_OUT SEED_CORPUS="${FUZZER}_seed_corpus.zip" @@ -177,7 +187,7 @@ elif [[ "$FUZZING_ENGINE" = centipede ]]; then # --address_space_limit_mb=0: No address space limit. # --binary: The target binary under test without sanitizer. # --extra_binary: The target binaries under test with sanitizers. - CMD_LINE="$OUT/centipede --workdir=\"$OUT/workdir\" --corpus_dir=\"$CORPUS_DIR\" --fork_server=1 --exit_on_crash=1 --timeout=1200 --rss_limit_mb=4096 --address_space_limit_mb=0 $(get_dictionary) --binary=\"$OUT/${FUZZER}\" --extra_binaries=\"$OUT/**_${SANITIZER}/${FUZZER}\" $*" + CMD_LINE="$OUT/centipede --workdir=\"$OUT/workdir\" --corpus_dir=\"$CORPUS_DIR\" --fork_server=1 --exit_on_crash=1 --timeout=1200 --rss_limit_mb=4096 --address_space_limit_mb=0 $(get_dictionary) --binary=\"$OUT/${FUZZER}\" $(get_extra_binaries) $*" else CMD_LINE="$OUT/$FUZZER $FUZZER_ARGS $*" diff --git a/infra/build/functions/build_lib.py b/infra/build/functions/build_lib.py index fd10103bd..065a30e72 100644 --- a/infra/build/functions/build_lib.py +++ b/infra/build/functions/build_lib.py @@ -75,7 +75,7 @@ ENGINE_INFO = { supported_architectures=['x86_64']), 'centipede': EngineInfo(upload_bucket='clusterfuzz-builds-centipede', - supported_sanitizers=['address'], + supported_sanitizers=['address', 'none'], supported_architectures=['x86_64']), } diff --git a/infra/build/functions/build_project.py b/infra/build/functions/build_project.py index a67c13c51..18f3d69aa 100755 --- a/infra/build/functions/build_project.py +++ b/infra/build/functions/build_project.py @@ -115,6 +115,14 @@ def get_sanitizer_strings(sanitizers): return processed_sanitizers +def set_default_sanitizer_for_centipede(project_yaml): + """Adds none as a sanitizer for centipede in yaml if it does not exist yet.""" + # Centipede requires a separate unsanitized binary to use sanitized ones. + if ('centipede' in project_yaml['fuzzing_engines'] and + project_yaml['sanitizers'] and 'none' not in project_yaml['sanitizers']): + project_yaml['sanitizers'].append('none') + + class Project: # pylint: disable=too-many-instance-attributes """Class representing an OSS-Fuzz project.""" @@ -164,6 +172,9 @@ def set_yaml_defaults(project_yaml): project_yaml.setdefault('run_tests', True) project_yaml.setdefault('coverage_extra_args', '') project_yaml.setdefault('labels', {}) + # Adds 'none' as a sanitizer for centipede to the project yaml by default, + # because Centipede always requires a separate build of unsanitized binary. + set_default_sanitizer_for_centipede(project_yaml) def is_supported_configuration(build): diff --git a/infra/build/functions/build_project_test.py b/infra/build/functions/build_project_test.py index ae8ff5ae3..33961c1e3 100644 --- a/infra/build/functions/build_project_test.py +++ b/infra/build/functions/build_project_test.py @@ -75,6 +75,42 @@ class TestRequestCoverageBuilds(fake_filesystem_unittest.TestCase): config) self.assertEqual(build_steps, expected_build_steps) + @mock.patch('build_lib.get_signed_url', return_value='test_url') + @mock.patch('build_project.get_datetime_now', + return_value=test_utils.FAKE_DATETIME) + def test_get_centipede_build_steps(self, mock_url, mock_get_datetime_now): + """Test for get_build_steps of centipede.""" + del mock_url, mock_get_datetime_now + # The none sanitizer should be added automatically when other sanitizers are + # specified by the users. + project_yaml_contents = ( + 'language: c++\n' + 'fuzzing_engines:\n' + ' - centipede\n' + 'sanitizers:\n' + ' - address\n' + 'architectures:\n' + ' - x86_64\n' + 'main_repo: https://github.com/google/centipede.git\n') + self.fs.create_dir(test_utils.PROJECT_DIR) + test_utils.create_project_data(test_utils.PROJECT, project_yaml_contents) + + expected_build_steps_file_path = test_utils.get_test_data_file_path( + 'expected_centipede_build_steps.json') + self.fs.add_real_file(expected_build_steps_file_path) + with open(expected_build_steps_file_path) as expected_build_steps_file: + expected_build_steps = json.load(expected_build_steps_file) + + config = build_project.Config(False, False, None, False, True) + project_yaml, dockerfile = build_project.get_project_data( + test_utils.PROJECT) + build_steps = build_project.get_build_steps(test_utils.PROJECT, + project_yaml, dockerfile, + test_utils.IMAGE_PROJECT, + test_utils.BASE_IMAGES_PROJECT, + config) + self.assertEqual(build_steps, expected_build_steps) + if __name__ == '__main__': unittest.main(exit=False) diff --git a/infra/build/functions/test_data/expected_centipede_build_steps.json b/infra/build/functions/test_data/expected_centipede_build_steps.json new file mode 100644 index 000000000..e4c8c7213 --- /dev/null +++ b/infra/build/functions/test_data/expected_centipede_build_steps.json @@ -0,0 +1,308 @@ +[ + { + "args": [ + "clone", + "https://github.com/google/oss-fuzz.git", + "--depth", + "1" + ], + "name": "gcr.io/cloud-builders/git" + }, + { + "name": "gcr.io/cloud-builders/docker", + "args": [ + "build", + "--tag", + "gcr.io/oss-fuzz/test-project", + "." + ], + "dir": "oss-fuzz/projects/test-project" + }, + { + "name": "gcr.io/oss-fuzz/test-project", + "args": [ + "bash", + "-c", + "srcmap > /workspace/srcmap.json && cat /workspace/srcmap.json" + ], + "env": [ + "OSSFUZZ_REVISION=$REVISION_ID", + "FUZZING_LANGUAGE=c++" + ], + "id": "srcmap" + }, + { + "name": "gcr.io/cloud-builders/docker", + "env": [ + "ARCHITECTURE=x86_64", + "FUZZING_ENGINE=centipede", + "FUZZING_LANGUAGE=c++", + "HOME=/root", + "OUT=/workspace/out/centipede-address-x86_64", + "SANITIZER=address" + ], + "args": [ + "run", + "--platform", + "linux/amd64", + "-v", + "/workspace:/workspace", + "-e", + "ARCHITECTURE=x86_64", + "-e", + "FUZZING_ENGINE=centipede", + "-e", + "FUZZING_LANGUAGE=c++", + "-e", + "HOME=/root", + "-e", + "OUT=/workspace/out/centipede-address-x86_64", + "-e", + "SANITIZER=address", + "-t", + "gcr.io/oss-fuzz/test-project", + "bash", + "-c", + "rm -r /out && cd /src && cd /src && mkdir -p /workspace/out/centipede-address-x86_64 && compile || (echo \"********************************************************************************\nFailed to build.\nTo reproduce, run:\npython infra/helper.py build_image test-project\npython infra/helper.py build_fuzzers --sanitizer address --engine centipede --architecture x86_64 test-project\n********************************************************************************\" && false)" + ], + "id": "compile-centipede-address-x86_64" + }, + { + "name": "gcr.io/cloud-builders/docker", + "env": [ + "ARCHITECTURE=x86_64", + "FUZZING_ENGINE=centipede", + "FUZZING_LANGUAGE=c++", + "HOME=/root", + "OUT=/workspace/out/centipede-address-x86_64", + "SANITIZER=address" + ], + "args": [ + "run", + "--platform", + "linux/amd64", + "-v", + "/workspace:/workspace", + "-e", + "ARCHITECTURE=x86_64", + "-e", + "FUZZING_ENGINE=centipede", + "-e", + "FUZZING_LANGUAGE=c++", + "-e", + "HOME=/root", + "-e", + "OUT=/workspace/out/centipede-address-x86_64", + "-e", + "SANITIZER=address", + "-t", + "gcr.io/oss-fuzz-base/base-runner", + "bash", + "-c", + "test_all.py || (echo \"********************************************************************************\nBuild checks failed.\nTo reproduce, run:\npython infra/helper.py build_image test-project\npython infra/helper.py build_fuzzers --sanitizer address --engine centipede --architecture x86_64 test-project\npython infra/helper.py check_build --sanitizer address --engine centipede --architecture x86_64 test-project\n********************************************************************************\" && false)" + ], + "id": "build-check-centipede-address-x86_64" + }, + { + "name": "gcr.io/oss-fuzz-base/base-runner", + "env": [ + "ARCHITECTURE=x86_64", + "FUZZING_ENGINE=centipede", + "FUZZING_LANGUAGE=c++", + "HOME=/root", + "OUT=/workspace/out/centipede-address-x86_64", + "SANITIZER=address" + ], + "args": [ + "bash", + "-c", + "targets_list > /workspace/targets.list.address" + ] + }, + { + "name": "gcr.io/oss-fuzz/test-project", + "args": [ + "bash", + "-c", + "cd /workspace/out/centipede-address-x86_64 && zip -r test-project-address-202001010000.zip *" + ] + }, + { + "name": "gcr.io/oss-fuzz-base/uploader", + "args": [ + "/workspace/srcmap.json", + "test_url" + ] + }, + { + "name": "gcr.io/oss-fuzz-base/uploader", + "args": [ + "/workspace/out/centipede-address-x86_64/test-project-address-202001010000.zip", + "test_url" + ] + }, + { + "name": "gcr.io/oss-fuzz-base/uploader", + "args": [ + "/workspace/targets.list.address", + "test_url" + ] + }, + { + "name": "gcr.io/cloud-builders/curl", + "args": [ + "-H", + "Content-Type: text/plain", + "-X", + "PUT", + "-d", + "test-project-address-202001010000.zip", + "test_url" + ] + }, + { + "name": "gcr.io/oss-fuzz/test-project", + "args": [ + "bash", + "-c", + "rm -r /workspace/out/centipede-address-x86_64" + ] + }, + { + "name": "gcr.io/cloud-builders/docker", + "env": [ + "ARCHITECTURE=x86_64", + "FUZZING_ENGINE=centipede", + "FUZZING_LANGUAGE=c++", + "HOME=/root", + "OUT=/workspace/out/centipede-none-x86_64", + "SANITIZER=none" + ], + "args": [ + "run", + "--platform", + "linux/amd64", + "-v", + "/workspace:/workspace", + "-e", + "ARCHITECTURE=x86_64", + "-e", + "FUZZING_ENGINE=centipede", + "-e", + "FUZZING_LANGUAGE=c++", + "-e", + "HOME=/root", + "-e", + "OUT=/workspace/out/centipede-none-x86_64", + "-e", + "SANITIZER=none", + "-t", + "gcr.io/oss-fuzz/test-project", + "bash", + "-c", + "rm -r /out && cd /src && cd /src && mkdir -p /workspace/out/centipede-none-x86_64 && compile || (echo \"********************************************************************************\nFailed to build.\nTo reproduce, run:\npython infra/helper.py build_image test-project\npython infra/helper.py build_fuzzers --sanitizer none --engine centipede --architecture x86_64 test-project\n********************************************************************************\" && false)" + ], + "id": "compile-centipede-none-x86_64" + }, + { + "name": "gcr.io/cloud-builders/docker", + "env": [ + "ARCHITECTURE=x86_64", + "FUZZING_ENGINE=centipede", + "FUZZING_LANGUAGE=c++", + "HOME=/root", + "OUT=/workspace/out/centipede-none-x86_64", + "SANITIZER=none" + ], + "args": [ + "run", + "--platform", + "linux/amd64", + "-v", + "/workspace:/workspace", + "-e", + "ARCHITECTURE=x86_64", + "-e", + "FUZZING_ENGINE=centipede", + "-e", + "FUZZING_LANGUAGE=c++", + "-e", + "HOME=/root", + "-e", + "OUT=/workspace/out/centipede-none-x86_64", + "-e", + "SANITIZER=none", + "-t", + "gcr.io/oss-fuzz-base/base-runner", + "bash", + "-c", + "test_all.py || (echo \"********************************************************************************\nBuild checks failed.\nTo reproduce, run:\npython infra/helper.py build_image test-project\npython infra/helper.py build_fuzzers --sanitizer none --engine centipede --architecture x86_64 test-project\npython infra/helper.py check_build --sanitizer none --engine centipede --architecture x86_64 test-project\n********************************************************************************\" && false)" + ], + "id": "build-check-centipede-none-x86_64" + }, + { + "name": "gcr.io/oss-fuzz-base/base-runner", + "env": [ + "ARCHITECTURE=x86_64", + "FUZZING_ENGINE=centipede", + "FUZZING_LANGUAGE=c++", + "HOME=/root", + "OUT=/workspace/out/centipede-none-x86_64", + "SANITIZER=none" + ], + "args": [ + "bash", + "-c", + "targets_list > /workspace/targets.list.none" + ] + }, + { + "name": "gcr.io/oss-fuzz/test-project", + "args": [ + "bash", + "-c", + "cd /workspace/out/centipede-none-x86_64 && zip -r test-project-none-202001010000.zip *" + ] + }, + { + "name": "gcr.io/oss-fuzz-base/uploader", + "args": [ + "/workspace/srcmap.json", + "test_url" + ] + }, + { + "name": "gcr.io/oss-fuzz-base/uploader", + "args": [ + "/workspace/out/centipede-none-x86_64/test-project-none-202001010000.zip", + "test_url" + ] + }, + { + "name": "gcr.io/oss-fuzz-base/uploader", + "args": [ + "/workspace/targets.list.none", + "test_url" + ] + }, + { + "name": "gcr.io/cloud-builders/curl", + "args": [ + "-H", + "Content-Type: text/plain", + "-X", + "PUT", + "-d", + "test-project-none-202001010000.zip", + "test_url" + ] + }, + { + "name": "gcr.io/oss-fuzz/test-project", + "args": [ + "bash", + "-c", + "rm -r /workspace/out/centipede-none-x86_64" + ] + } +] diff --git a/infra/build/functions/trial_build.py b/infra/build/functions/trial_build.py index 8ec49f6e5..6676b2866 100644 --- a/infra/build/functions/trial_build.py +++ b/infra/build/functions/trial_build.py @@ -109,7 +109,7 @@ def get_args(args=None): help='Sanitizers.') parser.add_argument('--fuzzing-engines', required=False, - default=['afl', 'libfuzzer', 'honggfuzz'], + default=['afl', 'libfuzzer', 'honggfuzz', 'centipede'], nargs='+', help='Fuzzing engines.') parser.add_argument('--branch', diff --git a/infra/ci/build_test.py b/infra/ci/build_test.py index 9da67e253..1ba3e5f31 100644 --- a/infra/ci/build_test.py +++ b/infra/ci/build_test.py @@ -68,7 +68,7 @@ class TestShouldBuild(unittest.TestCase): def test_libfuzzer_coverage_build(self): """Tests that should_build returns True for coverage build of a project - specifying 'libfuzzer' and for fuzzing_engines.""" + specifying 'libfuzzer' for fuzzing_engines.""" _set_coverage_build() project_yaml = { 'language': 'c++', @@ -79,7 +79,7 @@ class TestShouldBuild(unittest.TestCase): def test_go_coverage_build(self): """Tests that should_build returns True for coverage build of a project - specifying 'libfuzzer' and for fuzzing_engines.""" + specifying 'libfuzzer' for fuzzing_engines.""" _set_coverage_build() project_yaml = {'language': 'go'} self.assertTrue(build.should_build(project_yaml)) @@ -96,3 +96,29 @@ class TestShouldBuild(unittest.TestCase): 'sanitizers': ['address'] } self.assertFalse(build.should_build(project_yaml)) + + def test_centipede_none_build(self): + """Tests that should_build returns True for none sanitizer build of a + project specifying 'centipede' for fuzzing_engines.""" + os.environ['SANITIZER'] = 'none' + os.environ['ENGINE'] = 'centipede' + os.environ['ARCHITECTURE'] = 'x86_64' + project_yaml = { + 'language': 'c++', + 'fuzzing_engines': ['centipede'], + 'sanitizers': ['none'] + } + self.assertTrue(build.should_build(project_yaml)) + + def test_centipede_address_build(self): + """Tests that should_build returns True for address sanitizer build of a + project specifying 'centipede' for fuzzing_engines.""" + os.environ['SANITIZER'] = 'address' + os.environ['ENGINE'] = 'centipede' + os.environ['ARCHITECTURE'] = 'x86_64' + project_yaml = { + 'language': 'c++', + 'fuzzing_engines': ['centipede'], + 'sanitizers': ['address'] + } + self.assertTrue(build.should_build(project_yaml)) diff --git a/infra/helper.py b/infra/helper.py index 6a9b46708..dc56d5fe3 100755 --- a/infra/helper.py +++ b/infra/helper.py @@ -746,14 +746,6 @@ def _add_oss_fuzz_ci_if_needed(env): env.append('OSS_FUZZ_CI=' + oss_fuzz_ci) -def get_target_out_dir(args): - """Change the out/ to a subdir when building wth Centipede and sanitizers""" - if args.engine == 'centipede' and args.sanitizer != 'none': - return os.path.join(args.project.out, - f'{args.project.name}_{args.sanitizer}') - return args.project.out - - def check_build(args): """Checks that fuzzers in the container execute without errors.""" if not check_project_exists(args.project): @@ -773,10 +765,8 @@ def check_build(args): if args.e: env += args.e - target_dir = get_target_out_dir(args) - run_args = _env_to_docker_args(env) + [ - '-v', f'{target_dir}:/out', '-t', BASE_RUNNER_IMAGE + '-v', f'{args.project.out}:/out', '-t', BASE_RUNNER_IMAGE ] if args.fuzzer_name: