From c41e46ffc8bc409bdfde0c0d2c97e1305f0c4106 Mon Sep 17 00:00:00 2001 From: Catena cyber <35799796+catenacyber@users.noreply.github.com> Date: Mon, 8 Mar 2021 16:05:35 +0100 Subject: Rust coverage report (for Suricata) (#4697) * Rust coverage test * Workaround to get rust coverage for Suricata --- infra/base-images/base-builder/Dockerfile | 4 +- infra/base-images/base-builder/cargo | 51 ++++++++++++++++++++++ infra/base-images/base-builder/compile | 7 ++- .../base-clang/checkout_build_install_llvm.sh | 3 +- infra/base-images/base-runner/Dockerfile | 9 ++++ infra/base-images/base-runner/coverage | 2 +- infra/base-images/base-runner/rcfilt | 21 +++++++++ infra/build/functions/build_and_run_coverage.py | 2 +- infra/ci/build.py | 2 +- infra/helper.py | 2 +- projects/suricata/Dockerfile | 1 + projects/suricata/build.sh | 10 ++++- projects/suricata/rustc.py | 28 ++++++++++++ 13 files changed, 134 insertions(+), 8 deletions(-) create mode 100755 infra/base-images/base-builder/cargo create mode 100755 infra/base-images/base-runner/rcfilt create mode 100644 projects/suricata/rustc.py diff --git a/infra/base-images/base-builder/Dockerfile b/infra/base-images/base-builder/Dockerfile index acfea7a1b..40fdcfbb5 100644 --- a/infra/base-images/base-builder/Dockerfile +++ b/infra/base-images/base-builder/Dockerfile @@ -90,6 +90,8 @@ RUN curl https://sh.rustup.rs | sh -s -- -y --default-toolchain=nightly --profil RUN cargo install cargo-fuzz # Needed to recompile rust std library for MSAN RUN rustup component add rust-src --toolchain nightly +# Set up custom environment variable for source code copy for coverage reports +ENV OSSFUZZ_RUSTPATH /rust # Install Bazel through Bazelisk, which automatically fetches the latest Bazel version. ENV BAZELISK_VERSION 1.7.4 @@ -185,7 +187,7 @@ RUN cd $SRC && \ tar -xzv --strip-components=1 -f $SRC/oss-fuzz.tar.gz && \ rm -rf examples $SRC/oss-fuzz.tar.gz -COPY compile compile_afl compile_dataflow compile_libfuzzer compile_honggfuzz \ +COPY cargo compile compile_afl compile_dataflow compile_libfuzzer compile_honggfuzz \ compile_go_fuzzer precompile_honggfuzz precompile_afl debug_afl srcmap \ write_labels.py /usr/local/bin/ diff --git a/infra/base-images/base-builder/cargo b/infra/base-images/base-builder/cargo new file mode 100755 index 000000000..57daea49f --- /dev/null +++ b/infra/base-images/base-builder/cargo @@ -0,0 +1,51 @@ +#!/bin/bash -eu +# Copyright 2020 Google Inc. +# +# 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. +# +# This is a wrapper around calling cargo +# This just expands RUSTFLAGS in case of a coverage build +# We need this until https://github.com/rust-lang/cargo/issues/5450 is merged +# because cargo uses relative paths for the current crate +# and absolute paths for its dependencies +# +################################################################################ + +export PATH="/rust/bin:$PATH" + +if [ "$SANITIZER" = "coverage" ] && [ $1 = "build" ] +then + crate_src_abspath=`cargo metadata --no-deps --format-version 1 | jq -r '.workspace_root'` + export RUSTFLAGS="$RUSTFLAGS --remap-path-prefix src=$crate_src_abspath/src" +fi + +if [ "$SANITIZER" = "coverage" ] && [ $1 = "fuzz" ] +then + fuzz_src_abspath=`pwd` + export RUSTFLAGS="$RUSTFLAGS --remap-path-prefix fuzz_targets=$fuzz_src_abspath/fuzz_targets" + # hack to turn cargo fuzz build into cargo build so as to get coverage + # cargo fuzz adds "--target" "x86_64-unknown-linux-gnu" + ( + # go into fuzz directory if not already the case + cd fuzz || true + # do not optimize with --release, leading to Malformed instrumentation profile data + cargo build --bins + # copies the build output in the expected target directory + cd target + mkdir -p x86_64-unknown-linux-gnu/release + cp -r debug/* x86_64-unknown-linux-gnu/release/ + ) + exit 0 +fi + +cargo "$@" diff --git a/infra/base-images/base-builder/compile b/infra/base-images/base-builder/compile index 13f0c5c10..78453c98c 100755 --- a/infra/base-images/base-builder/compile +++ b/infra/base-images/base-builder/compile @@ -103,6 +103,11 @@ if [ "$SANITIZER" != "undefined" ] && [ "$SANITIZER" != "coverage" ] && [ "$ARCH else export RUSTFLAGS="--cfg fuzzing -Cdebuginfo=1 -Cforce-frame-pointers" fi +if [ "$SANITIZER" = "coverage" ] +then + # link to C++ from comment in f5098035eb1a14aa966c8651d88ea3d64323823d + export RUSTFLAGS="$RUSTFLAGS -Zinstrument-coverage -C link-arg=-lc++" +fi # Add Rust libfuzzer flags. # See https://github.com/rust-fuzz/libfuzzer/blob/master/build.rs#L12. @@ -145,7 +150,7 @@ BUILD_CMD="bash -eux $SRC/build.sh" # We need to preserve source code files for generating a code coverage report. # We need exact files that were compiled, so copy both $SRC and $WORK dirs. -COPY_SOURCES_CMD="cp -rL --parents $SRC $WORK /usr/include /usr/local/include $GOPATH $OUT" +COPY_SOURCES_CMD="cp -rL --parents $SRC $WORK /usr/include /usr/local/include $GOPATH $OSSFUZZ_RUSTPATH $OUT" if [ "${BUILD_UID-0}" -ne "0" ]; then adduser -u $BUILD_UID --disabled-password --gecos '' builder diff --git a/infra/base-images/base-clang/checkout_build_install_llvm.sh b/infra/base-images/base-clang/checkout_build_install_llvm.sh index 6d9fee7bf..60429106b 100755 --- a/infra/base-images/base-clang/checkout_build_install_llvm.sh +++ b/infra/base-images/base-clang/checkout_build_install_llvm.sh @@ -20,7 +20,8 @@ # 2). NPROC=$(expr $(nproc) / 2) -LLVM_DEP_PACKAGES="build-essential make cmake ninja-build git python3 g++-multilib binutils-dev" +# zlib1g-dev is needed for llvm-profdata to handle coverage data from rust compiler +LLVM_DEP_PACKAGES="build-essential make cmake ninja-build git python3 g++-multilib binutils-dev zlib1g-dev" apt-get install -y $LLVM_DEP_PACKAGES --no-install-recommends # Checkout diff --git a/infra/base-images/base-runner/Dockerfile b/infra/base-images/base-runner/Dockerfile index 3237916d4..ee44768cd 100755 --- a/infra/base-images/base-runner/Dockerfile +++ b/infra/base-images/base-runner/Dockerfile @@ -68,6 +68,7 @@ RUN apt-get update && apt-get install -y \ python3 \ python3-pip \ wget \ + curl \ zip RUN git clone https://chromium.googlesource.com/chromium/src/tools/code_coverage /opt/code_coverage && \ @@ -83,6 +84,13 @@ ENV UBSAN_OPTIONS="print_stacktrace=1:print_summary=1:silence_unsigned_overflow= ENV FUZZER_ARGS="-rss_limit_mb=2560 -timeout=25" ENV AFL_FUZZER_ARGS="-m none" +# Install rustfilt for symbol demangling. +ENV CARGO_HOME=/rust +ENV RUSTUP_HOME=/rust/rustup +ENV PATH=$PATH:/rust/bin +RUN curl https://sh.rustup.rs | sh -s -- -y --default-toolchain=nightly +RUN cargo install rustfilt + # Install OpenJDK 15 and trim its size by removing unused components. ENV JAVA_HOME=/usr/lib/jvm/java-15-openjdk-amd64 ENV JVM_LD_LIBRARY_PATH=$JAVA_HOME/lib/server @@ -103,6 +111,7 @@ COPY bad_build_check \ dataflow_tracer.py \ download_corpus \ minijail0 \ + rcfilt \ reproduce \ run_fuzzer \ run_minijail \ diff --git a/infra/base-images/base-runner/coverage b/infra/base-images/base-runner/coverage index 2fcf9e977..76e77ec2b 100755 --- a/infra/base-images/base-runner/coverage +++ b/infra/base-images/base-runner/coverage @@ -193,7 +193,7 @@ else # Generate HTML report. llvm-cov show -format=html -output-dir=$REPORT_ROOT_DIR \ - -Xdemangler c++filt -Xdemangler -n $LLVM_COV_ARGS + -Xdemangler rcfilt $LLVM_COV_ARGS # Export coverage summary in JSON format. llvm-cov export -summary-only $LLVM_COV_ARGS > $SUMMARY_FILE diff --git a/infra/base-images/base-runner/rcfilt b/infra/base-images/base-runner/rcfilt new file mode 100755 index 000000000..1c621100c --- /dev/null +++ b/infra/base-images/base-runner/rcfilt @@ -0,0 +1,21 @@ +#!/bin/bash -u +# Copyright 2020 Google Inc. +# +# 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. +# +# Symbol demangling for both C++ and Rust +# +################################################################################ + +# simply pipe +rustfilt | c++filt -n diff --git a/infra/build/functions/build_and_run_coverage.py b/infra/build/functions/build_and_run_coverage.py index 71d7338f9..cc2de5a32 100644 --- a/infra/build/functions/build_and_run_coverage.py +++ b/infra/build/functions/build_and_run_coverage.py @@ -48,7 +48,7 @@ LATEST_REPORT_INFO_CONTENT_TYPE = 'application/json' UPLOAD_URL_FORMAT = 'gs://' + COVERAGE_BUCKET_NAME + '/{project}/{type}/{date}' # Languages from project.yaml that have code coverage support. -LANGUAGES_WITH_COVERAGE_SUPPORT = ['c', 'c++', 'go'] +LANGUAGES_WITH_COVERAGE_SUPPORT = ['c', 'c++', 'go', 'rust'] def usage(): diff --git a/infra/ci/build.py b/infra/ci/build.py index f71799bb2..addeb7879 100755 --- a/infra/ci/build.py +++ b/infra/ci/build.py @@ -32,7 +32,7 @@ DEFAULT_ENGINES = ['afl', 'honggfuzz', 'libfuzzer'] DEFAULT_SANITIZERS = ['address', 'undefined'] # Languages from project.yaml that have code coverage support. -LANGUAGES_WITH_COVERAGE_SUPPORT = ['c', 'c++', 'go'] +LANGUAGES_WITH_COVERAGE_SUPPORT = ['c', 'c++', 'go', 'rust'] def get_changed_files_output(): diff --git a/infra/helper.py b/infra/helper.py index 47b42e668..6048d9771 100755 --- a/infra/helper.py +++ b/infra/helper.py @@ -59,7 +59,7 @@ CORPUS_BACKUP_URL_FORMAT = ( PROJECT_LANGUAGE_REGEX = re.compile(r'\s*language\s*:\s*([^\s]+)') # Languages from project.yaml that have code coverage support. -LANGUAGES_WITH_COVERAGE_SUPPORT = ['c', 'c++', 'go'] +LANGUAGES_WITH_COVERAGE_SUPPORT = ['c', 'c++', 'go', 'rust'] # pylint: disable=too-many-lines diff --git a/projects/suricata/Dockerfile b/projects/suricata/Dockerfile index 40352073f..deffdef30 100644 --- a/projects/suricata/Dockerfile +++ b/projects/suricata/Dockerfile @@ -32,3 +32,4 @@ RUN git clone --depth 1 https://github.com/OISF/libhtp.git libhtp RUN git clone --depth 1 https://github.com/OISF/suricata-verify suricata-verify WORKDIR $SRC COPY build.sh $SRC/ +COPY rustc.py $SRC/ diff --git a/projects/suricata/build.sh b/projects/suricata/build.sh index d0e152d8b..0318d5140 100755 --- a/projects/suricata/build.sh +++ b/projects/suricata/build.sh @@ -52,8 +52,16 @@ mv libhtp suricata/ cd suricata sh autogen.sh #run configure with right options +if [ "$SANITIZER" = "coverage" ] +then +export RUSTFLAGS="$RUSTFLAGS -C debug-assertions=no" +chmod +x $SRC/rustc.py +export RUSTC="$SRC/rustc.py" +./configure --disable-shared --enable-fuzztargets --enable-debug +else ./src/tests/fuzz/oss-fuzz-configure.sh -make +fi +make -j$(nproc) cp src/fuzz_* $OUT/ diff --git a/projects/suricata/rustc.py b/projects/suricata/rustc.py new file mode 100644 index 000000000..00f26df63 --- /dev/null +++ b/projects/suricata/rustc.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import subprocess + +#disable coverage for crate brotli_decompressor +sys.argv[0] = "rustc" +if "brotli_decompressor" in sys.argv: + try: + sys.argv.remove("-Zinstrument-coverage") + except: + pass + print(sys.argv) +subprocess.call(sys.argv) -- cgit v1.2.3