diff --git a/tests/fuzz/Makefile b/tests/fuzz/Makefile index 2944dddd3..48de3c6dd 100644 --- a/tests/fuzz/Makefile +++ b/tests/fuzz/Makefile @@ -6,6 +6,7 @@ tests/fuzz/fuzz-connectd-handshake-act*.o: tests/fuzz/connectd_handshake.h tests/fuzz/fuzz-ripemd160: LDLIBS += -lcrypto tests/fuzz/fuzz-sha256: LDLIBS += -lcrypto tests/fuzz/fuzz-wire-*.o: tests/fuzz/wire.h +tests/fuzz/fuzz-bolt12-*.o: tests/fuzz/bolt12.h FUZZ_TARGETS_SRC := $(wildcard tests/fuzz/fuzz-*.c) FUZZ_TARGETS_OBJS := $(FUZZ_TARGETS_SRC:.c=.o) diff --git a/tests/fuzz/bolt12.h b/tests/fuzz/bolt12.h new file mode 100644 index 000000000..b5ef5a7fd --- /dev/null +++ b/tests/fuzz/bolt12.h @@ -0,0 +1,149 @@ +/* This header contains custom mutators used by all the fuzz-bolt12-* fuzz + * targets. The actual fuzz targets only need to: + * 1. Define the bech32_hrp string to the expected HRP for inputs. + * 2. Implement the run() function. asdfa + */ +#ifndef LIGHTNING_TESTS_FUZZ_BOLT12_H +#define LIGHTNING_TESTS_FUZZ_BOLT12_H + +#include "config.h" +#include +#include +#include +#include +#include + +/* Include bolt12.c directly, to gain access to string_to_data(). */ +#include "../../common/bolt12.c" + +/* The HRP to use in our custom mutators. For bolt12, this can be "lno", "lnr", + * or "lni". */ +extern const char *bech32_hrp; + +/* Default mutator defined by libFuzzer. */ +size_t LLVMFuzzerMutate(u8 *data, size_t size, size_t max_size); + +/* Custom mutators for use by libFuzzer, defined below. */ +size_t LLVMFuzzerCustomMutator(u8 *fuzz_data, size_t size, size_t max_size, + unsigned int seed); +size_t LLVMFuzzerCustomCrossOver(const u8 *data1, size_t size1, const u8 *data2, + size_t size2, u8 *out, size_t max_size, + unsigned seed); + +/* Encodes a dummy bolt12 offer/invoice-request/invoice into fuzz_data and + * returns the size of the encoded string. */ +static size_t initial_input(u8 *fuzz_data, size_t size, size_t max_size) +{ + static char *dummy; + static size_t dummy_size; + if (!dummy) { + /* Initialize dummy to bech32_hrp followed by '1'. */ + size_t bech32_hrp_len = strlen(bech32_hrp); + dummy = tal_dup_arr(NULL, char, bech32_hrp, bech32_hrp_len, 1); + dummy[bech32_hrp_len] = '1'; + dummy_size = bech32_hrp_len + 1; + } + + size = max_size < dummy_size ? max_size : dummy_size; + memcpy(fuzz_data, dummy, size); + + clean_tmpctx(); + return size; +} + +/* A custom mutator that decodes the bech32 input, mutates the decoded input, + * and then re-encodes the mutated input. This produces an input corpus that + * consists entirely of correctly encoded bech32 strings, enabling efficient + * fuzzing of the bolt12 decoding logic without the fuzzer getting stuck on + * fuzzing the bech32 decoding logic. */ +size_t LLVMFuzzerCustomMutator(u8 *fuzz_data, size_t size, size_t max_size, + unsigned int seed) +{ + const u8 *decoded_data; + size_t decoded_size; + u8 *mutated_data; + size_t mutated_size; + char *encoded_data; + size_t encoded_size; + char *fail; + + /* Decode the input. */ + decoded_data = string_to_data(tmpctx, (char *)fuzz_data, size, + bech32_hrp, &decoded_size, &fail); + if (!decoded_data) + return initial_input(fuzz_data, size, max_size); + if (decoded_size > max_size) + return initial_input(fuzz_data, size, max_size); + + /* Mutate the data part of the decoded input. */ + mutated_data = tal_dup_arr(tmpctx, u8, decoded_data, decoded_size, + max_size - decoded_size); + mutated_size = LLVMFuzzerMutate(mutated_data, decoded_size, max_size); + tal_resize(&mutated_data, mutated_size); + + /* Encode the mutated input. */ + encoded_data = to_bech32_charset(tmpctx, bech32_hrp, mutated_data); + encoded_size = tal_bytelen(encoded_data) - 1; /* Truncate null byte. */ + + if (encoded_size > max_size) + return initial_input(fuzz_data, size, max_size); + + memcpy(fuzz_data, encoded_data, encoded_size); + clean_tmpctx(); + + return encoded_size; +} + +static size_t cross_over_fail(void) +{ + clean_tmpctx(); + return 0; +} + +/* A custom cross-over mutator that decodes the bech32 inputs before cross-over + * mutating them. Like LLVMFuzzerCustomMutator, this enables more efficient + * fuzzing of bolt12 offers, invoice requests, and invoices. */ +size_t LLVMFuzzerCustomCrossOver(const u8 *data1, size_t size1, const u8 *data2, + size_t size2, u8 *out, size_t max_size, + unsigned seed) +{ + const u8 *decoded_data1, *decoded_data2; + size_t decoded_size1, decoded_size2; + u8 *mutated_data; + size_t mutated_size; + char *encoded_data; + size_t encoded_size; + char *fail; + + /* Decode inputs. */ + decoded_data1 = string_to_data(tmpctx, (char *)data1, size1, bech32_hrp, + &decoded_size1, &fail); + if (!decoded_data1) + return cross_over_fail(); + decoded_data2 = string_to_data(tmpctx, (char *)data2, size2, bech32_hrp, + &decoded_size2, &fail); + if (!decoded_data2) + return cross_over_fail(); + + /* Cross-pollinate inputs. */ + mutated_data = tal_arr(tmpctx, u8, max_size); + mutated_size = cross_over(decoded_data1, decoded_size1, decoded_data2, + decoded_size2, mutated_data, max_size, seed); + tal_resize(&mutated_data, mutated_size); + + /* Encode the mutated input. */ + encoded_data = to_bech32_charset(tmpctx, bech32_hrp, mutated_data); + encoded_size = tal_bytelen(encoded_data) - 1; /* Truncate null byte. */ + + if (encoded_size > max_size) + return cross_over_fail(); + + memcpy(out, encoded_data, encoded_size); + clean_tmpctx(); + + return encoded_size; +} + +void init(int *argc, char ***argv) { common_setup("fuzzer"); } + +#endif /* LIGHTNING_TESTS_FUZZ_BOLT12_H */ diff --git a/tests/fuzz/fuzz-bolt12-invoice-decode.c b/tests/fuzz/fuzz-bolt12-invoice-decode.c new file mode 100644 index 000000000..20fde16ed --- /dev/null +++ b/tests/fuzz/fuzz-bolt12-invoice-decode.c @@ -0,0 +1,18 @@ +#include "config.h" +#include +#include +#include +#include +#include + +const char *bech32_hrp = "lni"; + +void run(const u8 *data, size_t size) +{ + char *fail; + + invoice_decode(tmpctx, (const char *)data, size, /*feature_set=*/NULL, + /*must_be_chain=*/NULL, &fail); + + clean_tmpctx(); +} diff --git a/tests/fuzz/fuzz-bolt12-invrequest-decode.c b/tests/fuzz/fuzz-bolt12-invrequest-decode.c new file mode 100644 index 000000000..54bbc50e7 --- /dev/null +++ b/tests/fuzz/fuzz-bolt12-invrequest-decode.c @@ -0,0 +1,18 @@ +#include "config.h" +#include +#include +#include +#include +#include + +const char *bech32_hrp = "lnr"; + +void run(const u8 *data, size_t size) +{ + char *fail; + + invrequest_decode(tmpctx, (const char *)data, size, + /*feature_set=*/NULL, /*must_be_chain=*/NULL, &fail); + + clean_tmpctx(); +} diff --git a/tests/fuzz/fuzz-bolt12-offer-decode.c b/tests/fuzz/fuzz-bolt12-offer-decode.c new file mode 100644 index 000000000..d062b95a6 --- /dev/null +++ b/tests/fuzz/fuzz-bolt12-offer-decode.c @@ -0,0 +1,18 @@ +#include "config.h" +#include +#include +#include +#include +#include + +const char *bech32_hrp = "lno"; + +void run(const u8 *data, size_t size) +{ + char *fail; + + offer_decode(tmpctx, (const char *)data, size, /*feature_set=*/NULL, + /*must_be_chain=*/NULL, &fail); + + clean_tmpctx(); +}