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