diff --git a/.gitignore b/.gitignore index ed8d0a8d..f23749bd 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,12 @@ cmd/lncli/lncli mobile/build mobile/*_generated.go +# Files created for fuzzing. +fuzz/**/*-fuzz.zip +fuzz/**/corpus +fuzz/**/crashers +fuzz/**/suppressions + # vim *.swp 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/go.mod b/go.mod index 38b07b6f..45fc90f7 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/dustin/go-humanize v1.0.0 // indirect + github.com/dvyukov/go-fuzz v0.0.0-20200916044129-21309f307f61 // indirect github.com/go-errors/errors v1.0.1 github.com/go-openapi/strfmt v0.19.5 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect diff --git a/go.sum b/go.sum index efd76105..1de001fb 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dvyukov/go-fuzz v0.0.0-20200916044129-21309f307f61 h1:u7QPBOrJA2lSn2FBupeLsx8pLb7SpCfc6DCutrAo+v8= +github.com/dvyukov/go-fuzz v0.0.0-20200916044129-21309f307f61/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY= github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 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