Makefile: run fuzzing corpora as normal unit tests in non-fuzzing mode.
What changed, and why it matters
This commit changes the build system so that existing fuzz tests can also run as ordinary unit tests in normal (non-fuzzing) builds. It does not fix a bug, change runtime behavior of Core Lightning in production, or introduce any user-facing feature. It is purely a testing/CI infrastructure improvement.
No security action required. Treat as normal build/test infrastructure change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch modifies Makefiles and the fuzz harness so that fuzz targets compile and execute in non-fuzzing builds by providing a small main() in tests/fuzz/libfuzz.c that iterates over stored corpora. It also guards calls to common_setup() in individual fuzz init functions to avoid double-initialization when libfuzz.c already called it. No production code paths are altered.
Changed components
tests/fuzz/Makefiletests/fuzz/libfuzz.ctests/fuzz/*.c fuzz init functionsMakefileInspect captured patch +104 / −17
diff --git a/Makefile b/Makefile
index f665f8a..3cee618 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/common/peer_failed.h b/common/peer_failed.h
index b782e52..5be8ba8 100644
--- a/common/peer_failed.h
+++ b/common/peer_failed.h
@@ -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;
diff --git a/tests/fuzz/Makefile b/tests/fuzz/Makefile
index ce2d397..c2440e9 100644
--- a/tests/fuzz/Makefile
+++ b/tests/fuzz/Makefile
@@ -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
diff --git a/tests/fuzz/bolt12.h b/tests/fuzz/bolt12.h
index b5ef5a7..1189248 100644
--- a/tests/fuzz/bolt12.h
+++ b/tests/fuzz/bolt12.h
@@ -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 */
diff --git a/tests/fuzz/connectd_handshake.h b/tests/fuzz/connectd_handshake.h
index 6412db8..6216e23 100644
--- a/tests/fuzz/connectd_handshake.h
+++ b/tests/fuzz/connectd_handshake.h
@@ -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.
diff --git a/tests/fuzz/fuzz-addr.c b/tests/fuzz/fuzz-addr.c
index 082c549..a833bc2 100644
--- a/tests/fuzz/fuzz-addr.c
+++ b/tests/fuzz/fuzz-addr.c
@@ -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)
diff --git a/tests/fuzz/fuzz-bolt11.c b/tests/fuzz/fuzz-bolt11.c
index 5eeeeda..6f706e0 100644
--- a/tests/fuzz/fuzz-bolt11.c
+++ b/tests/fuzz/fuzz-bolt11.c
@@ -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.
diff --git a/tests/fuzz/fuzz-bolt12-bech32-decode.c b/tests/fuzz/fuzz-bolt12-bech32-decode.c
index bd3d867..43f5fd2 100644
--- a/tests/fuzz/fuzz-bolt12-bech32-decode.c
+++ b/tests/fuzz/fuzz-bolt12-bech32-decode.c
@@ -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)
{
diff --git a/tests/fuzz/fuzz-channel_id.c b/tests/fuzz/fuzz-channel_id.c
index df3d35e..bf2c2d0 100644
--- a/tests/fuzz/fuzz-channel_id.c
+++ b/tests/fuzz/fuzz-channel_id.c
@@ -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)
diff --git a/tests/fuzz/fuzz-close_tx.c b/tests/fuzz/fuzz-close_tx.c
index 0fd29e5..d3f3abf 100644
--- a/tests/fuzz/fuzz-close_tx.c
+++ b/tests/fuzz/fuzz-close_tx.c
@@ -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");
}
diff --git a/tests/fuzz/fuzz-codex32.c b/tests/fuzz/fuzz-codex32.c
index 87e2324..eaaccc3 100644
--- a/tests/fuzz/fuzz-codex32.c
+++ b/tests/fuzz/fuzz-codex32.c
@@ -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 */
diff --git a/tests/fuzz/fuzz-cryptomsg.c b/tests/fuzz/fuzz-cryptomsg.c
index c7b7c64..9b3a47f 100644
--- a/tests/fuzz/fuzz-cryptomsg.c
+++ b/tests/fuzz/fuzz-cryptomsg.c
@@ -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. */
diff --git a/tests/fuzz/fuzz-initial_channel.c b/tests/fuzz/fuzz-initial_channel.c
index 5549b36..2fa04c2 100644
--- a/tests/fuzz/fuzz-initial_channel.c
+++ b/tests/fuzz/fuzz-initial_channel.c
@@ -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");
diff --git a/tests/fuzz/libfuzz.c b/tests/fuzz/libfuzz.c
index 98c5fd0..78ee006 100644
--- a/tests/fuzz/libfuzz.c
+++ b/tests/fuzz/libfuzz.c
@@ -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 */
diff --git a/tests/fuzz/wire.h b/tests/fuzz/wire.h
index 23d1ee1..fb2cfc8 100644
--- a/tests/fuzz/wire.h
+++ b/tests/fuzz/wire.h
@@ -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
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.