Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Golang coverage report test #3142

Merged
merged 4 commits into from
Nov 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infra/base-images/base-builder/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,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

Expand Down
2 changes: 1 addition & 1 deletion infra/base-images/base-builder/compile
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the whole $GOPATH to be copied over to $OUT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it can be just $GOPATH/src and $GOPATH/pkg but I am not sure...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, let's leave it this way


if [ "${BUILD_UID-0}" -ne "0" ]; then
adduser -u $BUILD_UID --disabled-password --gecos '' builder
Expand Down
69 changes: 69 additions & 0 deletions infra/base-images/base-builder/ossfuzz_coverage_runner.go
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we would need a Google copyright here:

// 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok


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()
}
}
22 changes: 22 additions & 0 deletions infra/base-images/base-runner/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base-image does this command already. Are you sure we need it here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think so, this must have been a debug leftover

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for verifying that it's needed!

RUN apt-get install -y \
binutils \
file \
Expand Down Expand Up @@ -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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied from base-builder

Maybe we can reuse the binaries already present in /out/root/go...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we need golang to be installed in the base-runner image, as it's being used for generating a coverage report?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oliverchang as we also install Go in the base-runner, would you be ok with us doing that once in the base-image instead?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving it here is probably fine.

We plan to diverge images here and migrate runners to be based on ClusterFuzz images instead, so we'd have to add it back here anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, thanks Oliver!

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

95 changes: 63 additions & 32 deletions infra/base-images/base-runner/coverage
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add one indentation level for all the commands from here down to line 190

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I had not done this at first for easier review ;-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

smart!

# 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.
Expand Down
2 changes: 1 addition & 1 deletion infra/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
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
Expand Down
16 changes: 16 additions & 0 deletions projects/go-dns/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ function compile_fuzzer {
function=$2
fuzzer=$3

if [[ $SANITIZER = *coverage* ]]; then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something like this needs to be done for each Go project, right?

Sadly, we decided not to put compile_fuzzer function anywhere globally and instead copy-pasted it into every Go project's build_file. Now is a moment to reconsider that decision

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this needs to be done for each project.

Do you want this generalization be part of this PR ? (or rather in another one)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's do it in another PR

cd $GOPATH/src/$path
fuzzed_package=`pwd | rev | cut -d'/' -f 1 | rev`
cp $GOPATH/ossfuzz_coverage_runner.go ./"${function,,}"_test.go
sed -i -e 's/FuzzFunction/'$function'/' ./"${function,,}"_test.go
sed -i -e 's/mypackagebeingfuzzed/'$fuzzed_package'/' ./"${function,,}"_test.go
sed -i -e 's/TestFuzzCorpus/Test'$function'Corpus/' ./"${function,,}"_test.go

echo "#/bin/sh" > $OUT/$fuzzer
echo "cd $path" >> $OUT/$fuzzer
echo "go test -run Test${function}Corpus -v -tags fuzz -coverprofile \$1 " >> $OUT/$fuzzer
chmod +x $OUT/$fuzzer

cd -
return 0
fi
# Compile and instrument all Go files relevant to this fuzz target.
go-fuzz -tags fuzz -func $function -o $fuzzer.a $path

Expand Down
4 changes: 2 additions & 2 deletions projects/gonids/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
################################################################################

FROM gcr.io/oss-fuzz-base/base-builder
RUN go get github.com/google/gonids
RUN go get -t github.com/google/gonids

ADD https://rules.emergingthreats.net/open/suricata/emerging.rules.zip emerging.rules.zip

COPY build.sh $SRC/
WORKDIR $SRC
WORKDIR $SRC/
18 changes: 17 additions & 1 deletion projects/gonids/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ function compile_fuzzer {
function=$2
fuzzer=$3

if [[ $SANITIZER = *coverage* ]]; then
cd $GOPATH/src/$path
fuzzed_package=`pwd | rev | cut -d'/' -f 1 | rev`
cp $GOPATH/ossfuzz_coverage_runner.go ./"${function,,}"_test.go
sed -i -e 's/FuzzFunction/'$function'/' ./"${function,,}"_test.go
sed -i -e 's/mypackagebeingfuzzed/'$fuzzed_package'/' ./"${function,,}"_test.go
sed -i -e 's/TestFuzzCorpus/Test'$function'Corpus/' ./"${function,,}"_test.go

echo "#/bin/sh" > $OUT/$fuzzer
echo "cd $path" >> $OUT/$fuzzer
echo "go test -run Test${function}Corpus -v -coverprofile \$1 " >> $OUT/$fuzzer
chmod +x $OUT/$fuzzer

cd -
return 0
fi
# Compile and instrument all Go files relevant to this fuzz target.
go-fuzz -func $function -o $fuzzer.a $path

Expand All @@ -37,4 +53,4 @@ mkdir corpus
set +x
cat *.rules | while read l; do echo $l > corpus/$i.rule; i=$((i+1)); done
set -x
zip -r $OUT/fuzz_parserule_seed_corpus.zip corpus
zip -q -r $OUT/fuzz_parserule_seed_corpus.zip corpus