diff options
author | Catena cyber <35799796+catenacyber@users.noreply.github.com> | 2020-11-19 09:14:30 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-19 00:14:30 -0800 |
commit | 07ea81ba3e65b66467aefce2e844bba5881be691 (patch) | |
tree | 1c93ba3e42a1fe2e2f8e4ac0e72f14f7c628caff /infra | |
parent | 43f768df012e4c0e52f8ae0a7171cd748ec3f000 (diff) | |
download | oss-fuzz-07ea81ba3e65b66467aefce2e844bba5881be691.tar.gz |
[infra] Add code coverage report generation for Go projects (#3142)
* Golang coverage report
* Enables golang coverage report for gonids and go-dns
* Generates summary for golang coverage reports
* Performance profile for golang projects
Diffstat (limited to 'infra')
-rw-r--r-- | infra/base-images/base-builder/Dockerfile | 1 | ||||
-rwxr-xr-x | infra/base-images/base-builder/compile | 2 | ||||
-rw-r--r-- | infra/base-images/base-builder/ossfuzz_coverage_runner.go | 69 | ||||
-rw-r--r-- | infra/base-images/base-runner/Dockerfile | 22 | ||||
-rwxr-xr-x | infra/base-images/base-runner/coverage | 95 | ||||
-rwxr-xr-x | infra/helper.py | 2 |
6 files changed, 157 insertions, 34 deletions
diff --git a/infra/base-images/base-builder/Dockerfile b/infra/base-images/base-builder/Dockerfile index 6b6299e28..70599f379 100644 --- a/infra/base-images/base-builder/Dockerfile +++ b/infra/base-images/base-builder/Dockerfile @@ -152,6 +152,7 @@ COPY compile compile_afl compile_dataflow compile_libfuzzer compile_honggfuzz \ precompile_honggfuzz srcmap write_labels.py /usr/local/bin/ COPY detect_repo.py /opt/cifuzz/ +COPY ossfuzz_coverage_runner.go $GOPATH RUN precompile_honggfuzz diff --git a/infra/base-images/base-builder/compile b/infra/base-images/base-builder/compile index 2a9876af2..cdbbfe0a9 100755 --- a/infra/base-images/base-builder/compile +++ b/infra/base-images/base-builder/compile @@ -92,7 +92,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 $OUT" +COPY_SOURCES_CMD="cp -rL --parents $SRC $WORK /usr/include /usr/local/include $GOPATH $OUT" if [ "${BUILD_UID-0}" -ne "0" ]; then adduser -u $BUILD_UID --disabled-password --gecos '' builder diff --git a/infra/base-images/base-builder/ossfuzz_coverage_runner.go b/infra/base-images/base-builder/ossfuzz_coverage_runner.go new file mode 100644 index 000000000..d433da246 --- /dev/null +++ b/infra/base-images/base-builder/ossfuzz_coverage_runner.go @@ -0,0 +1,69 @@ +// 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. + +package mypackagebeingfuzzed + +import ( + "io/ioutil" + "os" + "runtime/pprof" + "testing" +) + +func TestFuzzCorpus(t *testing.T) { + dir := os.Getenv("FUZZ_CORPUS_DIR") + if dir == "" { + t.Logf("No fuzzing corpus directory set") + return + } + infos, err := ioutil.ReadDir(dir) + if err != nil { + t.Logf("Not fuzzing corpus directory %s", err) + return + } + filename := "" + defer func() { + if r := recover(); r != nil { + t.Error("Fuzz panicked in "+filename, r) + } + }() + profname := os.Getenv("FUZZ_PROFILE_NAME") + if profname != "" { + f, err := os.Create(profname + ".cpu.prof") + if err != nil { + t.Logf("error creating profile file %s\n", err) + } else { + _ = pprof.StartCPUProfile(f) + } + } + for i := range infos { + filename = dir + infos[i].Name() + data, err := ioutil.ReadFile(filename) + if err != nil { + t.Error("Failed to read corpus file", err) + } + FuzzFunction(data) + } + if profname != "" { + pprof.StopCPUProfile() + f, err := os.Create(profname + ".heap.prof") + if err != nil { + t.Logf("error creating heap profile file %s\n", err) + } + if err = pprof.WriteHeapProfile(f); err != nil { + t.Logf("error writing heap profile file %s\n", err) + } + f.Close() + } +} diff --git a/infra/base-images/base-runner/Dockerfile b/infra/base-images/base-runner/Dockerfile index b1b7bec93..85d5f6d6a 100644 --- a/infra/base-images/base-runner/Dockerfile +++ b/infra/base-images/base-runner/Dockerfile @@ -24,6 +24,7 @@ COPY --from=base-clang /usr/local/bin/llvm-cov /usr/local/bin/ COPY --from=base-clang /usr/local/bin/llvm-profdata /usr/local/bin/ COPY --from=base-clang /usr/local/bin/llvm-symbolizer /usr/local/bin/ +RUN apt-get update RUN apt-get install -y \ binutils \ file \ @@ -64,3 +65,24 @@ ENV MSAN_OPTIONS="print_stats=1:strip_path_prefix=/workspace/:symbolize=1:dedup_ ENV UBSAN_OPTIONS="print_stacktrace=1:print_summary=1:silence_unsigned_overflow=1:strip_path_prefix=/workspace/:symbolize=1:dedup_token_length=3" ENV FUZZER_ARGS="-rss_limit_mb=2560 -timeout=25" ENV AFL_FUZZER_ARGS="-m none" + +# Download and install the latest stable Go. +ADD https://storage.googleapis.com/golang/getgo/installer_linux $SRC/ +RUN chmod +x $SRC/installer_linux && \ + SHELL="bash" $SRC/installer_linux && \ + rm $SRC/installer_linux + +# Set up Golang environment variables (copied from /root/.bash_profile). +ENV GOPATH /root/go + +# /root/.go/bin is for the standard Go binaries (i.e. go, gofmt, etc). +# $GOPATH/bin is for the binaries from the dependencies installed via "go get". +ENV PATH $PATH:/root/.go/bin:$GOPATH/bin + +# gocovmerge merges coverage profiles. +RUN go get -u github.com/wadey/gocovmerge +# pprof-merge merges performance profiles. +RUN go get -u github.com/rakyll/pprof-merge +# gocovsum produces a json summary. +RUN go get -u github.com/catenacyber/gocovsum + diff --git a/infra/base-images/base-runner/coverage b/infra/base-images/base-runner/coverage index d2bd4ddd9..f4f316107 100755 --- a/infra/base-images/base-runner/coverage +++ b/infra/base-images/base-runner/coverage @@ -111,21 +111,37 @@ function run_fuzz_target { fi } +export SYSGOPATH=$GOPATH +export GOPATH=$OUT/$GOPATH # Run each fuzz target, generate raw coverage dumps. for fuzz_target in $FUZZ_TARGETS; do - # Continue if not a fuzz target. - if [[ $FUZZING_ENGINE != "none" ]]; then - grep "LLVMFuzzerTestOneInput" $fuzz_target > /dev/null 2>&1 || continue - fi - - echo "Running $fuzz_target" - run_fuzz_target $fuzz_target & - - if [[ -z $objects ]]; then - # The first object needs to be passed without -object= flag. - objects="$fuzz_target" + # Test if fuzz target is a golang one. + if [[ $FUZZING_LANGUAGE == "go" ]]; then + # Continue if not a fuzz target. + if [[ $FUZZING_ENGINE != "none" ]]; then + grep "go test -run" $fuzz_target > /dev/null 2>&1 || continue + fi + cd $GOPATH/src + echo "Running go target $fuzz_target" + export FUZZ_CORPUS_DIR="/corpus/${fuzz_target}/" + export FUZZ_PROFILE_NAME="$DUMPS_DIR/$fuzz_target.perf" + bash $OUT/$fuzz_target $DUMPS_DIR/$fuzz_target.profdata & + cd $OUT else - objects="$objects -object=$fuzz_target" + # Continue if not a fuzz target. + if [[ $FUZZING_ENGINE != "none" ]]; then + grep "LLVMFuzzerTestOneInput" $fuzz_target > /dev/null 2>&1 || continue + fi + + echo "Running $fuzz_target" + run_fuzz_target $fuzz_target & + + if [[ -z $objects ]]; then + # The first object needs to be passed without -object= flag. + objects="$fuzz_target" + else + objects="$objects -object=$fuzz_target" + fi fi # Do not spawn more processes than the number of CPUs available. @@ -139,32 +155,47 @@ done # Wait for background processes to finish. wait -# From this point on the script does not tolerate any errors. -set -e +if [[ $FUZZING_LANGUAGE == "go" ]]; then + $SYSGOPATH/bin/gocovmerge $DUMPS_DIR/*.profdata > fuzz.cov + go tool cover -html=fuzz.cov -o $REPORT_ROOT_DIR/index.html + $SYSGOPATH/bin/gocovsum fuzz.cov > $SUMMARY_FILE + cp $REPORT_ROOT_DIR/index.html $REPORT_PLATFORM_DIR/index.html + $SYSGOPATH/bin/pprof-merge $DUMPS_DIR/*.perf.cpu.prof + mv merged.data $REPORT_ROOT_DIR/cpu.prof + $SYSGOPATH/bin/pprof-merge $DUMPS_DIR/*.perf.heap.prof + mv merged.data $REPORT_ROOT_DIR/heap.prof + #TODO some proxy for go tool pprof -http=127.0.0.1:8001 $DUMPS_DIR/cpu.prof + echo "Finished generating code coverage report for Go fuzz targets." +else -# Merge all dumps from the individual targets. -rm -f $PROFILE_FILE -llvm-profdata merge -sparse $DUMPS_DIR/*.profdata -o $PROFILE_FILE + # From this point on the script does not tolerate any errors. + set -e -# TODO(mmoroz): add script from Chromium for rendering directory view reports. -# The first path in $objects does not have -object= prefix (llvm-cov format). -shared_libraries=$(coverage_helper shared_libs -build-dir=$OUT -object=$objects) -objects="$objects $shared_libraries" + # Merge all dumps from the individual targets. + rm -f $PROFILE_FILE + llvm-profdata merge -sparse $DUMPS_DIR/*.profdata -o $PROFILE_FILE -# It's important to use $LLVM_COV_COMMON_ARGS as the last argument due to -# positional arguments (SOURCES) that can be passed via $COVERAGE_EXTRA_ARGS. -LLVM_COV_ARGS="-instr-profile=$PROFILE_FILE $objects $LLVM_COV_COMMON_ARGS" + # TODO(mmoroz): add script from Chromium for rendering directory view reports. + # The first path in $objects does not have -object= prefix (llvm-cov format). + shared_libraries=$(coverage_helper shared_libs -build-dir=$OUT -object=$objects) + objects="$objects $shared_libraries" -# Generate HTML report. -llvm-cov show -format=html -output-dir=$REPORT_ROOT_DIR \ - -Xdemangler c++filt -Xdemangler -n $LLVM_COV_ARGS + # It's important to use $LLVM_COV_COMMON_ARGS as the last argument due to + # positional arguments (SOURCES) that can be passed via $COVERAGE_EXTRA_ARGS. + LLVM_COV_ARGS="-instr-profile=$PROFILE_FILE $objects $LLVM_COV_COMMON_ARGS" -# Export coverage summary in JSON format. -llvm-cov export -summary-only $LLVM_COV_ARGS > $SUMMARY_FILE + # Generate HTML report. + llvm-cov show -format=html -output-dir=$REPORT_ROOT_DIR \ + -Xdemangler c++filt -Xdemangler -n $LLVM_COV_ARGS -# Post process HTML report. -coverage_helper -v post_process -src-root-dir=/ -summary-file=$SUMMARY_FILE \ - -output-dir=$REPORT_ROOT_DIR $PATH_EQUIVALENCE_ARGS + # Export coverage summary in JSON format. + llvm-cov export -summary-only $LLVM_COV_ARGS > $SUMMARY_FILE + + # Post process HTML report. + coverage_helper -v post_process -src-root-dir=/ -summary-file=$SUMMARY_FILE \ + -output-dir=$REPORT_ROOT_DIR $PATH_EQUIVALENCE_ARGS + +fi if [[ -n $HTTP_PORT ]]; then # Serve the report locally. diff --git a/infra/helper.py b/infra/helper.py index 99d871129..d91239df7 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++'] +LANGUAGES_WITH_COVERAGE_SUPPORT = ['c', 'c++', 'go'] def main(): # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements |