Makefile: run fuzzing corpora as normal unit tests in non-fuzzing mode.

This means we can make sure the compile and run in normal builds.

Side note: various tests call common_setup(), which means we called it
twice in unit testing mode, so we conditionalize those.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2025-10-22 19:44:30 +10:30
parent a6ea428294
commit 2adfdfd0d9
15 changed files with 104 additions and 17 deletions

View File

@@ -392,10 +392,7 @@ include cln-grpc/Makefile
endif
include plugins/Makefile
include tests/plugins/Makefile
ifneq ($(FUZZING),0)
include tests/fuzz/Makefile
endif
include tests/fuzz/Makefile
ifneq ($V,1)
MSGGEN_ARGS := -s
@@ -709,6 +706,7 @@ endif
# We special case the fuzzing target binaries, as they need to link against libfuzzer,
# which brings its own main().
# FUZZER_LIB and LLVM_LDFLAGS are set by configure script on macOS
ifneq ($(FUZZING),0)
ifeq ($(OS),Darwin)
ifneq ($(FUZZER_LIB),)
FUZZ_LDFLAGS = $(FUZZER_LIB) $(LLVM_LDFLAGS)
@@ -718,9 +716,10 @@ endif
else
FUZZ_LDFLAGS = -fsanitize=fuzzer
endif
endif
$(ALL_FUZZ_TARGETS):
@$(call VERBOSE, "ld $@", $(LINK.o) $(filter-out %.a,$^) $(LOADLIBES) $(EXTERNAL_LDLIBS) $(LDLIBS) libccan.a $(FUZZ_LDFLAGS) -o $@)
@$(call VERBOSE, "ld $@", $(LINK.o) $(filter-out %.a,$^) libcommon.a libccan.a $(LOADLIBES) $(EXTERNAL_LDLIBS) $(LDLIBS) $(FUZZ_LDFLAGS) -o $@)
ifeq ($(OS),Darwin)
@$(call VERBOSE, "dsymutil $@", dsymutil $@)
endif
@@ -841,6 +840,10 @@ update-mocks/%: % $(ALL_GEN_HEADERS) $(ALL_GEN_SOURCES)
unittest/%: % bolt-precheck
BOLTDIR=$(LOCAL_BOLTDIR) $(VG) $(VG_TEST_ARGS) $* > /dev/null
# FIXME: we don't do leak detection on fuzz tests, since they don't have a cleanup function.
fuzzunittest/%: % bolt-precheck
BOLTDIR=$(LOCAL_BOLTDIR) $(VG) $* > /dev/null
# Commands
MKDIR_P = mkdir -p
INSTALL = install

View File

@@ -3,6 +3,7 @@
#include "config.h"
#include <ccan/compiler/compiler.h>
#include <ccan/short_types/short_types.h>
#include <ccan/take/take.h>
struct channel_id;
struct per_peer_state;

View File

@@ -13,8 +13,13 @@ FUZZ_TARGETS_SRC := $(wildcard tests/fuzz/fuzz-*.c)
FUZZ_TARGETS_OBJS := $(FUZZ_TARGETS_SRC:.c=.o)
FUZZ_TARGETS_BIN := $(FUZZ_TARGETS_SRC:.c=)
$(FUZZ_TARGETS_OBJS): $(COMMON_HEADERS) $(WIRE_HEADERS) $(COMMON_SRC)
$(FUZZ_TARGETS_OBJS): $(COMMON_HEADERS) $(WIRE_HEADERS) $(COMMON_SRC) tests/fuzz/libfuzz.h
$(FUZZ_TARGETS_BIN): $(LIBFUZZ_OBJS) libcommon.a
ALL_C_SOURCES += $(FUZZ_TARGETS_SRC) $(LIBFUZZ_SRC)
ALL_FUZZ_TARGETS += $(FUZZ_TARGETS_BIN)
# In non-fuzzing builds, these become normal tests.
ifneq ($(FUZZING),1)
check-units: $(FUZZ_TARGETS_BIN:%=fuzzunittest/%)
endif

View File

@@ -144,6 +144,11 @@ size_t LLVMFuzzerCustomCrossOver(const u8 *data1, size_t size1, const u8 *data2,
return encoded_size;
}
void init(int *argc, char ***argv) { common_setup("fuzzer"); }
void init(int *argc, char ***argv)
{
/* Don't call this if we're in unit-test mode, as libfuzz.c does it */
if (!tmpctx)
common_setup("fuzzer");
}
#endif /* LIGHTNING_TESTS_FUZZ_BOLT12_H */

View File

@@ -74,7 +74,9 @@ void init(int *argc, char ***argv)
assert(devnull >= 0);
status_setup_sync(devnull);
common_setup("fuzzer");
/* Don't call this if we're in unit-test mode, as libfuzz.c does it */
if (!tmpctx)
common_setup("fuzzer");
/* These keys are copied from BOLT 8 test vectors, though we use them in
* a different setting.

View File

@@ -9,7 +9,9 @@
void init(int *argc, char ***argv)
{
chainparams = chainparams_for_network("bitcoin");
common_setup("fuzzer");
/* Don't call this if we're in unit-test mode, as libfuzz.c does it */
if (!tmpctx)
common_setup("fuzzer");
}
void run(const uint8_t *data, size_t size)

View File

@@ -18,7 +18,12 @@ size_t LLVMFuzzerCustomCrossOver(const u8 *in1, size_t in1_size, const u8 *in2,
size_t in2_size, u8 *out, size_t max_out_size,
unsigned seed);
void init(int *argc, char ***argv) { common_setup("fuzzer"); }
void init(int *argc, char ***argv)
{
/* Don't call this if we're in unit-test mode, as libfuzz.c does it */
if (!tmpctx)
common_setup("fuzzer");
}
// Encodes a dummy bolt11 invoice into `fuzz_data` and returns the size of the
// encoded string.

View File

@@ -8,7 +8,12 @@
/* Include bolt12.c directly, to gain access to string_to_data(). */
#include "../../common/bolt12.c"
void init(int *argc, char ***argv) { common_setup("fuzzer"); }
void init(int *argc, char ***argv)
{
/* Don't call this if we're in unit-test mode, as libfuzz.c does it */
if (!tmpctx)
common_setup("fuzzer");
}
void run(const u8 *data, size_t size)
{

View File

@@ -10,7 +10,9 @@
void init(int *argc, char ***argv)
{
common_setup("fuzzer");
/* Don't call this if we're in unit-test mode, as libfuzz.c does it */
if (!tmpctx)
common_setup("fuzzer");
}
void run(const uint8_t *data, size_t size)

View File

@@ -12,7 +12,9 @@
void init(int *argc, char ***argv)
{
common_setup("fuzzer");
/* Don't call this if we're in unit-test mode, as libfuzz.c does it */
if (!tmpctx)
common_setup("fuzzer");
chainparams = chainparams_for_network("bitcoin");
}

View File

@@ -34,7 +34,9 @@ static struct codex32 *codex32_dup(const tal_t *ctx, const struct codex32 *src)
void init(int *argc, char ***argv)
{
common_setup("fuzzer");
/* Don't call this if we're in unit-test mode, as libfuzz.c does it */
if (!tmpctx)
common_setup("fuzzer");
}
/* Custom mutator with structure-aware and byte-level mutations */

View File

@@ -54,7 +54,9 @@ void init(int *argc, char ***argv)
init_cs_in.s_ck = ck;
init_cs_in.r_ck = ck;
common_setup("fuzzer");
/* Don't call this if we're in unit-test mode, as libfuzz.c does it */
if (!tmpctx)
common_setup("fuzzer");
}
/* Test that encrypting and decrypting the message does not alter it. */

View File

@@ -23,7 +23,9 @@
void init(int *argc, char ***argv)
{
common_setup("fuzzer");
/* Don't call this if we're in unit-test mode, as libfuzz.c does it */
if (!tmpctx)
common_setup("fuzzer");
int devnull = open("/dev/null", O_WRONLY);
status_setup_sync(devnull);
chainparams = chainparams_for_network("bitcoin");

View File

@@ -2,10 +2,18 @@
#include <assert.h>
#include <ccan/isaac/isaac64.h>
#include <ccan/short_types/short_types.h>
#include <ccan/tal/grab_file/grab_file.h>
#include <ccan/tal/path/path.h>
#include <ccan/tal/tal.h>
#include <common/pseudorand.h>
#include <common/setup.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <tests/fuzz/libfuzz.h>
#include <unistd.h>
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int LLVMFuzzerInitialize(int *argc, char ***argv);
@@ -118,3 +126,38 @@ size_t cross_over(const u8 *in1, size_t in1_size, const u8 *in2,
max_out_size);
return overwrite_part(in1, in1_size, in2, in2_size, out, max_out_size);
}
/* In non-fuzzing builds, these become unit tests which just run the corpora:
* this is also good for attaching a debugger to! */
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
int main(int argc, char *argv[])
{
DIR *d;
struct dirent *di;
common_setup(argv[0]);
assert(chdir("tests/fuzz/corpora") == 0);
assert(chdir(path_basename(tmpctx, argv[0])) == 0);
/* FIXME: Support explicit path args? */
init(&argc, &argv);
d = opendir(".");
while ((di = readdir(d)) != NULL) {
u8 *contents;
if (streq(di->d_name, ".") || streq(di->d_name, ".."))
continue;
contents = grab_file(tmpctx, di->d_name);
assert(contents);
run(contents, tal_bytelen(contents)-1);
}
closedir(d);
common_shutdown();
}
/* We never call any functions which might call these */
size_t LLVMFuzzerMutate(uint8_t *data, size_t size, size_t max_size);
size_t LLVMFuzzerMutate(uint8_t *data, size_t size, size_t max_size)
{
abort();
}
#endif /* !FUZZING */

View File

@@ -21,7 +21,13 @@ static u8 *prefix_arr(const u8 *data, size_t size, u16 prefix)
}
/* The init function used by all fuzz-wire-* targets. */
void init(int *argc, char ***argv) { common_setup("fuzzer"); dev_towire_allow_invalid_node_id = true; }
void init(int *argc, char ***argv)
{
/* Don't call this if we're in unit-test mode, as libfuzz.c does it */
if (!tmpctx)
common_setup("fuzzer");
dev_towire_allow_invalid_node_id = true;
}
/* Test that decoding arbitrary data does not crash. Then, if the data was
* successfully decoded, test that encoding and decoding the message does not