diff --git a/Makefile b/Makefile index 9381a5e6..46c560ac 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ LINT_PKG := github.com/golangci/golangci-lint/cmd/golangci-lint GOACC_PKG := github.com/ory/go-acc FALAFEL_PKG := github.com/lightninglabs/falafel GOIMPORTS_PKG := golang.org/x/tools/cmd/goimports +GOFUZZ_BUILD_PKG := github.com/dvyukov/go-fuzz/go-fuzz-build +GOFUZZ_PKG := github.com/dvyukov/go-fuzz/go-fuzz GO_BIN := ${GOPATH}/bin BTCD_BIN := $(GO_BIN)/btcd @@ -15,6 +17,8 @@ GOMOBILE_BIN := GO111MODULE=off $(GO_BIN)/gomobile GOVERALLS_BIN := $(GO_BIN)/goveralls LINT_BIN := $(GO_BIN)/golangci-lint GOACC_BIN := $(GO_BIN)/go-acc +GOFUZZ_BUILD_BIN := $(GO_BIN)/go-fuzz-build +GOFUZZ_BIN := $(GO_BIN)/go-fuzz BTCD_DIR :=${GOPATH}/src/$(BTCD_PKG) MOBILE_BUILD_DIR :=${GOPATH}/src/$(MOBILE_PKG)/build @@ -35,6 +39,7 @@ BTCD_COMMIT := $(shell cat go.mod | \ LINT_COMMIT := v1.18.0 GOACC_COMMIT := ddc355013f90fea78d83d3a6c71f1d37ac07ecd5 FALAFEL_COMMIT := v0.7.1 +GOFUZZ_COMMIT := 21309f307f61 DEPGET := cd /tmp && GO111MODULE=on go get -v GOBUILD := GO111MODULE=on go build -v @@ -51,6 +56,7 @@ XARGS := xargs -L 1 include make/testing_flags.mk include make/release_flags.mk +include make/fuzz_flags.mk DEV_TAGS := $(if ${tags},$(DEV_TAGS) ${tags},$(DEV_TAGS)) @@ -116,6 +122,14 @@ goimports: @$(call print, "Installing goimports.") $(DEPGET) $(GOIMPORTS_PKG) +$(GOFUZZ_BIN): + @$(call print, "Fetching go-fuzz") + $(DEPGET) $(GOFUZZ_PKG)@$(GOFUZZ_COMMIT) + +$(GOFUZZ_BUILD_BIN): + @$(call print, "Fetching go-fuzz-build") + $(DEPGET) $(GOFUZZ_BUILD_PKG)@$(GOFUZZ_COMMIT) + # ============ # INSTALLATION # ============ @@ -197,6 +211,17 @@ flake-unit: @$(call print, "Flake hunting unit tests.") while [ $$? -eq 0 ]; do GOTRACEBACK=all $(UNIT) -count=1; done +# ============= +# FUZZING +# ============= +fuzz-build: $(GOFUZZ_BUILD_BIN) + @$(call print, "Creating fuzz harnesses for packages '$(FUZZPKG)'.") + scripts/fuzz.sh build "$(FUZZPKG)" + +fuzz-run: $(GOFUZZ_BIN) + @$(call print, "Fuzzing packages '$(FUZZPKG)'.") + scripts/fuzz.sh run "$(FUZZPKG)" "$(FUZZ_TEST_RUN_TIME)" "$(FUZZ_TEST_TIMEOUT)" "$(FUZZ_NUM_PROCESSES)" "$(FUZZ_BASE_WORKDIR)" + # ========= # UTILITIES # ========= diff --git a/make/fuzz_flags.mk b/make/fuzz_flags.mk new file mode 100644 index 00000000..c77deb51 --- /dev/null +++ b/make/fuzz_flags.mk @@ -0,0 +1,34 @@ +FUZZPKG = brontide lnwire wtwire +FUZZ_TEST_RUN_TIME = 30 +FUZZ_TEST_TIMEOUT = 20 +FUZZ_NUM_PROCESSES = 4 +FUZZ_BASE_WORKDIR = $(shell pwd)/fuzz + +# If specific package is being fuzzed, construct the full name of the +# subpackage. +ifneq ($(pkg),) +FUZZPKG := $(pkg) +endif + +# The default run time per fuzz test is pretty low and normally will be +# overwritten by a user depending on the time they have available. +ifneq ($(run_time),) +FUZZ_TEST_RUN_TIME := $(run_time) +endif + +# If the timeout needs to be increased, overwrite the default value. +ifneq ($(timeout),) +FUZZ_TEST_TIMEOUT := $(timeout) +endif + +# Overwrites the number of parallel processes. Should be set to the number of +# processor cores in a system. +ifneq ($(processes),) +FUZZ_NUM_PROCESSES := $(processes) +endif + +# Overwrite the base work directory for the fuzz run. Can be used to supply any +# previously generated corpus. +ifneq ($(base_workdir),) +FUZZ_BASE_WORKDIR := $(base_workdir) +endif diff --git a/scripts/fuzz.sh b/scripts/fuzz.sh new file mode 100755 index 00000000..1a79ccef --- /dev/null +++ b/scripts/fuzz.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +set -e + +function build_fuzz() { + PACKAGES=$1 + + for pkg in $PACKAGES; do + pushd fuzz/$pkg + + for file in *.go; do + if [[ "$file" == "fuzz_utils.go" ]]; then + continue + fi + + NAME=$(echo $file | sed 's/\.go$//1') + echo "Building zip file for $pkg/$NAME" + go-fuzz-build -func "Fuzz_$NAME" -o "$pkg-$NAME-fuzz.zip" "github.com/lightningnetwork/lnd/fuzz/$pkg" + done + + popd + done +} + +# timeout is a cross platform alternative to the GNU timeout command that +# unfortunately isn't available on macOS by default. +timeout() { + time=$1 + $2 & + pid=$! + sleep $time + kill -s SIGINT $pid +} + +function run_fuzz() { + PACKAGES=$1 + RUN_TIME=$2 + TIMEOUT=$3 + PROCS=$4 + BASE_WORKDIR=$5 + + for pkg in $PACKAGES; do + pushd fuzz/$pkg + + for file in *.go; do + if [[ "$file" == "fuzz_utils.go" ]]; then + continue + fi + + NAME=$(echo $file | sed 's/\.go$//1') + WORKDIR=$BASE_WORKDIR/$pkg/$NAME + mkdir -p $WORKDIR + echo "Running fuzzer $pkg-$NAME-fuzz.zip with $PROCS processors for $RUN_TIME seconds" + COMMAND="go-fuzz -bin=$pkg-$NAME-fuzz.zip -workdir=$WORKDIR -procs=$PROCS -timeout=$TIMEOUT" + echo "$COMMAND" + timeout "$RUN_TIME" "$COMMAND" + done + + popd + done +} + +# usage prints the usage of the whole script. +function usage() { + echo "Usage: " + echo "fuzz.sh build " + echo "fuzz.sh run " +} + +# Extract the sub command and remove it from the list of parameters by shifting +# them to the left. +SUBCOMMAND=$1 +shift + +# Call the function corresponding to the specified sub command or print the +# usage if the sub command was not found. +case $SUBCOMMAND in +build) + echo "Building fuzz packages" + build_fuzz "$@" + ;; +run) + echo "Running fuzzer" + run_fuzz "$@" + ;; +*) + usage + exit 1 + ;; +esac