From 7b9e0c889ee7a6817e971f35c5986836341c96d7 Mon Sep 17 00:00:00 2001 From: nsa Date: Mon, 3 Feb 2020 20:43:01 -0500 Subject: [PATCH 1/3] docs: add fuzz.md --- docs/fuzz.md | 54 +++++++++++++++++++ docs/go-fuzz/README.md | 106 ------------------------------------- docs/go-fuzz/corpus.tar.gz | Bin 8850 -> 0 bytes docs/go-fuzz/wirefuzz.go | 54 ------------------- 4 files changed, 54 insertions(+), 160 deletions(-) create mode 100644 docs/fuzz.md delete mode 100644 docs/go-fuzz/README.md delete mode 100644 docs/go-fuzz/corpus.tar.gz delete mode 100644 docs/go-fuzz/wirefuzz.go diff --git a/docs/fuzz.md b/docs/fuzz.md new file mode 100644 index 00000000..17634462 --- /dev/null +++ b/docs/fuzz.md @@ -0,0 +1,54 @@ +# Fuzzing LND # + +The `fuzz` package is organized into subpackages which are named after the `lnd` package they test. Each subpackage has its own set of fuzz targets. + +### Setup and Installation ### +This section will cover setup and installation of `go-fuzz` and fuzzing binaries. + +* First, we must get `go-fuzz`. +``` +$ go get -u github.com/dvyukov/go-fuzz/... +``` +* The following is a command to build all fuzzing harnesses for a specific package. +``` +$ cd fuzz/ +$ find * -maxdepth 1 -regex '[A-Za-z0-9\-_.]'* -not -name fuzz_utils.go | sed 's/\.go$//1' | xargs -I % sh -c 'go-fuzz-build -func Fuzz_% -o -%-fuzz.zip github.com/lightningnetwork/lnd/fuzz/' +``` + +* This may take a while since this will create zip files associated with each fuzzing target. + +* Now, run `go-fuzz` with `workdir` set as below! +``` +$ go-fuzz -bin=<.zip archive here> -workdir= -procs= +``` + +`go-fuzz` will print out log lines every couple of seconds. Example output: +``` +2017/09/19 17:44:23 workers: 8, corpus: 23 (3s ago), crashers: 1, restarts: 1/748, execs: 400690 (16694/sec), cover: 394, uptime: 24s +``` +Corpus is the number of items in the corpus. `go-fuzz` may add valid inputs to +the corpus in an attempt to gain more coverage. Crashers is the number of inputs +resulting in a crash. The inputs, and their outputs are logged in: +`fuzz///crashers`. `go-fuzz` also creates a `suppressions` directory +of stacktraces to ignore so that it doesn't create duplicate stacktraces. +Cover is a number representing edge coverage of the program being fuzzed. + +### Brontide ### +The brontide fuzzers need to be run with a `-timeout` flag of 20 seconds or greater since there is a lot of machine state that must be printed on panic. + +### Corpus ### +Fuzzing generally works best with a corpus that is of minimal size while achieving the maximum coverage. However, `go-fuzz` automatically minimizes the corpus in-memory before fuzzing so a large corpus shouldn't make a difference - edge coverage is all that really matters. + +### Test Harness ### +If you take a look at the test harnesses that are used, you will see that they all consist of one function: +``` +func Fuzz(data []byte) int +``` +If: + +- `-1` is returned, the fuzzing input is ignored +- `0` is returned, `go-fuzz` will add the input to the corpus and deprioritize it in future mutations. +- `1` is returned, `go-fuzz` will add the input to the corpus and prioritize it in future mutations. + +### Conclusion ### +Citizens, do your part and `go-fuzz` `lnd` today! diff --git a/docs/go-fuzz/README.md b/docs/go-fuzz/README.md deleted file mode 100644 index 38f9e806..00000000 --- a/docs/go-fuzz/README.md +++ /dev/null @@ -1,106 +0,0 @@ -# How to fuzz the Lightning Network Daemon's wire protocol using go-fuzz # - -This document will describe how to use the fuzz-testing library `go-fuzz` on -the `lnd` wire protocol. - -### Introduction ### - -Lnd uses its own wire protocol to send and receive messages of all types. There -are 22 different message types, each with their own specific format. If a -message is not in the correct format, `lnd` should logically reject the message -and throw an error. But what if it doesn't? What if we could sneakily craft a -custom message that could pass all the necessary checks and cause an error to -go undetected? Chaos would ensue. However, crafting such a message would require -an in-depth understanding of the many different cogs that make the wire protocol -tick. - -A better solution is fuzz-testing. Fuzz-testing or fuzzing is when a program -known as a fuzzer generates many, many inputs to a function or program in an -attempt to cause it to crash. Fuzzing is surprisingly effective at finding bugs -and a particular fuzzing program `AFL` is well-known for the amount of bugs it -has found with its learned approach. The library we will be using, `go-fuzz`, is -based on `AFL` and has quite a track record of finding bugs in a diverse set of -go programs. `go-fuzz` takes a coverage-guided approach in an attempt to cover -as many code paths as possible on an attack surface. We give `go-fuzz` real, -valid inputs and it will essentially change bits until it achieves a crash! -After reading this document, you too may be able to find errors in `lnd` with -`go-fuzz`! - -### Setup and Installation ### -This section will cover setup and installation of `go-fuzz`. - -* First, we must get `go-fuzz`: -``` -$ go get github.com/dvyukov/go-fuzz/go-fuzz -$ go get github.com/dvyukov/go-fuzz/go-fuzz-build -``` -* Next, create a folder in the `lnwire` package. You can name it whatever. -``` -$ mkdir lnwire/ -``` -* Unzip `corpus.tar.gz` in the `docs/go-fuzz` folder and move it to the folder you just made. -``` -$ tar -xzf docs/go-fuzz/corpus.tar.gz -$ mv corpus lnwire/ -``` -* Now, move `wirefuzz.go` to the same folder you just created. -``` -$ mv docs/go-fuzz/wirefuzz.go lnwire/ -``` -* Change the package name in `wirefuzz.go` from `wirefuzz` to ``. -* Build the test program - this produces a `-fuzz.zip` (archive) file. -``` -$ go-fuzz-build github.com/lightningnetwork/lnd/lnwire/ -``` -* Now, run `go-fuzz`!!! -``` -$ go-fuzz -bin=<.zip archive here> -workdir=lnwire/ -``` - -`go-fuzz` will print out log lines every couple of seconds. Example output: -``` -2017/09/19 17:44:23 slaves: 8, corpus: 23 (3s ago), crashers: 1, restarts: 1/748, execs: 400690 (16694/sec), cover: 394, uptime: 24s -``` -Corpus is the number of items in the corpus. `go-fuzz` may add valid inputs to -the corpus in an attempt to gain more coverage. Crashers is the number of inputs -resulting in a crash. The inputs, and their outputs are logged in: -`/crashers`. `go-fuzz` also creates a `suppressions` directory -of stacktraces to ignore so that it doesn't create duplicate stacktraces. -Cover is a number representing coverage of the program being fuzzed. When I ran -this earlier, `go-fuzz` found two bugs ([#310](https://github.com/lightningnetwork/lnd/pull/310) and [#312](https://github.com/lightningnetwork/lnd/pull/312)) within minutes! - -### Corpus Notes ### -You may wonder how I made the corpus that you unzipped in the previous step. -It's quite simple really. For every message type that `lnwire_test.go` -processed in `TestLightningWireProtocol`, I logged it (in `[]byte` format) to -a .txt file. Within minutes, I had a corpus of valid `lnwire` messages that -I could use with `go-fuzz`! `go-fuzz` will alter these valid messages to create -the sneakily crafted message that I described in the introduction that manages -to bypass validation checks and crash the program. I ran `go-fuzz` for several -hours on the corpus I generated and found two bugs. I believe I have exhausted -the current corpus, but there are still perhaps possible malicious inputs that -`go-fuzz` has not yet reached and could reach with a slightly different generated -corpus. - -### Test Harness ### -If you take a look at the test harness that I used, `wirefuzz.go`, you will see -that it consists of one function: `func Fuzz(data []byte) int`. `go-fuzz` requires -that each input in the corpus is in `[]byte` format. The test harness is also -quite simple. It reads in `[]byte` messages into `lnwire.Message` objects, -serializes them into a buffer, deserializes them back into `lnwire.Message` objects -and asserts their equality. If the pre-serialization and post-deserialization -`lnwire.Message` objects are not equal, the wire protocol has encountered a bug. -Wherever a `0` is returned, `go-fuzz` will ignore that input as it has reached -an unimportant code path caused by the parser catching the error. If a `1` is -returned, the `[]byte` input was parsed successfully and the two `lnwire.Message` -objects were indeed equal. This `[]byte` input is then added to the corpus as -a valid message. If a `panic` is reached, serialization or deserialization failed -and `go-fuzz` may have found a bug. - -### Conclusion ### -Fuzzing is a powerful and quick way to find bugs in programs that works especially -well with protocols where there is a strict format with validation rules. Fuzzing -is important as an automated security tool and can find real bugs in real-world -software. The fuzzing of `lnd` is by no means complete and there exist probably -many more bugs in the software that may `go` undetected if left unfuzzed. Citizens, -do your part and `go-fuzz` `lnd` today! diff --git a/docs/go-fuzz/corpus.tar.gz b/docs/go-fuzz/corpus.tar.gz deleted file mode 100644 index da6a0ea683366f1c7468adacc308362636b7d75e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8850 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 msg.MaxPayloadLength(0) { - // Ignore this input - max payload constraint violated - return 0 - } - - // Deserialize the message from the serialized bytes buffer and - // assert that the original message is equal to the newly deserialized message. - newMsg, err := lnwire.ReadMessage(&b, 0) - if err != nil { - // Could not deserialize message from bytes buffer, panic - panic(err) - } - if !reflect.DeepEqual(msg, newMsg) { - // Deserialized message and original message are not deeply equal - panic(fmt.Errorf("Deserialized message and original message " + - "are not deeply equal.")) - } - - return 1 -} From 5a03fe572f8076dcd78d508af979915cae76c71b Mon Sep 17 00:00:00 2001 From: nsa Date: Mon, 3 Feb 2020 20:43:22 -0500 Subject: [PATCH 2/3] lnwire: move zero-length queryBody check to zlib case --- lnwire/query_short_chan_ids.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lnwire/query_short_chan_ids.go b/lnwire/query_short_chan_ids.go index 91f25227..76729364 100644 --- a/lnwire/query_short_chan_ids.go +++ b/lnwire/query_short_chan_ids.go @@ -148,13 +148,6 @@ func decodeShortChanIDs(r io.Reader) (ShortChanIDEncoding, []ShortChannelID, err // as that was just the encoding type. queryBody = queryBody[1:] - // At this point, if there's no body remaining, then only the encoding - // type was specified, meaning that there're no further bytes to be - // parsed. - if len(queryBody) == 0 { - return encodingType, nil, nil - } - // Otherwise, depending on the encoding type, we'll decode the encode // short channel ID's in a different manner. switch encodingType { @@ -210,6 +203,13 @@ func decodeShortChanIDs(r io.Reader) (ShortChanIDEncoding, []ShortChannelID, err zlibDecodeMtx.Lock() defer zlibDecodeMtx.Unlock() + // At this point, if there's no body remaining, then only the encoding + // type was specified, meaning that there're no further bytes to be + // parsed. + if len(queryBody) == 0 { + return encodingType, nil, nil + } + // Before we start to decode, we'll create a limit reader over // the current reader. This will ensure that we can control how // much memory we're allocating during the decoding process. From 363bdc42b0d8690447080f5c2de11be14a760f95 Mon Sep 17 00:00:00 2001 From: nsa Date: Mon, 3 Feb 2020 20:44:22 -0500 Subject: [PATCH 3/3] fuzz/lnwire: adding fuzz harnesses for all lnwire messages + zlib --- fuzz/lnwire/accept_channel.go | 135 +++++++++++++++++++ fuzz/lnwire/announce_signatures.go | 20 +++ fuzz/lnwire/channel_announcement.go | 20 +++ fuzz/lnwire/channel_reestablish.go | 20 +++ fuzz/lnwire/channel_update.go | 20 +++ fuzz/lnwire/closing_signed.go | 20 +++ fuzz/lnwire/commit_sig.go | 20 +++ fuzz/lnwire/error.go | 20 +++ fuzz/lnwire/funding_created.go | 20 +++ fuzz/lnwire/funding_locked.go | 20 +++ fuzz/lnwire/funding_signed.go | 20 +++ fuzz/lnwire/fuzz_utils.go | 72 +++++++++++ fuzz/lnwire/gossip_timestamp_range.go | 20 +++ fuzz/lnwire/init.go | 20 +++ fuzz/lnwire/node_announcement.go | 112 ++++++++++++++++ fuzz/lnwire/open_channel.go | 151 ++++++++++++++++++++++ fuzz/lnwire/ping.go | 20 +++ fuzz/lnwire/pong.go | 20 +++ fuzz/lnwire/query_channel_range.go | 20 +++ fuzz/lnwire/query_short_chan_ids.go | 20 +++ fuzz/lnwire/query_short_chan_ids_zlib.go | 51 ++++++++ fuzz/lnwire/reply_channel_range.go | 20 +++ fuzz/lnwire/reply_channel_range_zlib.go | 59 +++++++++ fuzz/lnwire/reply_short_chan_ids_end.go | 20 +++ fuzz/lnwire/revoke_and_ack.go | 20 +++ fuzz/lnwire/shutdown.go | 20 +++ fuzz/lnwire/update_add_htlc.go | 20 +++ fuzz/lnwire/update_fail_htlc.go | 20 +++ fuzz/lnwire/update_fail_malformed_htlc.go | 20 +++ fuzz/lnwire/update_fee.go | 20 +++ fuzz/lnwire/update_fulfill_htlc.go | 20 +++ 31 files changed, 1080 insertions(+) create mode 100644 fuzz/lnwire/accept_channel.go create mode 100644 fuzz/lnwire/announce_signatures.go create mode 100644 fuzz/lnwire/channel_announcement.go create mode 100644 fuzz/lnwire/channel_reestablish.go create mode 100644 fuzz/lnwire/channel_update.go create mode 100644 fuzz/lnwire/closing_signed.go create mode 100644 fuzz/lnwire/commit_sig.go create mode 100644 fuzz/lnwire/error.go create mode 100644 fuzz/lnwire/funding_created.go create mode 100644 fuzz/lnwire/funding_locked.go create mode 100644 fuzz/lnwire/funding_signed.go create mode 100644 fuzz/lnwire/fuzz_utils.go create mode 100644 fuzz/lnwire/gossip_timestamp_range.go create mode 100644 fuzz/lnwire/init.go create mode 100644 fuzz/lnwire/node_announcement.go create mode 100644 fuzz/lnwire/open_channel.go create mode 100644 fuzz/lnwire/ping.go create mode 100644 fuzz/lnwire/pong.go create mode 100644 fuzz/lnwire/query_channel_range.go create mode 100644 fuzz/lnwire/query_short_chan_ids.go create mode 100644 fuzz/lnwire/query_short_chan_ids_zlib.go create mode 100644 fuzz/lnwire/reply_channel_range.go create mode 100644 fuzz/lnwire/reply_channel_range_zlib.go create mode 100644 fuzz/lnwire/reply_short_chan_ids_end.go create mode 100644 fuzz/lnwire/revoke_and_ack.go create mode 100644 fuzz/lnwire/shutdown.go create mode 100644 fuzz/lnwire/update_add_htlc.go create mode 100644 fuzz/lnwire/update_fail_htlc.go create mode 100644 fuzz/lnwire/update_fail_malformed_htlc.go create mode 100644 fuzz/lnwire/update_fee.go create mode 100644 fuzz/lnwire/update_fulfill_htlc.go diff --git a/fuzz/lnwire/accept_channel.go b/fuzz/lnwire/accept_channel.go new file mode 100644 index 00000000..1f702825 --- /dev/null +++ b/fuzz/lnwire/accept_channel.go @@ -0,0 +1,135 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "bytes" + + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_accept_channel is used by go-fuzz. +func Fuzz_accept_channel(data []byte) int { + // Prefix with MsgAcceptChannel. + data = prefixWithMsgType(data, lnwire.MsgAcceptChannel) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.AcceptChannel{} + + // We have to do this here instead of in fuzz.Harness so that + // reflect.DeepEqual isn't called. Because of the UpfrontShutdownScript + // encoding, the first message and second message aren't deeply equal since + // the first has a nil slice and the other has an empty slice. + + // Create a reader with the byte array. + r := bytes.NewReader(data) + + // Make sure byte array length (excluding 2 bytes for message type) is + // less than max payload size for the wire message. We check this because + // otherwise `go-fuzz` will keep creating inputs that crash on ReadMessage + // due to a large message size. + payloadLen := uint32(len(data)) - 2 + if payloadLen > emptyMsg.MaxPayloadLength(0) { + // Ignore this input - max payload constraint violated. + return -1 + } + + msg, err := lnwire.ReadMessage(r, 0) + if err != nil { + // go-fuzz generated []byte that cannot be represented as a + // wire message but we will return 0 so go-fuzz can modify the + // input. + return 0 + } + + // We will serialize the message into a new bytes buffer. + var b bytes.Buffer + if _, err := lnwire.WriteMessage(&b, msg, 0); err != nil { + // Could not serialize message into bytes buffer, panic + panic(err) + } + + // Deserialize the message from the serialized bytes buffer, and then + // assert that the original message is equal to the newly deserialized + // message. + newMsg, err := lnwire.ReadMessage(&b, 0) + if err != nil { + // Could not deserialize message from bytes buffer, panic + panic(err) + } + + // Now compare every field instead of using reflect.DeepEqual. + // For UpfrontShutdownScript, we only compare bytes. This probably takes + // up more branches than necessary, but that's fine for now. + var shouldPanic bool + first := msg.(*lnwire.AcceptChannel) + second := newMsg.(*lnwire.AcceptChannel) + + if !bytes.Equal(first.PendingChannelID[:], second.PendingChannelID[:]) { + shouldPanic = true + } + + if first.DustLimit != second.DustLimit { + shouldPanic = true + } + + if first.MaxValueInFlight != second.MaxValueInFlight { + shouldPanic = true + } + + if first.ChannelReserve != second.ChannelReserve { + shouldPanic = true + } + + if first.HtlcMinimum != second.HtlcMinimum { + shouldPanic = true + } + + if first.MinAcceptDepth != second.MinAcceptDepth { + shouldPanic = true + } + + if first.CsvDelay != second.CsvDelay { + shouldPanic = true + } + + if first.MaxAcceptedHTLCs != second.MaxAcceptedHTLCs { + shouldPanic = true + } + + if !first.FundingKey.IsEqual(second.FundingKey) { + shouldPanic = true + } + + if !first.RevocationPoint.IsEqual(second.RevocationPoint) { + shouldPanic = true + } + + if !first.PaymentPoint.IsEqual(second.PaymentPoint) { + shouldPanic = true + } + + if !first.DelayedPaymentPoint.IsEqual(second.DelayedPaymentPoint) { + shouldPanic = true + } + + if !first.HtlcPoint.IsEqual(second.HtlcPoint) { + shouldPanic = true + } + + if !first.FirstCommitmentPoint.IsEqual(second.FirstCommitmentPoint) { + shouldPanic = true + } + + if !bytes.Equal(first.UpfrontShutdownScript, second.UpfrontShutdownScript) { + shouldPanic = true + } + + if shouldPanic { + panic("original message and deserialized message are not equal") + } + + // Add this input to the corpus. + return 1 +} diff --git a/fuzz/lnwire/announce_signatures.go b/fuzz/lnwire/announce_signatures.go new file mode 100644 index 00000000..048cf5ab --- /dev/null +++ b/fuzz/lnwire/announce_signatures.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_announce_signatures is used by go-fuzz. +func Fuzz_announce_signatures(data []byte) int { + // Prefix with MsgAnnounceSignatures. + data = prefixWithMsgType(data, lnwire.MsgAnnounceSignatures) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.AnnounceSignatures{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/channel_announcement.go b/fuzz/lnwire/channel_announcement.go new file mode 100644 index 00000000..771df5d1 --- /dev/null +++ b/fuzz/lnwire/channel_announcement.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_channel_announcement is used by go-fuzz. +func Fuzz_channel_announcement(data []byte) int { + // Prefix with MsgChannelAnnouncement. + data = prefixWithMsgType(data, lnwire.MsgChannelAnnouncement) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.ChannelAnnouncement{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/channel_reestablish.go b/fuzz/lnwire/channel_reestablish.go new file mode 100644 index 00000000..08cca9a5 --- /dev/null +++ b/fuzz/lnwire/channel_reestablish.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_channel_reestablish is used by go-fuzz. +func Fuzz_channel_reestablish(data []byte) int { + // Prefix with MsgChannelReestablish. + data = prefixWithMsgType(data, lnwire.MsgChannelReestablish) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.ChannelReestablish{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/channel_update.go b/fuzz/lnwire/channel_update.go new file mode 100644 index 00000000..993181f7 --- /dev/null +++ b/fuzz/lnwire/channel_update.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_channel_update is used by go-fuzz. +func Fuzz_channel_update(data []byte) int { + // Prefix with MsgChannelUpdate. + data = prefixWithMsgType(data, lnwire.MsgChannelUpdate) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.ChannelUpdate{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/closing_signed.go b/fuzz/lnwire/closing_signed.go new file mode 100644 index 00000000..b6898a7c --- /dev/null +++ b/fuzz/lnwire/closing_signed.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_closing_signed is used by go-fuzz. +func Fuzz_closing_signed(data []byte) int { + // Prefix with MsgClosingSigned. + data = prefixWithMsgType(data, lnwire.MsgClosingSigned) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.ClosingSigned{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/commit_sig.go b/fuzz/lnwire/commit_sig.go new file mode 100644 index 00000000..6f9c76ec --- /dev/null +++ b/fuzz/lnwire/commit_sig.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_commit_sig is used by go-fuzz. +func Fuzz_commit_sig(data []byte) int { + // Prefix with MsgCommitSig. + data = prefixWithMsgType(data, lnwire.MsgCommitSig) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.CommitSig{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/error.go b/fuzz/lnwire/error.go new file mode 100644 index 00000000..8b5dd671 --- /dev/null +++ b/fuzz/lnwire/error.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_error is used by go-fuzz. +func Fuzz_error(data []byte) int { + // Prefix with MsgError. + data = prefixWithMsgType(data, lnwire.MsgError) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.Error{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/funding_created.go b/fuzz/lnwire/funding_created.go new file mode 100644 index 00000000..ffdb3390 --- /dev/null +++ b/fuzz/lnwire/funding_created.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_funding_created is used by go-fuzz. +func Fuzz_funding_created(data []byte) int { + // Prefix with MsgFundingCreated. + data = prefixWithMsgType(data, lnwire.MsgFundingCreated) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.FundingCreated{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/funding_locked.go b/fuzz/lnwire/funding_locked.go new file mode 100644 index 00000000..aa5d2c26 --- /dev/null +++ b/fuzz/lnwire/funding_locked.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_funding_locked is used by go-fuzz. +func Fuzz_funding_locked(data []byte) int { + // Prefix with MsgFundingLocked. + data = prefixWithMsgType(data, lnwire.MsgFundingLocked) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.FundingLocked{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/funding_signed.go b/fuzz/lnwire/funding_signed.go new file mode 100644 index 00000000..47f2bfd0 --- /dev/null +++ b/fuzz/lnwire/funding_signed.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_funding_signed is used by go-fuzz. +func Fuzz_funding_signed(data []byte) int { + // Prefix with MsgFundingSigned. + prefixWithMsgType(data, lnwire.MsgFundingSigned) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.FundingSigned{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/fuzz_utils.go b/fuzz/lnwire/fuzz_utils.go new file mode 100644 index 00000000..767f602a --- /dev/null +++ b/fuzz/lnwire/fuzz_utils.go @@ -0,0 +1,72 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "bytes" + "encoding/binary" + "reflect" + + "github.com/lightningnetwork/lnd/lnwire" +) + +// prefixWithMsgType takes []byte and adds a wire protocol prefix +// to make the []byte into an actual message to be used in fuzzing. +func prefixWithMsgType(data []byte, prefix lnwire.MessageType) []byte { + var prefixBytes [2]byte + binary.BigEndian.PutUint16(prefixBytes[:], uint16(prefix)) + data = append(prefixBytes[:], data...) + return data +} + +// harness performs the actual fuzz testing of the appropriate wire message. +// This function will check that the passed-in message passes wire length checks, +// is a valid message once deserialized, and passes a sequence of serialization +// and deserialization checks. Returns an int that determines whether the input +// is unique or not. +func harness(data []byte, emptyMsg lnwire.Message) int { + // Create a reader with the byte array. + r := bytes.NewReader(data) + + // Make sure byte array length (excluding 2 bytes for message type) is + // less than max payload size for the wire message. We check this because + // otherwise `go-fuzz` will keep creating inputs that crash on ReadMessage + // due to a large message size. + payloadLen := uint32(len(data)) - 2 + if payloadLen > emptyMsg.MaxPayloadLength(0) { + // Ignore this input - max payload constraint violated. + return -1 + } + + msg, err := lnwire.ReadMessage(r, 0) + if err != nil { + // go-fuzz generated []byte that cannot be represented as a + // wire message but we will return 0 so go-fuzz can modify the + // input. + return 0 + } + + // We will serialize the message into a new bytes buffer. + var b bytes.Buffer + if _, err := lnwire.WriteMessage(&b, msg, 0); err != nil { + // Could not serialize message into bytes buffer, panic + panic(err) + } + + // Deserialize the message from the serialized bytes buffer, and then + // assert that the original message is equal to the newly deserialized + // message. + newMsg, err := lnwire.ReadMessage(&b, 0) + if err != nil { + // Could not deserialize message from bytes buffer, panic + panic(err) + } + + if !reflect.DeepEqual(msg, newMsg) { + // Deserialized message and original message are not deeply equal. + panic("original message and deserialized message are not deeply equal") + } + + // Add this input to the corpus. + return 1 +} diff --git a/fuzz/lnwire/gossip_timestamp_range.go b/fuzz/lnwire/gossip_timestamp_range.go new file mode 100644 index 00000000..9e5e4f11 --- /dev/null +++ b/fuzz/lnwire/gossip_timestamp_range.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_gossip_timestamp_range is used by go-fuzz. +func Fuzz_gossip_timestamp_range(data []byte) int { + // Prefix with MsgGossipTimestampRange. + data = prefixWithMsgType(data, lnwire.MsgGossipTimestampRange) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.GossipTimestampRange{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/init.go b/fuzz/lnwire/init.go new file mode 100644 index 00000000..b2be1aba --- /dev/null +++ b/fuzz/lnwire/init.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_init is used by go-fuzz. +func Fuzz_init(data []byte) int { + // Prefix with MsgInit. + data = prefixWithMsgType(data, lnwire.MsgInit) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.Init{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/node_announcement.go b/fuzz/lnwire/node_announcement.go new file mode 100644 index 00000000..76eabe35 --- /dev/null +++ b/fuzz/lnwire/node_announcement.go @@ -0,0 +1,112 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "bytes" + "reflect" + + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_node_announcement is used by go-fuzz. +func Fuzz_node_announcement(data []byte) int { + // Prefix with MsgNodeAnnouncement. + data = prefixWithMsgType(data, lnwire.MsgNodeAnnouncement) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.NodeAnnouncement{} + + // We have to do this here instead of in fuzz.Harness so that + // reflect.DeepEqual isn't called. Address (de)serialization messes up + // the fuzzing assertions. + + // Create a reader with the byte array. + r := bytes.NewReader(data) + + // Make sure byte array length (excluding 2 bytes for message type) is + // less than max payload size for the wire message. We check this because + // otherwise `go-fuzz` will keep creating inputs that crash on ReadMessage + // due to a large message size. + payloadLen := uint32(len(data)) - 2 + if payloadLen > emptyMsg.MaxPayloadLength(0) { + // Ignore this input - max payload constraint violated. + return -1 + } + + msg, err := lnwire.ReadMessage(r, 0) + if err != nil { + // go-fuzz generated []byte that cannot be represented as a + // wire message but we will return 0 so go-fuzz can modify the + // input. + return 0 + } + + // We will serialize the message into a new bytes buffer. + var b bytes.Buffer + if _, err := lnwire.WriteMessage(&b, msg, 0); err != nil { + // Could not serialize message into bytes buffer, panic + panic(err) + } + + // Deserialize the message from the serialized bytes buffer, and then + // assert that the original message is equal to the newly deserialized + // message. + newMsg, err := lnwire.ReadMessage(&b, 0) + if err != nil { + // Could not deserialize message from bytes buffer, panic + panic(err) + } + + // Now compare every field instead of using reflect.DeepEqual for the + // Addresses field. + var shouldPanic bool + first := msg.(*lnwire.NodeAnnouncement) + second := newMsg.(*lnwire.NodeAnnouncement) + if !bytes.Equal(first.Signature[:], second.Signature[:]) { + shouldPanic = true + } + + if !reflect.DeepEqual(first.Features, second.Features) { + shouldPanic = true + } + + if first.Timestamp != second.Timestamp { + shouldPanic = true + } + + if !bytes.Equal(first.NodeID[:], second.NodeID[:]) { + shouldPanic = true + } + + if !reflect.DeepEqual(first.RGBColor, second.RGBColor) { + shouldPanic = true + } + + if !bytes.Equal(first.Alias[:], second.Alias[:]) { + shouldPanic = true + } + + if len(first.Addresses) != len(second.Addresses) { + shouldPanic = true + } + + for i := range first.Addresses { + if first.Addresses[i].String() != second.Addresses[i].String() { + shouldPanic = true + break + } + } + + if !reflect.DeepEqual(first.ExtraOpaqueData, second.ExtraOpaqueData) { + shouldPanic = true + } + + if shouldPanic { + panic("original message and deserialized message are not equal") + } + + // Add this input to the corpus. + return 1 +} diff --git a/fuzz/lnwire/open_channel.go b/fuzz/lnwire/open_channel.go new file mode 100644 index 00000000..ddd268cc --- /dev/null +++ b/fuzz/lnwire/open_channel.go @@ -0,0 +1,151 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "bytes" + + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_open_channel is used by go-fuzz. +func Fuzz_open_channel(data []byte) int { + // Prefix with MsgOpenChannel. + data = prefixWithMsgType(data, lnwire.MsgOpenChannel) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.OpenChannel{} + + // We have to do this here instead of in fuzz.Harness so that + // reflect.DeepEqual isn't called. Because of the UpfrontShutdownScript + // encoding, the first message and second message aren't deeply equal since + // the first has a nil slice and the other has an empty slice. + + // Create a reader with the byte array. + r := bytes.NewReader(data) + + // Make sure byte array length (excluding 2 bytes for message type) is + // less than max payload size for the wire message. We check this because + // otherwise `go-fuzz` will keep creating inputs that crash on ReadMessage + // due to a large message size. + payloadLen := uint32(len(data)) - 2 + if payloadLen > emptyMsg.MaxPayloadLength(0) { + // Ignore this input - max payload constraint violated. + return -1 + } + + msg, err := lnwire.ReadMessage(r, 0) + if err != nil { + // go-fuzz generated []byte that cannot be represented as a + // wire message but we will return 0 so go-fuzz can modify the + // input. + return 0 + } + + // We will serialize the message into a new bytes buffer. + var b bytes.Buffer + if _, err := lnwire.WriteMessage(&b, msg, 0); err != nil { + // Could not serialize message into bytes buffer, panic + panic(err) + } + + // Deserialize the message from the serialized bytes buffer, and then + // assert that the original message is equal to the newly deserialized + // message. + newMsg, err := lnwire.ReadMessage(&b, 0) + if err != nil { + // Could not deserialize message from bytes buffer, panic + panic(err) + } + + // Now compare every field instead of using reflect.DeepEqual. + // For UpfrontShutdownScript, we only compare bytes. This probably takes + // up more branches than necessary, but that's fine for now. + var shouldPanic bool + first := msg.(*lnwire.OpenChannel) + second := newMsg.(*lnwire.OpenChannel) + + if !first.ChainHash.IsEqual(&second.ChainHash) { + shouldPanic = true + } + + if !bytes.Equal(first.PendingChannelID[:], second.PendingChannelID[:]) { + shouldPanic = true + } + + if first.FundingAmount != second.FundingAmount { + shouldPanic = true + } + + if first.PushAmount != second.PushAmount { + shouldPanic = true + } + + if first.DustLimit != second.DustLimit { + shouldPanic = true + } + + if first.MaxValueInFlight != second.MaxValueInFlight { + shouldPanic = true + } + + if first.ChannelReserve != second.ChannelReserve { + shouldPanic = true + } + + if first.HtlcMinimum != second.HtlcMinimum { + shouldPanic = true + } + + if first.FeePerKiloWeight != second.FeePerKiloWeight { + shouldPanic = true + } + + if first.CsvDelay != second.CsvDelay { + shouldPanic = true + } + + if first.MaxAcceptedHTLCs != second.MaxAcceptedHTLCs { + shouldPanic = true + } + + if !first.FundingKey.IsEqual(second.FundingKey) { + shouldPanic = true + } + + if !first.RevocationPoint.IsEqual(second.RevocationPoint) { + shouldPanic = true + } + + if !first.PaymentPoint.IsEqual(second.PaymentPoint) { + shouldPanic = true + } + + if !first.DelayedPaymentPoint.IsEqual(second.DelayedPaymentPoint) { + shouldPanic = true + } + + if !first.HtlcPoint.IsEqual(second.HtlcPoint) { + shouldPanic = true + } + + if !first.FirstCommitmentPoint.IsEqual(second.FirstCommitmentPoint) { + shouldPanic = true + } + + if first.ChannelFlags != second.ChannelFlags { + shouldPanic = true + } + + if !bytes.Equal(first.UpfrontShutdownScript, second.UpfrontShutdownScript) { + shouldPanic = true + } + + if shouldPanic { + panic("original message and deserialized message are not equal") + } + + // Add this input to the corpus. + return 1 +} diff --git a/fuzz/lnwire/ping.go b/fuzz/lnwire/ping.go new file mode 100644 index 00000000..87e893e0 --- /dev/null +++ b/fuzz/lnwire/ping.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_ping is used by go-fuzz. +func Fuzz_ping(data []byte) int { + // Prefix with MsgPing. + data = prefixWithMsgType(data, lnwire.MsgPing) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.Ping{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/pong.go b/fuzz/lnwire/pong.go new file mode 100644 index 00000000..b51e3152 --- /dev/null +++ b/fuzz/lnwire/pong.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_pong is used by go-fuzz. +func Fuzz_pong(data []byte) int { + // Prefix with MsgPong. + data = prefixWithMsgType(data, lnwire.MsgPong) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.Pong{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/query_channel_range.go b/fuzz/lnwire/query_channel_range.go new file mode 100644 index 00000000..38f5f4d7 --- /dev/null +++ b/fuzz/lnwire/query_channel_range.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_query_channel_range is used by go-fuzz. +func Fuzz_query_channel_range(data []byte) int { + // Prefix with MsgQueryChannelRange. + data = prefixWithMsgType(data, lnwire.MsgQueryChannelRange) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.QueryChannelRange{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/query_short_chan_ids.go b/fuzz/lnwire/query_short_chan_ids.go new file mode 100644 index 00000000..b0adb879 --- /dev/null +++ b/fuzz/lnwire/query_short_chan_ids.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_query_short_chan_ids is used by go-fuzz. +func Fuzz_query_short_chan_ids(data []byte) int { + // Prefix with MsgQueryShortChanIDs. + data = prefixWithMsgType(data, lnwire.MsgQueryShortChanIDs) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.QueryShortChanIDs{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/query_short_chan_ids_zlib.go b/fuzz/lnwire/query_short_chan_ids_zlib.go new file mode 100644 index 00000000..7b2061d7 --- /dev/null +++ b/fuzz/lnwire/query_short_chan_ids_zlib.go @@ -0,0 +1,51 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "bytes" + "compress/zlib" + "encoding/binary" + + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_query_short_chan_ids_zlib is used by go-fuzz. +func Fuzz_query_short_chan_ids_zlib(data []byte) int { + + var buf bytes.Buffer + zlibWriter := zlib.NewWriter(&buf) + _, err := zlibWriter.Write(data) + if err != nil { + // Zlib bug? + panic(err) + } + + if err := zlibWriter.Close(); err != nil { + // Zlib bug? + panic(err) + } + + compressedPayload := buf.Bytes() + + chainhash := []byte("00000000000000000000000000000000") + numBytesInBody := len(compressedPayload) + 1 + zlibByte := []byte("\x01") + + bodyBytes := make([]byte, 2) + binary.BigEndian.PutUint16(bodyBytes, uint16(numBytesInBody)) + + payload := append(chainhash, bodyBytes...) + payload = append(payload, zlibByte...) + payload = append(payload, compressedPayload...) + + // Prefix with MsgQueryShortChanIDs. + payload = prefixWithMsgType(payload, lnwire.MsgQueryShortChanIDs) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.QueryShortChanIDs{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(payload, &emptyMsg) +} diff --git a/fuzz/lnwire/reply_channel_range.go b/fuzz/lnwire/reply_channel_range.go new file mode 100644 index 00000000..3bfdbf36 --- /dev/null +++ b/fuzz/lnwire/reply_channel_range.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_reply_channel_range is used by go-fuzz. +func Fuzz_reply_channel_range(data []byte) int { + // Prefix with MsgReplyChannelRange. + data = prefixWithMsgType(data, lnwire.MsgReplyChannelRange) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.ReplyChannelRange{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/reply_channel_range_zlib.go b/fuzz/lnwire/reply_channel_range_zlib.go new file mode 100644 index 00000000..944d1fa1 --- /dev/null +++ b/fuzz/lnwire/reply_channel_range_zlib.go @@ -0,0 +1,59 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "bytes" + "compress/zlib" + "encoding/binary" + + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_reply_channel_range_zlib is used by go-fuzz. +func Fuzz_reply_channel_range_zlib(data []byte) int { + + var buf bytes.Buffer + zlibWriter := zlib.NewWriter(&buf) + _, err := zlibWriter.Write(data) + if err != nil { + // Zlib bug? + panic(err) + } + + if err := zlibWriter.Close(); err != nil { + // Zlib bug? + panic(err) + } + + compressedPayload := buf.Bytes() + + // Initialize some []byte vars which will prefix our payload + chainhash := []byte("00000000000000000000000000000000") + firstBlockHeight := []byte("\x00\x00\x00\x00") + numBlocks := []byte("\x00\x00\x00\x00") + completeByte := []byte("\x00") + + numBytesInBody := len(compressedPayload) + 1 + zlibByte := []byte("\x01") + + bodyBytes := make([]byte, 2) + binary.BigEndian.PutUint16(bodyBytes, uint16(numBytesInBody)) + + payload := append(chainhash, firstBlockHeight...) + payload = append(payload, numBlocks...) + payload = append(payload, completeByte...) + payload = append(payload, bodyBytes...) + payload = append(payload, zlibByte...) + payload = append(payload, compressedPayload...) + + // Prefix with MsgReplyChannelRange. + payload = prefixWithMsgType(payload, lnwire.MsgReplyChannelRange) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.ReplyChannelRange{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(payload, &emptyMsg) +} diff --git a/fuzz/lnwire/reply_short_chan_ids_end.go b/fuzz/lnwire/reply_short_chan_ids_end.go new file mode 100644 index 00000000..c09282d9 --- /dev/null +++ b/fuzz/lnwire/reply_short_chan_ids_end.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_reply_short_chan_ids_end is used by go-fuzz. +func Fuzz_reply_short_chan_ids_end(data []byte) int { + // Prefix with MsgReplyShortChanIDsEnd. + data = prefixWithMsgType(data, lnwire.MsgReplyShortChanIDsEnd) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.ReplyShortChanIDsEnd{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/revoke_and_ack.go b/fuzz/lnwire/revoke_and_ack.go new file mode 100644 index 00000000..23951c35 --- /dev/null +++ b/fuzz/lnwire/revoke_and_ack.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_revoke_and_ack is used by go-fuzz. +func Fuzz_revoke_and_ack(data []byte) int { + // Prefix with MsgRevokeAndAck. + data = prefixWithMsgType(data, lnwire.MsgRevokeAndAck) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.RevokeAndAck{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/shutdown.go b/fuzz/lnwire/shutdown.go new file mode 100644 index 00000000..1ffd8660 --- /dev/null +++ b/fuzz/lnwire/shutdown.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_shutdown is used by go-fuzz. +func Fuzz_shutdown(data []byte) int { + // Prefix with MsgShutdown. + data = prefixWithMsgType(data, lnwire.MsgShutdown) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.Shutdown{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/update_add_htlc.go b/fuzz/lnwire/update_add_htlc.go new file mode 100644 index 00000000..570de741 --- /dev/null +++ b/fuzz/lnwire/update_add_htlc.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_update_add_htlc is used by go-fuzz. +func Fuzz_update_add_htlc(data []byte) int { + // Prefix with MsgUpdateAddHTLC. + data = prefixWithMsgType(data, lnwire.MsgUpdateAddHTLC) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.UpdateAddHTLC{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/update_fail_htlc.go b/fuzz/lnwire/update_fail_htlc.go new file mode 100644 index 00000000..4ef9ab88 --- /dev/null +++ b/fuzz/lnwire/update_fail_htlc.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_update_fail_htlc is used by go-fuzz. +func Fuzz_update_fail_htlc(data []byte) int { + // Prefix with MsgUpdateFailHTLC. + data = prefixWithMsgType(data, lnwire.MsgUpdateFailHTLC) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.UpdateFailHTLC{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/update_fail_malformed_htlc.go b/fuzz/lnwire/update_fail_malformed_htlc.go new file mode 100644 index 00000000..0a0d45a6 --- /dev/null +++ b/fuzz/lnwire/update_fail_malformed_htlc.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_update_fail_malformed_htlc is used by go-fuzz. +func Fuzz_update_fail_malformed_htlc(data []byte) int { + // Prefix with MsgUpdateFailMalformedHTLC. + data = prefixWithMsgType(data, lnwire.MsgUpdateFailMalformedHTLC) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.UpdateFailMalformedHTLC{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/update_fee.go b/fuzz/lnwire/update_fee.go new file mode 100644 index 00000000..bb82c5c1 --- /dev/null +++ b/fuzz/lnwire/update_fee.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_update_fee is used by go-fuzz. +func Fuzz_update_fee(data []byte) int { + // Prefix with MsgUpdateFee. + data = prefixWithMsgType(data, lnwire.MsgUpdateFee) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.UpdateFee{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +} diff --git a/fuzz/lnwire/update_fulfill_htlc.go b/fuzz/lnwire/update_fulfill_htlc.go new file mode 100644 index 00000000..de28dfbe --- /dev/null +++ b/fuzz/lnwire/update_fulfill_htlc.go @@ -0,0 +1,20 @@ +// +build gofuzz + +package lnwirefuzz + +import ( + "github.com/lightningnetwork/lnd/lnwire" +) + +// Fuzz_update_fulfill_htlc is used by go-fuzz. +func Fuzz_update_fulfill_htlc(data []byte) int { + // Prefix with MsgUpdateFulfillHTLC. + data = prefixWithMsgType(data, lnwire.MsgUpdateFulfillHTLC) + + // Create an empty message so that the FuzzHarness func can check + // if the max payload constraint is violated. + emptyMsg := lnwire.UpdateFulfillHTLC{} + + // Pass the message into our general fuzz harness for wire messages! + return harness(data, &emptyMsg) +}