From 3a7a1fad4eb56522b6ab590e53c695e2fb08e7e2 Mon Sep 17 00:00:00 2001 From: Matt Morehouse Date: Mon, 23 Oct 2023 16:27:35 -0500 Subject: [PATCH] fuzz: add bolt11 cross-over mutator This mutator helped the fuzzer find new coverage and detect the bug fixed in 87f4907bb40a38e06254ef9b9a3600f58f3a3f5b. --- tests/fuzz/fuzz-bolt11.c | 75 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/fuzz/fuzz-bolt11.c b/tests/fuzz/fuzz-bolt11.c index c57b08fe9..c28827aee 100644 --- a/tests/fuzz/fuzz-bolt11.c +++ b/tests/fuzz/fuzz-bolt11.c @@ -17,6 +17,9 @@ size_t LLVMFuzzerMutate(uint8_t *data, size_t size, size_t max_size); size_t LLVMFuzzerCustomMutator(uint8_t *fuzz_data, size_t size, size_t max_size, unsigned int seed); +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"); } @@ -109,6 +112,78 @@ size_t LLVMFuzzerCustomMutator(uint8_t *fuzz_data, size_t size, size_t max_size, return output_len; } +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) +{ + if (in1_size < 9 || in2_size < 9) + return 0; + + // Interpret fuzz inputs as string (ensure it's null terminated). + char *input1 = to_string(tmpctx, in1, in1_size); + char *input2 = to_string(tmpctx, in2, in2_size); + + const size_t max_hrp1 = strlen(input1) - 6; + const size_t max_hrp2 = strlen(input2) - 6; + const size_t max_data1 = max_hrp1 - 2; + const size_t max_data2 = max_hrp2 - 2; + + // Attempt to bech32 decode the inputs. + char *hrp1 = tal_arr(tmpctx, char, max_hrp1); + char *hrp2 = tal_arr(tmpctx, char, max_hrp2); + u5 *data1 = tal_arr(tmpctx, u5, max_data1); + u5 *data2 = tal_arr(tmpctx, u5, max_data2); + + size_t data1_len = 0; + if (bech32_decode(hrp1, data1, &data1_len, input1, (size_t)-1) != + BECH32_ENCODING_BECH32) + // Decoding failed, this should only happen when starting from + // an empty corpus. + return 0; + size_t data2_len = 0; + if (bech32_decode(hrp2, data2, &data2_len, input2, (size_t)-1) != + BECH32_ENCODING_BECH32) + // Decoding failed, this should only happen when starting from + // an empty corpus. + return 0; + + srand(seed); + char *out_hrp; + u5 *out_data; + size_t out_data_len; + if (rand() % 2) { + // Cross-over the HRP. + out_data = data1; + out_data_len = data1_len; + + size_t max_out_hrp_size = max_out_size - data1_len - 8; + out_hrp = tal_arr(tmpctx, char, max_out_hrp_size + 1); + + size_t out_hrp_size = cross_over( + (u8 *)hrp1, strlen(hrp1), (u8 *)hrp2, strlen(hrp2), + (u8 *)out_hrp, max_out_hrp_size, (unsigned)rand()); + out_hrp[out_hrp_size] = '\0'; + } else { + // Cross-over the data part. + out_hrp = hrp1; + + size_t max_out_data_size = max_out_size - strlen(hrp1) - 8; + out_data = tal_arr(tmpctx, u5, max_out_data_size); + + out_data_len = + cross_over(data1, data1_len, data2, data2_len, out_data, + max_out_data_size, (unsigned)rand()); + } + + // Encode the output. + if (!bech32_encode((char *)out, out_hrp, out_data, out_data_len, + max_out_size, BECH32_ENCODING_BECH32)) + return 0; + + clean_tmpctx(); + return strlen((char *)out); +} + void run(const uint8_t *data, size_t size) { char *invoice_str = to_string(tmpctx, data, size);