From 62b54d012583bf98513d0f14ea0c5eafbea8a411 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Mon, 7 Sep 2020 17:32:19 +0200 Subject: [PATCH] build: introduce a fuzzing mode This adds a new configuration, --enable-fuzzing (which is more than welcome to be coupled with --enable-address-sanitizer), to pass the fuzzer sanitizer argument when compiling objects. This allows libfuzzer to actually be able "to fuzz" by detecting coverage and be smart when mutating inputs. As libfuzzer brings its own ~~fees~~ main(), we compile objects with fsanitize=fuzzer-no-link, and special-case the linkage of the fuzz targets. A "lib" is added to abstract out the interface to the fuzzing tool used. This allow us to use the same targets to fuzz using AFL, hongfuzz or w/e by adding their entrypoints into libfuzz. (h/t to practicalswift who introduced this for bitcoin-core, which i mimiced) Signed-off-by: Antoine Poinsot --- Makefile | 30 ++++++++++++++++++++++++------ configure | 17 +++++++++++++++++ tests/fuzz/Makefile | 30 ++++++++++++++++++++++++++++++ tests/fuzz/fuzz-addr.c | 22 ++++++++++++++++++++++ tests/fuzz/libfuzz.c | 16 ++++++++++++++++ tests/fuzz/libfuzz.h | 18 ++++++++++++++++++ 6 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 tests/fuzz/Makefile create mode 100644 tests/fuzz/fuzz-addr.c create mode 100644 tests/fuzz/libfuzz.c create mode 100644 tests/fuzz/libfuzz.h diff --git a/Makefile b/Makefile index 116be32df..13d6b4de4 100644 --- a/Makefile +++ b/Makefile @@ -42,10 +42,14 @@ VG=VALGRIND=1 valgrind -q --error-exitcode=7 VG_TEST_ARGS = --track-origins=yes --leak-check=full --show-reachable=yes --errors-for-leak-kinds=all endif +SANITIZER_FLAGS := + ifneq ($(ASAN),0) -SANITIZER_FLAGS=-fsanitize=address -else -SANITIZER_FLAGS= +SANITIZER_FLAGS += -fsanitize=address +endif + +ifneq ($(FUZZING), 0) +SANITIZER_FLAGS += -fsanitize=fuzzer-no-link endif ifeq ($(DEVELOPER),1) @@ -208,6 +212,7 @@ WIRE_GEN_DEPS := $(WIRE_GEN) $(wildcard tools/gen/*_template) # These are filled by individual Makefiles ALL_PROGRAMS := ALL_TEST_PROGRAMS := +ALL_FUZZ_TARGETS := ALL_C_SOURCES := ALL_C_HEADERS := gen_header_versions.h gen_version.h @@ -224,6 +229,8 @@ unexport CFLAGS CONFIGURATOR_CC := $(CC) LDFLAGS += $(PIE_LDFLAGS) $(SANITIZER_FLAGS) $(COPTFLAGS) +CFLAGS += $(SANITIZER_FLAGS) + ifeq ($(STATIC),1) # For MacOS, Jacob Rapoport changed this to: # -L/usr/local/lib -Wl,-lgmp -lsqlite3 -lz -Wl,-lm -lpthread -ldl $(COVFLAGS) @@ -308,6 +315,9 @@ include devtools/Makefile include tools/Makefile include plugins/Makefile include tests/plugins/Makefile +ifneq ($(FUZZING),0) + include tests/fuzz/Makefile +endif # We make pretty much everything depend on these. ALL_GEN_HEADERS := $(filter gen%.h %printgen.h %wiregen.h,$(ALL_C_HEADERS)) @@ -473,10 +483,10 @@ gen_header_versions.h: tools/headerversions @tools/headerversions $@ # All binaries require the external libs, ccan and system library versions. -$(ALL_PROGRAMS) $(ALL_TEST_PROGRAMS): $(EXTERNAL_LIBS) $(CCAN_OBJS) +$(ALL_PROGRAMS) $(ALL_TEST_PROGRAMS) $(ALL_FUZZ_TARGETS): $(EXTERNAL_LIBS) $(CCAN_OBJS) # Each test program depends on its own object. -$(ALL_TEST_PROGRAMS): %: %.o +$(ALL_TEST_PROGRAMS) $(ALL_FUZZ_TARGETS): %: %.o # Without this rule, the (built-in) link line contains # external/libwallycore.a directly, which causes a symbol clash (it @@ -485,6 +495,13 @@ $(ALL_TEST_PROGRAMS): %: %.o $(ALL_PROGRAMS) $(ALL_TEST_PROGRAMS): @$(call VERBOSE, "ld $@", $(LINK.o) $(filter-out %.a,$^) $(LOADLIBES) $(EXTERNAL_LDLIBS) $(LDLIBS) -o $@) +# We special case the fuzzing target binaries, as they need to link against libfuzzer, +# which brings its own main(). +FUZZ_LDFLAGS = -fsanitize=fuzzer +$(ALL_FUZZ_TARGETS): + @$(call VERBOSE, "ld $@", $(LINK.o) $(filter-out %.a,$^) $(LOADLIBES) $(EXTERNAL_LDLIBS) $(LDLIBS) $(FUZZ_LDFLAGS) -o $@) + + # Everything depends on the CCAN headers, and Makefile $(CCAN_OBJS) $(CDUMP_OBJS): $(CCAN_HEADERS) Makefile @@ -504,7 +521,7 @@ update-ccan: # Now ALL_PROGRAMS is fully populated, we can expand it. all-programs: $(ALL_PROGRAMS) -all-test-programs: $(ALL_TEST_PROGRAMS) +all-test-programs: $(ALL_TEST_PROGRAMS) $(ALL_FUZZ_TARGETS) distclean: clean $(RM) ccan/config.h config.vars @@ -518,6 +535,7 @@ clean: $(RM) $(CCAN_OBJS) $(CDUMP_OBJS) $(ALL_OBJS) $(RM) $(ALL_PROGRAMS) $(RM) $(ALL_TEST_PROGRAMS) + $(RM) $(ALL_FUZZ_TARGETS) $(RM) gen_*.h */gen_* ccan/tools/configurator/configurator $(RM) ccan/ccan/cdump/tools/cdump-enumstr.o find . -name '*gcda' -delete diff --git a/configure b/configure index 924fd9694..da10c4664 100755 --- a/configure +++ b/configure @@ -120,6 +120,7 @@ set_defaults() CONFIGURATOR_CC=${CONFIGURATOR_CC-$CC} VALGRIND=${VALGRIND:-$(default_valgrind_setting)} TEST_NETWORK=${TEST_NETWORK:-regtest} + FUZZING=${FUZZING:-0} } usage() @@ -155,6 +156,7 @@ usage() echo " Static link sqlite3, gmp and zlib libraries" usage_with_default "--enable/disable-address-sanitizer" "$ASAN" "enable" "disable" echo " Compile with address-sanitizer" + usage_with_default "--enable/disable-fuzzing" "$FUZZING" "enable" "disable" exit 1 } @@ -206,6 +208,8 @@ for opt in "$@"; do --disable-static) STATIC=0;; --enable-address-sanitizer) ASAN=1;; --disable-address-sanitizer) ASAN=0;; + --enable-fuzzing) FUZZING=1;; + --disable-fuzzing) FUZZING=0;; --help|-h) usage;; *) echo "Unknown option '$opt'" >&2 @@ -229,6 +233,18 @@ if [ "$ASAN" = "1" ]; then fi fi +if [ "$FUZZING" = "1" ]; then + case "$CC" in + (*"clang"*) + ;; + (*) + echo "Fuzzing is currently only supported with clang." + exit 1 + ;; + esac +fi + + SQLITE3_CFLAGS="" SQLITE3_LDLIBS="-lsqlite3" if command -v "${PKG_CONFIG}" >/dev/null; then @@ -400,6 +416,7 @@ add_var ASAN "$ASAN" add_var TEST_NETWORK "$TEST_NETWORK" add_var HAVE_PYTHON3_MAKO "$HAVE_PYTHON3_MAKO" add_var SHA256SUM "$SHA256SUM" +add_var FUZZING "$FUZZING" # Hack to avoid sha256 name clash with libwally: will be fixed when that # becomes a standalone shared lib. diff --git a/tests/fuzz/Makefile b/tests/fuzz/Makefile new file mode 100644 index 000000000..ea3b64a21 --- /dev/null +++ b/tests/fuzz/Makefile @@ -0,0 +1,30 @@ +LIBFUZZ_SRC := tests/fuzz/libfuzz.c +LIBFUZZ_HEADERS := $(LIBFUZZ_SRC:.c=.h) +LIBFUZZ_OBJS := $(LIBFUZZ_SRC:.c=.o) + + +FUZZ_TARGETS_SRC := $(wildcard tests/fuzz/fuzz-*.c) +FUZZ_TARGETS_OBJS := $(FUZZ_TARGETS_SRC:.c=.o) +FUZZ_TARGETS_BIN := $(FUZZ_TARGETS_SRC:.c=) + +FUZZ_COMMON_OBJS := \ + common/utils.o +$(FUZZ_TARGETS_OBJS): $(COMMON_HEADERS) $(WIRE_HEADERS) $(COMMON_SRC) +$(FUZZ_TARGETS_BIN): $(LIBFUZZ_OBJS) $(FUZZ_COMMON_OBJS) $(BITCOIN_OBJS) + +tests/fuzz/fuzz-addr: \ + common/amount.o \ + common/addr.o \ + common/base32.o \ + common/bech32.o \ + common/bigsize.o \ + common/json.o \ + common/json_stream.o \ + common/wireaddr.o \ + common/type_to_string.o \ + wire/fromwire.o \ + wire/onion_wiregen.o \ + wire/towire.o + +ALL_C_SOURCES += $(FUZZ_TARGETS_SRC) $(LIBFUZZ_SRC) +ALL_FUZZ_TARGETS += $(FUZZ_TARGETS_BIN) diff --git a/tests/fuzz/fuzz-addr.c b/tests/fuzz/fuzz-addr.c new file mode 100644 index 000000000..aff51d57e --- /dev/null +++ b/tests/fuzz/fuzz-addr.c @@ -0,0 +1,22 @@ +#include "common/utils.h" +#include +#include + +#include +#include +#include + +void init(int *argc, char ***argv) +{ + chainparams = chainparams_for_network("bitcoin"); + common_setup("fuzzer"); +} + +void run(const uint8_t *data, size_t size) +{ + uint8_t *script_pubkey = tal_dup_arr(tmpctx, uint8_t, data, size, 0); + + encode_scriptpubkey_to_addr(tmpctx, chainparams, script_pubkey); + + clean_tmpctx(); +} diff --git a/tests/fuzz/libfuzz.c b/tests/fuzz/libfuzz.c new file mode 100644 index 000000000..81c6f22e7 --- /dev/null +++ b/tests/fuzz/libfuzz.c @@ -0,0 +1,16 @@ +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +int LLVMFuzzerInitialize(int *argc, char ***argv); + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + run(data, size); + + return 0; +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) { + init(argc, argv); + + return 0; +} diff --git a/tests/fuzz/libfuzz.h b/tests/fuzz/libfuzz.h new file mode 100644 index 000000000..b359e7a44 --- /dev/null +++ b/tests/fuzz/libfuzz.h @@ -0,0 +1,18 @@ +#ifndef LIGHTNING_TESTS_FUZZ_LIBFUZZ_H +#define LIGHTNING_TESTS_FUZZ_LIBFUZZ_H + +#include +#include + +/* Called once before running the target. Use it to setup the testing + * environment. */ +void init(int *argc, char ***argv); + +/* The actual target called multiple times with mutated data. */ +void run(const uint8_t *data, size_t size); + +/* Copy an array of chunks from data. */ +const uint8_t **get_chunks(const void *ctx, const uint8_t *data, + size_t data_size, size_t chunk_size); + +#endif /* LIGHTNING_TESTS_FUZZ_LIBFUZZ_H */