Files
lnd/tlv/primitive_test.go
T
Erick Cestari 5a7925008e multi: enforce strict TLV length checks
This commit improves TLV decoding safety and consistency across multiple
packages by enforcing fixed-length requirements and adding unit tests to
prevent malformed TLV records from being accepted.

Changes include:

- lnwire:
  * Enforce 8-byte length in Fee TLV decoder.
  * Enforce PubNonceSize in Musig2Nonce TLV decoder.
  * Enforce 8-byte length in ShortChannelID TLV decoder.
  * Added roundtrip and invalid length tests for Fee, Musig2Nonce,
    and ShortChannelID records.

- routing/route:
  * Enforce Vertex TLV length (33 bytes).
  * Added encode/decode and invalid length tests for Vertex.

- tlv:
  * Enforce correct length in DBytes33 decoder (33 bytes).
  * Added tests ensuring all fixed-size primitive decoders reject
    incorrect TLV lengths.

By strictly validating TLV lengths, we prevent malformed or corrupted
TLV records from being silently accepted, improving protocol safety.
2025-12-29 19:18:49 -03:00

343 lines
7.0 KiB
Go

package tlv_test
import (
"bytes"
"reflect"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/tlv"
"github.com/stretchr/testify/require"
)
var testPK, _ = btcec.ParsePubKey([]byte{0x02,
0x3d, 0xa0, 0x92, 0xf6, 0x98, 0x0e, 0x58, 0xd2,
0xc0, 0x37, 0x17, 0x31, 0x80, 0xe9, 0xa4, 0x65,
0x47, 0x60, 0x26, 0xee, 0x50, 0xf9, 0x66, 0x95,
0x96, 0x3e, 0x8e, 0xfe, 0x43, 0x6f, 0x54, 0xeb,
})
type primitive struct {
u8 byte
u16 uint16
u32 uint32
u64 uint64
b32 [32]byte
b33 [33]byte
b64 [64]byte
pk *btcec.PublicKey
bytes []byte
boolean bool
}
// TestWrongEncodingType asserts that all primitives encoders will fail with a
// ErrTypeForEncoding when an incorrect type is provided.
func TestWrongEncodingType(t *testing.T) {
encoders := []tlv.Encoder{
tlv.EUint8,
tlv.EUint16,
tlv.EUint32,
tlv.EUint64,
tlv.EBytes32,
tlv.EBytes33,
tlv.EBytes64,
tlv.EPubKey,
tlv.EVarBytes,
tlv.EBool,
}
// We'll use an int32 since it is not a primitive type, which should
// cause the primitive encoders to fail with an ErrTypeForEncoding
// failure.
var (
value int32
buf [8]byte
b bytes.Buffer
)
for _, encoder := range encoders {
err := encoder(&b, &value, &buf)
if _, ok := err.(tlv.ErrTypeForEncoding); !ok {
t.Fatalf("expected error of type ErrTypeForEncoding, "+
"got %T", err)
}
}
}
// TestWrongDecodingType asserts that all primitives decoders will fail with a
// ErrTypeForDecoding when an incorrect type is provided.
func TestWrongDecodingType(t *testing.T) {
decoders := []tlv.Decoder{
tlv.DUint8,
tlv.DUint16,
tlv.DUint32,
tlv.DUint64,
tlv.DBytes32,
tlv.DBytes33,
tlv.DBytes64,
tlv.DPubKey,
tlv.DVarBytes,
tlv.DBool,
}
// We'll use an int32 since it is not a primitive type, which should
// cause the primitive decoders to fail with an ErrTypeForDecoding
// failure.
var (
value int32
buf [8]byte
b bytes.Buffer
)
for _, decoder := range decoders {
err := decoder(&b, &value, &buf, 0)
if _, ok := err.(tlv.ErrTypeForDecoding); !ok {
t.Fatalf("expected error of type ErrTypeForDecoding, "+
"got %T", err)
}
}
}
type fieldEncoder struct {
val interface{}
encoder tlv.Encoder
}
type fieldDecoder struct {
val interface{}
decoder tlv.Decoder
size uint64
}
// TestPrimitiveEncodings tests that we are able to serialize all known
// primitive field types. After successfully encoding, we check that we are able
// to decode the output and arrive at the same fields.
func TestPrimitiveEncodings(t *testing.T) {
prim := primitive{
u8: 0x01,
u16: 0x0201,
u32: 0x02000001,
u64: 0x0200000000000001,
b32: [32]byte{0x02, 0x01},
b33: [33]byte{0x03, 0x01},
b64: [64]byte{0x02, 0x01},
pk: testPK,
bytes: []byte{0xaa, 0xbb},
boolean: true,
}
encoders := []fieldEncoder{
{
val: &prim.u8,
encoder: tlv.EUint8,
},
{
val: &prim.u16,
encoder: tlv.EUint16,
},
{
val: &prim.u32,
encoder: tlv.EUint32,
},
{
val: &prim.u64,
encoder: tlv.EUint64,
},
{
val: &prim.b32,
encoder: tlv.EBytes32,
},
{
val: &prim.b33,
encoder: tlv.EBytes33,
},
{
val: &prim.b64,
encoder: tlv.EBytes64,
},
{
val: &prim.pk,
encoder: tlv.EPubKey,
},
{
val: &prim.bytes,
encoder: tlv.EVarBytes,
},
{
val: &prim.boolean,
encoder: tlv.EBool,
},
}
// First we'll encode the primitive fields into a buffer.
var (
b bytes.Buffer
buf [8]byte
)
for _, field := range encoders {
err := field.encoder(&b, field.val, &buf)
if err != nil {
t.Fatalf("unable to encode %T: %v",
field.val, err)
}
}
// Next, we'll attempt to decode the primitive fields into a separate
// primitive struct.
r := bytes.NewReader(b.Bytes())
var prim2 primitive
decoders := []fieldDecoder{
{
val: &prim2.u8,
decoder: tlv.DUint8,
size: 1,
},
{
val: &prim2.u16,
decoder: tlv.DUint16,
size: 2,
},
{
val: &prim2.u32,
decoder: tlv.DUint32,
size: 4,
},
{
val: &prim2.u64,
decoder: tlv.DUint64,
size: 8,
},
{
val: &prim2.b32,
decoder: tlv.DBytes32,
size: 32,
},
{
val: &prim2.b33,
decoder: tlv.DBytes33,
size: 33,
},
{
val: &prim2.b64,
decoder: tlv.DBytes64,
size: 64,
},
{
val: &prim2.pk,
decoder: tlv.DPubKey,
size: 33,
},
{
val: &prim2.bytes,
decoder: tlv.DVarBytes,
size: 2,
},
{
val: &prim2.boolean,
decoder: tlv.DBool,
size: 1,
},
}
for _, field := range decoders {
err := field.decoder(r, field.val, &buf, field.size)
if err != nil {
t.Fatalf("unable to decode %T: %v",
field.val, err)
}
}
// Finally, we'll compare that the original and the decode structs are
// equal.
if !reflect.DeepEqual(prim, prim2) {
t.Fatalf("primitive mismatch, "+
"expected: %v, got: %v",
prim, prim2)
}
}
// TestPrimitiveWrongLength asserts that fixed-size primitive decoders fail
// with ErrTypeForDecoding when given an incorrect TLV length.
func TestPrimitiveWrongLength(t *testing.T) {
prim := primitive{
u8: 0x01,
u16: 0x0201,
u32: 0x02000001,
u64: 0x0200000000000001,
b32: [32]byte{0x02, 0x01},
b33: [33]byte{0x03, 0x01},
b64: [64]byte{0x02, 0x01},
pk: testPK,
boolean: true,
}
type item struct {
enc fieldEncoder
dec fieldDecoder
}
items := []item{
{
fieldEncoder{&prim.u8, tlv.EUint8},
fieldDecoder{new(byte), tlv.DUint8, 1},
},
{
fieldEncoder{&prim.u16, tlv.EUint16},
fieldDecoder{new(uint16), tlv.DUint16, 2},
},
{
fieldEncoder{&prim.u32, tlv.EUint32},
fieldDecoder{new(uint32), tlv.DUint32, 4},
},
{
fieldEncoder{&prim.u64, tlv.EUint64},
fieldDecoder{new(uint64), tlv.DUint64, 8},
},
{
fieldEncoder{&prim.b32, tlv.EBytes32},
fieldDecoder{new([32]byte), tlv.DBytes32, 32},
},
{
fieldEncoder{&prim.b33, tlv.EBytes33},
fieldDecoder{new([33]byte), tlv.DBytes33, 33},
},
{
fieldEncoder{&prim.b64, tlv.EBytes64},
fieldDecoder{new([64]byte), tlv.DBytes64, 64},
},
{
fieldEncoder{&prim.pk, tlv.EPubKey},
fieldDecoder{new(*btcec.PublicKey), tlv.DPubKey, 33},
},
{
fieldEncoder{&prim.boolean, tlv.EBool},
fieldDecoder{new(bool), tlv.DBool, 1},
},
}
for _, it := range items {
var buf [8]byte
var b bytes.Buffer
err := it.enc.encoder(&b, it.enc.val, &buf)
require.NoError(t, err, "encode %T", it.enc.val)
data := b.Bytes()
// Generate two wrong lengths: expected-1 (if >0) and
// expected+1.
wrongs := []uint64{it.dec.size + 1}
if it.dec.size > 0 {
wrongs = append(wrongs, it.dec.size-1)
}
for _, l := range wrongs {
r := bytes.NewReader(data)
err := it.dec.decoder(r, it.dec.val, &buf, l)
require.ErrorAs(
t, err, &tlv.ErrTypeForDecoding{},
"decoder %T should reject wrong length "+
"%d (expected %d)",
it.dec.decoder, l, it.dec.size,
)
}
}
}