fuzz-tests: Add a test for the onion message handler
What changed, and why it matters
This commit only adds a new fuzz test for an existing function that handles incoming onion messages. It does not change the actual message-handling code, so it cannot introduce a security vulnerability by itself. It is a testing improvement.
No action required. This is a defensive testing addition. Consider running the new fuzz target in CI if not already planned.
Security signals we found
No production code modified
New fuzz test for externally reachable handler
Mocks use longjmp to abort on unexpected paths
Evidence from the diff
The commit adds a fuzz target fuzz-handle_onion_message and its Makefile dependencies. It mocks several functions (inject_peer_msg, towire_warningfmt, siphash_seed, ecdh) and feeds arbitrary fuzzer data into handle_onion_message() in connectd/onion_message.c. No changes are made to handle_onion_message() or related production code.
Changed components
tests/fuzz/fuzz-handle_onion_message.ctests/fuzz/MakefileInspect captured patch +117 / −0
diff --git a/tests/fuzz/Makefile b/tests/fuzz/Makefile
index c2440e9e..f016ec91 100644
--- a/tests/fuzz/Makefile
+++ b/tests/fuzz/Makefile
@@ -9,6 +9,16 @@ tests/fuzz/fuzz-hmac-sha256: LDLIBS += -lcrypto
tests/fuzz/fuzz-wire-*.o: tests/fuzz/wire.h
tests/fuzz/fuzz-bolt12-*.o: tests/fuzz/bolt12.h
+tests/fuzz/fuzz-handle_onion_message: common/sphinx.o \
+ common/blindedpath.o \
+ common/hmac.o \
+ common/blinding.o \
+ common/onionreply.o \
+ common/dev_disconnect.o \
+ common/onion_message_parse.o \
+ connectd/onion_message.o \
+ connectd/connectd_wiregen.o
+
FUZZ_TARGETS_SRC := $(wildcard tests/fuzz/fuzz-*.c)
FUZZ_TARGETS_OBJS := $(FUZZ_TARGETS_SRC:.c=.o)
FUZZ_TARGETS_BIN := $(FUZZ_TARGETS_SRC:.c=)
diff --git a/tests/fuzz/fuzz-handle_onion_message.c b/tests/fuzz/fuzz-handle_onion_message.c
new file mode 100644
index 00000000..03112be6
--- /dev/null
+++ b/tests/fuzz/fuzz-handle_onion_message.c
@@ -0,0 +1,107 @@
+#include "config.h"
+#include <fcntl.h>
+#include <setjmp.h>
+#include <secp256k1_ecdh.h>
+#include <common/daemon_conn.h>
+#include <common/ecdh.h>
+#include <common/setup.h>
+#include <common/status.h>
+#include <common/wire_error.h>
+#include <connectd/connectd_wiregen.h>
+#include <connectd/connectd.h>
+#include <connectd/multiplex.h>
+#include <connectd/onion_message.h>
+#include <wire/peer_wiregen.h>
+#include <tests/fuzz/libfuzz.h>
+
+static int lightningd_fd;
+static struct privkey priv;
+static struct siphash_seed siphashseed;
+jmp_buf fuzz_env;
+
+/* MOCKS START */
+void inject_peer_msg(struct peer *peer UNNEEDED, const u8 *msg TAKES UNNEEDED)
+{ longjmp(fuzz_env, 1); }
+
+u8 *towire_warningfmt(const tal_t *ctx UNNEEDED,
+ const struct channel_id *channel UNNEEDED,
+ const char *fmt UNNEEDED, ...)
+{ longjmp(fuzz_env, 1); }
+
+const struct siphash_seed *siphash_seed(void)
+{ return &siphashseed; }
+/* MOCKS END */
+
+void ecdh(const struct pubkey *point, struct secret *ss)
+{
+ assert(secp256k1_ecdh(secp256k1_ctx, ss->data, &point->pubkey,
+ priv.secret.data, NULL, NULL) == 1);
+}
+
+static struct daemon *new_daemon(const tal_t *ctx)
+{
+ struct daemon *daemon = talz(ctx, struct daemon);
+
+ daemon->our_features = tal(ctx, struct feature_set);
+ daemon->our_features->bits[NODE_ANNOUNCE_FEATURE] = tal_arr(ctx, u8, 0);
+ set_feature_bit(&daemon->our_features->bits[NODE_ANNOUNCE_FEATURE], OPT_ONION_MESSAGES);
+
+ daemon->scid_htable = tal(ctx, struct scid_htable);
+ scid_htable_init(daemon->scid_htable);
+
+ daemon->peers = tal(ctx, struct peer_htable);
+ peer_htable_init(daemon->peers);
+
+ memset(&daemon->mykey, 'a', sizeof(daemon->mykey));
+ node_id_from_pubkey(&daemon->id, &daemon->mykey);
+
+ daemon->master = daemon_conn_new(ctx, lightningd_fd, NULL, NULL, daemon);
+
+ return daemon;
+}
+
+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");
+ lightningd_fd = open("/dev/null", O_WRONLY);
+ status_setup_sync(lightningd_fd);
+ chainparams = chainparams_for_network("bitcoin");
+
+ memset(&priv, 'b', sizeof(priv));
+ memset(&siphashseed, 1, sizeof(siphashseed));
+}
+
+void run(const uint8_t *data, size_t size)
+{
+ if (setjmp(fuzz_env) != 0)
+ goto cleanup;
+
+ struct daemon *daemon;
+ struct peer *peer;
+ struct pubkey dummy_key;
+
+ memset(&dummy_key, 'c', sizeof(dummy_key));
+
+ daemon = new_daemon(tmpctx);
+ if (!daemon)
+ goto cleanup;
+
+ peer = talz(tmpctx, struct peer);
+
+ peer->daemon = daemon;
+ node_id_from_pubkey(&peer->id, &dummy_key);
+ peer->onionmsg_incoming_tokens = ONION_MSG_MSEC;
+
+ /* Use fuzzer data as payload of the onion message. */
+ const u8 *onion_msg = towire_onion_message(tmpctx, &dummy_key,
+ tal_dup_arr(tmpctx, u8, data, size, 0));
+
+ handle_onion_message(daemon, peer, onion_msg);
+
+cleanup:
+ if (daemon)
+ tal_free(daemon->master);
+ clean_tmpctx();
+}
Why this scored 12/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.