lnd/htlcswitch/hop/fuzz_test.go
Carla Kirk-Cohen b81a6f3d2f
htlcswitch: split parsing and validation of TLV payloads
When handling blinded errors, we need to know whether there was a
blinding key in our payload when we successfully parsed our payload
but then found an invalid set of fields. The combination of
parsing and validation in NewPayloadFromReader means that we don't know
whether a blinding point was available to us by the time the error is
returned.

This commit splits parsing and validation into two functions so that
we can take a look at what we actually pulled of the payload in between
parsing and TLV validation.
2024-04-25 09:15:58 -04:00

183 lines
4.7 KiB
Go

package hop
import (
"bytes"
"errors"
"testing"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
)
const (
LegacyPayloadSize = sphinx.LegacyHopDataSize - sphinx.HMACSize
MaxOnionPacketSize = 1366
)
func FuzzHopData(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > LegacyPayloadSize {
return
}
r := bytes.NewReader(data)
var hopData1, hopData2 sphinx.HopData
if err := hopData1.Decode(r); err != nil {
return
}
var b bytes.Buffer
require.NoError(t, hopData1.Encode(&b))
require.NoError(t, hopData2.Decode(&b))
require.Equal(t, hopData1, hopData2)
})
}
func FuzzHopPayload(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > sphinx.MaxPayloadSize {
return
}
r := bytes.NewReader(data)
var hopPayload1, hopPayload2 sphinx.HopPayload
if err := hopPayload1.Decode(r); err != nil {
return
}
var b bytes.Buffer
require.NoError(t, hopPayload1.Encode(&b))
require.NoError(t, hopPayload2.Decode(&b))
require.Equal(t, hopPayload1, hopPayload2)
})
}
func FuzzOnionPacket(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > MaxOnionPacketSize {
return
}
r := bytes.NewReader(data)
var pkt1, pkt2 sphinx.OnionPacket
if err := pkt1.Decode(r); err != nil {
return
}
var b bytes.Buffer
require.NoError(t, pkt1.Encode(&b))
require.NoError(t, pkt2.Decode(&b))
require.Equal(t, pkt1, pkt2)
})
}
func hopFromPayload(p *Payload) (*route.Hop, uint64) {
return &route.Hop{
AmtToForward: p.FwdInfo.AmountToForward,
OutgoingTimeLock: p.FwdInfo.OutgoingCTLV,
MPP: p.MPP,
AMP: p.AMP,
Metadata: p.metadata,
EncryptedData: p.encryptedData,
BlindingPoint: p.blindingPoint,
CustomRecords: p.customRecords,
TotalAmtMsat: p.totalAmtMsat,
}, p.FwdInfo.NextHop.ToUint64()
}
// FuzzPayloadFinal fuzzes final hop payloads, providing the additional context
// that the hop should be final (which is usually obtained by the structure
// of the sphinx packet) for the case where a blinding point was provided in
// UpdateAddHtlc.
func FuzzPayloadFinalBlinding(f *testing.F) {
fuzzPayload(f, true, true)
}
// FuzzPayloadFinal fuzzes final hop payloads, providing the additional context
// that the hop should be final (which is usually obtained by the structure
// of the sphinx packet) for the case where no blinding point was provided in
// UpdateAddHtlc.
func FuzzPayloadFinalNoBlinding(f *testing.F) {
fuzzPayload(f, true, false)
}
// FuzzPayloadIntermediate fuzzes intermediate hop payloads, providing the
// additional context that a hop should be intermediate (which is usually
// obtained by the structure of the sphinx packet) for the case where a
// blinding point was provided in UpdateAddHtlc.
func FuzzPayloadIntermediateBlinding(f *testing.F) {
fuzzPayload(f, false, true)
}
// FuzzPayloadIntermediate fuzzes intermediate hop payloads, providing the
// additional context that a hop should be intermediate (which is usually
// obtained by the structure of the sphinx packet) for the case where no
// blinding point was provided in UpdateAddHtlc.
func FuzzPayloadIntermediateNoBlinding(f *testing.F) {
fuzzPayload(f, false, false)
}
func fuzzPayload(f *testing.F, finalPayload, updateAddBlinded bool) {
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > sphinx.MaxPayloadSize {
return
}
r := bytes.NewReader(data)
payload1, parsed, err := ParseTLVPayload(r)
if err != nil {
return
}
if err = ValidateParsedPayloadTypes(
parsed, finalPayload, updateAddBlinded,
); err != nil {
return
}
var b bytes.Buffer
hop, nextChanID := hopFromPayload(payload1)
err = hop.PackHopPayload(&b, nextChanID, finalPayload)
switch {
// PackHopPayload refuses to encode an AMP record
// without an MPP record. However, ValidateParsedPayloadTypes
// does allow decoding an AMP record without an MPP
// record, since validation is done at a later stage. Do
// not report a bug for this case.
case errors.Is(err, route.ErrAMPMissingMPP):
return
// PackHopPayload will not encode regular payloads or final
// hops in blinded routes that do not have an amount or expiry
// TLV set. However, ValidateParsedPayloadTypes will allow
// creation of payloads where these TLVs are present, but they
// have zero values because validation is done at a later stage.
case errors.Is(err, route.ErrMissingField):
return
default:
require.NoError(t, err)
}
payload2, parsed, err := ParseTLVPayload(&b)
require.NoError(t, err)
err = ValidateParsedPayloadTypes(
parsed, finalPayload, updateAddBlinded,
)
require.NoError(t, err)
require.Equal(t, payload1, payload2)
})
}