aboutsummaryrefslogtreecommitdiff
path: root/infra
diff options
context:
space:
mode:
authorCatena cyber <35799796+catenacyber@users.noreply.github.com>2020-11-19 09:14:30 +0100
committerGitHub <noreply@github.com>2020-11-19 00:14:30 -0800
commit07ea81ba3e65b66467aefce2e844bba5881be691 (patch)
tree1c93ba3e42a1fe2e2f8e4ac0e72f14f7c628caff /infra
parent43f768df012e4c0e52f8ae0a7171cd748ec3f000 (diff)
downloadoss-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/Dockerfile1
-rwxr-xr-xinfra/base-images/base-builder/compile2
-rw-r--r--infra/base-images/base-builder/ossfuzz_coverage_runner.go69
-rw-r--r--infra/base-images/base-runner/Dockerfile22
-rwxr-xr-xinfra/base-images/base-runner/coverage95
-rwxr-xr-xinfra/helper.py2
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