diff options
author | Roxane <roxane.fruytier@10gen.com> | 2019-07-19 13:20:14 -0400 |
---|---|---|
committer | Roxane <roxane.fruytier@10gen.com> | 2019-07-23 12:56:16 -0400 |
commit | a5b2b781c681b1f06371a6935aa2f0468a3612da (patch) | |
tree | 99e1976ada5fd87e815a7b913f848571461af624 | |
parent | 38e92ef5b9fe645cd73fec3742c0fde9caea0cb2 (diff) | |
download | mongo-a5b2b781c681b1f06371a6935aa2f0468a3612da.tar.gz |
SERVER-41796 Create Evergreen variant for libfuzzer targets
-rw-r--r-- | SConstruct | 3 | ||||
-rwxr-xr-x | buildscripts/merge_corpus.sh | 26 | ||||
-rw-r--r-- | buildscripts/resmokeconfig/suites/libfuzzer.yml | 7 | ||||
-rw-r--r-- | buildscripts/resmokelib/config.py | 3 | ||||
-rw-r--r-- | buildscripts/resmokelib/core/jasper_process.py | 2 | ||||
-rw-r--r-- | buildscripts/resmokelib/core/process.py | 4 | ||||
-rw-r--r-- | buildscripts/resmokelib/selector.py | 1 | ||||
-rw-r--r-- | buildscripts/resmokelib/testing/executor.py | 1 | ||||
-rw-r--r-- | buildscripts/resmokelib/testing/testcases/cpp_libfuzzer_test.py | 54 | ||||
-rw-r--r-- | etc/evergreen.yml | 111 | ||||
-rw-r--r-- | src/mongo/db/storage/key_string_to_bson_fuzzer.cpp | 27 |
11 files changed, 220 insertions, 19 deletions
diff --git a/SConstruct b/SConstruct index 0d1a5fd5096..7fb8c50bae6 100644 --- a/SConstruct +++ b/SConstruct @@ -2672,6 +2672,9 @@ def doConfigure(myenv): # to fail sanitizer_list.remove('fuzzer') sanitizer_list.append('fuzzer-no-link') + # These flags are needed to generate a coverage report + myenv.Append(LINKFLAGS=['-fprofile-instr-generate','-fcoverage-mapping']) + myenv.Append(CCFLAGS=['-fprofile-instr-generate','-fcoverage-mapping']) sanitizer_option = '-fsanitize=' + ','.join(sanitizer_list) diff --git a/buildscripts/merge_corpus.sh b/buildscripts/merge_corpus.sh new file mode 100755 index 00000000000..162f7037af5 --- /dev/null +++ b/buildscripts/merge_corpus.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# merge_corpus.sh +# +# Merges the corpus of each libfuzzer tests + +set -o verbose +set -o errexit + +input="build/libfuzzer_tests.txt" +corpus_dir="corpus" + +# We need to merge the corpus once it has been tested +while IFS= read -r line +do + mkdir "$corpus_dir"/corpus-"${line##*/}"-new + ./"$line" "$corpus_dir"/corpus-"${line##*/}"-new "$corpus_dir"/corpus-"${line##*/}" -merge=1 +done < "$input" + +# Delete old corpus +find corpus/* -not -name '*-new' -type d -exec rm -rv {} + + +# Rename new corpus to old corpus +for f in ./corpus/* +do + mv "$f" "${f%-new}" +done
\ No newline at end of file diff --git a/buildscripts/resmokeconfig/suites/libfuzzer.yml b/buildscripts/resmokeconfig/suites/libfuzzer.yml new file mode 100644 index 00000000000..3460f41ff64 --- /dev/null +++ b/buildscripts/resmokeconfig/suites/libfuzzer.yml @@ -0,0 +1,7 @@ +test_kind: cpp_libfuzzer_test + +selector: + root: build/libfuzzer_tests.txt + +executor: + config: {} diff --git a/buildscripts/resmokelib/config.py b/buildscripts/resmokelib/config.py index 729be502a8f..5733e4789a0 100644 --- a/buildscripts/resmokelib/config.py +++ b/buildscripts/resmokelib/config.py @@ -435,9 +435,10 @@ ORDER_TESTS_BY_NAME = True DEFAULT_BENCHMARK_TEST_LIST = "build/benchmarks.txt" DEFAULT_UNIT_TEST_LIST = "build/unittests.txt" DEFAULT_INTEGRATION_TEST_LIST = "build/integration_tests.txt" +DEFAULT_LIBFUZZER_TEST_LIST = "build/libfuzzer_tests.txt" # External files or executables, used as suite selectors, that are created during the build and # therefore might not be available when creating a test membership map. EXTERNAL_SUITE_SELECTORS = (DEFAULT_BENCHMARK_TEST_LIST, DEFAULT_UNIT_TEST_LIST, DEFAULT_INTEGRATION_TEST_LIST, DEFAULT_DBTEST_EXECUTABLE, - DEFAULT_MONGOEBENCH_EXECUTABLE) + DEFAULT_MONGOEBENCH_EXECUTABLE, DEFAULT_LIBFUZZER_TEST_LIST) diff --git a/buildscripts/resmokelib/core/jasper_process.py b/buildscripts/resmokelib/core/jasper_process.py index fc4ef79fa9c..941d4cfbe47 100644 --- a/buildscripts/resmokelib/core/jasper_process.py +++ b/buildscripts/resmokelib/core/jasper_process.py @@ -83,7 +83,7 @@ class Process(_process.Process): self.wait() return self._return_code - def wait(self): + def wait(self, timeout=None): """Wait until process has terminated and all output has been consumed by the logger pipes.""" if self._return_code is None: wait = self._stub.Wait(self._id) diff --git a/buildscripts/resmokelib/core/process.py b/buildscripts/resmokelib/core/process.py index de241ef57ce..2c49eba1e12 100644 --- a/buildscripts/resmokelib/core/process.py +++ b/buildscripts/resmokelib/core/process.py @@ -193,10 +193,10 @@ class Process(object): """Poll.""" return self._process.poll() - def wait(self): + def wait(self, timeout=None): """Wait until process has terminated and all output has been consumed by the logger pipes.""" - return_code = self._process.wait() + return_code = self._process.wait(timeout) if self._stdout_pipe: self._stdout_pipe.wait_until_finished() diff --git a/buildscripts/resmokelib/selector.py b/buildscripts/resmokelib/selector.py index 36f2e608889..5880e2b3c9f 100644 --- a/buildscripts/resmokelib/selector.py +++ b/buildscripts/resmokelib/selector.py @@ -706,6 +706,7 @@ _SELECTOR_REGISTRY = { "sleep_test": (_SleepTestCaseSelectorConfig, _SleepTestCaseSelector), "genny_test": (_FileBasedSelectorConfig, _Selector), "gennylib_test": (_GennylibTestCaseSelectorConfig, _GennylibTestCaseSelector), + "cpp_libfuzzer_test": (_CppTestSelectorConfig, _CppTestSelector), } diff --git a/buildscripts/resmokelib/testing/executor.py b/buildscripts/resmokelib/testing/executor.py index 21a1d12e128..234abed1d38 100644 --- a/buildscripts/resmokelib/testing/executor.py +++ b/buildscripts/resmokelib/testing/executor.py @@ -238,7 +238,6 @@ class TestSuiteExecutor(object): # pylint: disable=too-many-instance-attributes fixture_config = {} fixture_class = fixtures.NOOP_FIXTURE_CLASS - if self.fixture_config is not None: fixture_config = self.fixture_config.copy() fixture_class = fixture_config.pop("class") diff --git a/buildscripts/resmokelib/testing/testcases/cpp_libfuzzer_test.py b/buildscripts/resmokelib/testing/testcases/cpp_libfuzzer_test.py new file mode 100644 index 00000000000..b624e92b0cc --- /dev/null +++ b/buildscripts/resmokelib/testing/testcases/cpp_libfuzzer_test.py @@ -0,0 +1,54 @@ +"""The libfuzzertest.TestCase for C++ libfuzzer tests.""" + +import subprocess +import os +import datetime + +from . import interface +from ... import core +from ... import utils + + +class CPPLibfuzzerTestCase(interface.ProcessTestCase): + """A C++ libfuzzer test to execute.""" + + REGISTERED_NAME = "cpp_libfuzzer_test" + DEFAULT_TIMEOUT = datetime.timedelta(hours=1) + + def __init__(self, logger, program_executable, program_options=None): + """Initialize the CPPLibfuzzerTestCase with the executable to run.""" + + interface.ProcessTestCase.__init__(self, logger, "C++ libfuzzer test", program_executable) + + self.program_executable = program_executable + self.program_options = utils.default_if_none(program_options, {}).copy() + self.corpus_directory = "corpus/corpus-" + self.short_name() + + os.makedirs(self.corpus_directory, exist_ok=True) + + def _make_process(self): + default_args = [ + self.program_executable, self.corpus_directory, "-max_len=100000", "-rss_limit_mb=5000" + ] + return core.programs.make_process(self.logger, default_args, **self.program_options) + + def _execute(self, process): + """Run the specified process.""" + self.logger.info("Starting Libfuzzer Test %s...\n%s", self.short_description(), + process.as_command()) + process.start() + self.logger.info("%s started with pid %s.", self.short_description(), process.pid) + try: + self.return_code = process.wait(self.DEFAULT_TIMEOUT.total_seconds()) + except subprocess.TimeoutExpired: + # If the test timeout, then no errors were detected. Thus, the return code should be 0. + process.stop(kill=True) + process.wait() + self.logger.info("%s timed out. No errors were found.", self.short_description()) + self.return_code = 0 + + if self.return_code != 0: + self.logger.info("Failed %s", self.return_code) + raise self.failureException("%s failed" % (self.short_description())) + + self.logger.info("%s finished.", self.short_description()) diff --git a/etc/evergreen.yml b/etc/evergreen.yml index c20cefd6c32..442224d959c 100644 --- a/etc/evergreen.yml +++ b/etc/evergreen.yml @@ -200,6 +200,12 @@ variables: - compile_unittests - unittests +- &libfuzzertests + name: libfuzzertests! + execution_tasks: + - compile_libfuzzertests + - libfuzzertests + - &dbtest name: dbtest! execution_tasks: @@ -400,6 +406,7 @@ variables: - ubuntu1804-debug-asan - ubuntu1804-debug-ubsan - ubuntu1804-debug-aubsan-lite + - ubuntu1804-debug-aubsan-lite_fuzzer - ubuntu1804-debug-aubsan-async @@ -529,6 +536,51 @@ functions: bucket: mciuploads extract_to: src/benchrun_embedded/testcases + "fetch corpus": &fetch_corpus + command: s3.get + params: + aws_key: ${s3_access_key_id} + aws_secret: ${s3_secret_access_key} + remote_file: ${project}/corpus/mongo-${build_variant}-latest.tgz + bucket: fuzzer-artifacts + local_file: src/corpus.tgz + + "extract corpus": &extract_corpus + command: shell.exec + type: test + params: + shell: bash + script: | + set -o verbose + set -o errexit + + target_dir="src/corpus" + mkdir -p $target_dir + mv src/corpus.tgz $target_dir + + cd $target_dir + tar xzf corpus.tgz + + "archive new corpus": &archive_new_corpus + command: archive.targz_pack + params: + target: corpus.tgz + source_dir: src/corpus + include: + - "**" + + "upload new corpus": &upload_new_corpus + command: s3.put + params: + aws_key: ${s3_access_key_id} + aws_secret: ${s3_secret_access_key} + local_file: corpus.tgz + remote_file: ${project}/corpus/mongo-${build_variant}-latest.tgz + bucket: fuzzer-artifacts + permissions: private + content_type: ${content_type|application/gzip} + display_name: "Fuzzer Tests Corpus Tar Archive" + "get buildnumber": &get_buildnumber command: keyval.inc params: @@ -3761,6 +3813,36 @@ tasks: vars: resmoke_args: --suites=unittests +##compile_libfuzzertests - build libfuzzertests ## +- name: compile_libfuzzertests + commands: + - func: "scons compile" + vars: + targets: libfuzzer_tests + task_compile_flags: >- + --detect-odr-violations + +## libfuzzertests - run libfuzzertests ## +- name: libfuzzertests + commands: + - func: "fetch corpus" + - func: "extract corpus" + - func: "run tests" + vars: + resmoke_args: --suites=libfuzzer + - command: shell.exec + type: setup + params: + working_dir: src + shell: bash + script: | + set -o verbose + set -o errexit + + ./buildscripts/merge_corpus.sh + - func: "archive new corpus" + - func: "upload new corpus" + ## compile_dbtest ## - name: compile_dbtest commands: @@ -8214,6 +8296,11 @@ task_groups: - compile_dbtest - dbtest - <<: *compile_task_group_template + name: libfuzzertests_TG + tasks: + - compile_libfuzzertests + - libfuzzertests +- <<: *compile_task_group_template name: compile_all_run_unittests_TG tasks: - compile @@ -12284,6 +12371,30 @@ buildvariants: - name: .logical_session_cache .one_sec - name: watchdog_wiredtiger +- name: ubuntu1804-debug-aubsan-lite_fuzzer + display_name: "{A,UB}SAN Enterprise SSL Ubuntu 18.04 FUZZER" + modules: + - enterprise + run_on: + - ubuntu1804-build + stepback: false + batchtime: 1440 # 1 day + expansions: + # We need llvm-symbolizer in the PATH for ASAN for clang-3.7 or later. + variant_path_suffix: /opt/mongodbtoolchain/v3/bin + lang_environment: LANG=C + san_options: UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=1" LSAN_OPTIONS="suppressions=etc/lsan.suppressions:report_objects=1" ASAN_OPTIONS=detect_leaks=1:check_initialization_order=true:strict_init_order=true:abort_on_error=1:disable_coredump=0:handle_abort=1 + compile_flags: LINKFLAGS=-nostdlib++ LIBS=stdc++ --variables-files=etc/scons/mongodbtoolchain_v3_clang.vars --dbg=on --opt=on --allocator=system --sanitize=undefined,address,fuzzer --ssl -j$(grep -c ^processor /proc/cpuinfo) --nostrip + resmoke_jobs_factor: 0.3 # Avoid starting too many mongod's under {A,UB}SAN build. + tooltags: "ssl" + build_mongoreplay: true + hang_analyzer_dump_core: false + scons_cache_scope: shared + display_tasks: + - *libfuzzertests + tasks: + - name: libfuzzertests_TG + - name: ubuntu1804-debug-aubsan-async display_name: "~ {A,UB}SAN Enterprise SSL Ubuntu 18.04 async" modules: diff --git a/src/mongo/db/storage/key_string_to_bson_fuzzer.cpp b/src/mongo/db/storage/key_string_to_bson_fuzzer.cpp index 1b34c79790d..7208a589c54 100644 --- a/src/mongo/db/storage/key_string_to_bson_fuzzer.cpp +++ b/src/mongo/db/storage/key_string_to_bson_fuzzer.cpp @@ -36,28 +36,27 @@ const auto kV1 = mongo::KeyString::Version::V1; const auto kV0 = mongo::KeyString::Version::V0; uint8_t getZeroType(char val) { - using mongo::KeyString; switch (val % 10) { case 0: - return KeyString::TypeBits::kInt; + return mongo::KeyString::TypeBits::kInt; case 1: - return KeyString::TypeBits::kDouble; + return mongo::KeyString::TypeBits::kDouble; case 2: - return KeyString::TypeBits::kLong; + return mongo::KeyString::TypeBits::kLong; case 3: - return KeyString::TypeBits::kNegativeDoubleZero; + return mongo::KeyString::TypeBits::kNegativeDoubleZero; case 4: - return KeyString::TypeBits::kDecimalZero0xxx; + return mongo::KeyString::TypeBits::kDecimalZero0xxx; case 5: - return KeyString::TypeBits::kDecimalZero1xxx; + return mongo::KeyString::TypeBits::kDecimalZero1xxx; case 6: - return KeyString::TypeBits::kDecimalZero2xxx; + return mongo::KeyString::TypeBits::kDecimalZero2xxx; case 7: - return KeyString::TypeBits::kDecimalZero3xxx; + return mongo::KeyString::TypeBits::kDecimalZero3xxx; case 8: - return KeyString::TypeBits::kDecimalZero4xxx; + return mongo::KeyString::TypeBits::kDecimalZero4xxx; case 9: - return KeyString::TypeBits::kDecimalZero5xxx; + return mongo::KeyString::TypeBits::kDecimalZero5xxx; default: return 0x00; } @@ -72,12 +71,12 @@ extern "C" int LLVMFuzzerTestOneInput(const char* Data, size_t Size) { mongo::KeyString::TypeBits tb(version); - const auto len = Data[2]; - if (len > Size - 3) + const size_t len = Data[2]; + if (len > static_cast<size_t>(Size - 3)) return 0; // Data[2] defines the number of types to append to the TypeBits // Data[3 + i] defines which types have to be added - for (int i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { char randomType = Data[3 + i] & 0xf; char randomZeroType = (Data[3 + i] & 0xf0) >> 4; switch (randomType % 9) { |