lnwire: fuzz onion failure messages

Fuzz tests for decoding and encoding of onion failure messages, based on
the fuzz harness for other lnwire messages. The onion failure messages
were uncovered by existing fuzz tests.
This commit is contained in:
Matt Morehouse committed 2023-10-27 14:12:41 -05:00
1 parent 3b7cda9e8d
commit 2181fd432c
1 file changed
+158
+158
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"compress/zlib"
"encoding/binary"
"reflect"
"testing"
"github.com/stretchr/testify/require"
@@ -635,3 +636,160 @@ func FuzzConvertFixedSignature(f *testing.F) {
require.Equal(t, derBytes, derBytes2, "signature mismatch")
})
}
// prefixWithFailCode adds a failure code prefix to data.
func prefixWithFailCode(data []byte, code FailCode) []byte {
var codeBytes [2]byte
binary.BigEndian.PutUint16(codeBytes[:], uint16(code))
data = append(codeBytes[:], data...)
return data
}
// onionFailureHarness performs the actual fuzz testing of the appropriate onion
// failure 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.
func onionFailureHarness(t *testing.T, data []byte, code FailCode) {
data = prefixWithFailCode(data, code)
// Don't waste time fuzzing messages larger than we'll ever accept.
if len(data) > MaxSliceLength {
return
}
// First check whether the failure message can be decoded.
r := bytes.NewReader(data)
msg, err := DecodeFailureMessage(r, 0)
if err != nil {
return
}
// We now have a valid decoded message. Verify that encoding and
// decoding the message does not mutate it.
var b bytes.Buffer
if err := EncodeFailureMessage(&b, msg, 0); err != nil {
t.Fatalf("failed to encode failure message: %v", err)
}
newMsg, err := DecodeFailureMessage(&b, 0)
if err != nil {
t.Fatalf("failed to decode serialized failure message: %v", err)
}
if !reflect.DeepEqual(msg, newMsg) {
t.Fatalf("original message and deserialized message are not "+
"equal: %v != %v", msg, newMsg)
}
// Now verify that encoding/decoding full packets works as expected.
var pktBuf bytes.Buffer
if err := EncodeFailure(&pktBuf, msg, 0); err != nil {
// EncodeFailure returns an error if the encoded message would
// exceed FailureMessageLength bytes, as LND always encodes
// fixed-size packets for privacy. But it is valid to decode
// messages longer than this, so we should not report an error
// if the original message was longer.
//
// We add 2 to the length of the original message since it may
// have omitted a channel_update type prefix of 2 bytes. When
// we re-encode such a message, we will add the 2-byte prefix
// as prescribed by the spec.
if len(data)+2 > FailureMessageLength {
return
}
t.Fatalf("failed to encode failure packet: %v", err)
}
// We should use FailureMessageLength sized packets plus 2 bytes to
// encode the message length and 2 bytes to encode the padding length,
// as recommended by the spec.
if pktBuf.Len() != FailureMessageLength+4 {
t.Fatalf("wrong failure message length: %v", pktBuf.Len())
}
pktMsg, err := DecodeFailure(&pktBuf, 0)
if err != nil {
t.Fatalf("failed to decode failure packet: %v", err)
}
if !reflect.DeepEqual(msg, pktMsg) {
t.Fatalf("original message and decoded packet message are not "+
"equal: %v != %v", msg, pktMsg)
}
}
func FuzzFailInvalidOnionVersion(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeInvalidOnionVersion)
})
}
func FuzzFailInvalidOnionHmac(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeInvalidOnionHmac)
})
}
func FuzzFailInvalidOnionKey(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeInvalidOnionKey)
})
}
func FuzzFailTemporaryChannelFailure(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeTemporaryChannelFailure)
})
}
func FuzzFailAmountBelowMinimum(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeAmountBelowMinimum)
})
}
func FuzzFailFeeInsufficient(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeFeeInsufficient)
})
}
func FuzzFailIncorrectCltvExpiry(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeIncorrectCltvExpiry)
})
}
func FuzzFailExpiryTooSoon(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeExpiryTooSoon)
})
}
func FuzzFailChannelDisabled(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeChannelDisabled)
})
}
func FuzzFailFinalIncorrectCltvExpiry(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeFinalIncorrectCltvExpiry)
})
}
func FuzzFailFinalIncorrectHtlcAmount(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeFinalIncorrectHtlcAmount)
})
}
func FuzzInvalidOnionPayload(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
onionFailureHarness(t, data, CodeInvalidOnionPayload)
})
}