Skip to content

Commit eea4840

Browse files
authored
Merge pull request #10249 from erickcestari/fix-scid-tlv-length
lnwire+tlv+route: enforce TLV length validation and add tests
2 parents 013f5c9 + 5230029 commit eea4840

File tree

11 files changed

+272
-5
lines changed

11 files changed

+272
-5
lines changed

docs/release-notes/release-notes-0.21.0.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
has been removed from the public key parsing methods, and proper mutex
4040
protection has been added to the cache access in `DisconnectBlockAtHeight`.
4141

42+
- [Fixed TLV decoders to reject malformed records with incorrect lengths](https://github.com/lightningnetwork/lnd/pull/10249).
43+
TLV decoders now strictly enforce fixed-length requirements for Fee (8 bytes),
44+
Musig2Nonce (66 bytes), ShortChannelID (8 bytes), Vertex (33 bytes), and
45+
DBytes33 (33 bytes) records, preventing malformed TLV data from being
46+
accepted.
47+
4248
# New Features
4349

4450
- Basic Support for [onion messaging forwarding](https://github.com/lightningnetwork/lnd/pull/9868)
@@ -108,6 +114,11 @@
108114

109115
## Testing
110116

117+
* [Added unit tests for TLV length validation across multiple packages](https://github.com/lightningnetwork/lnd/pull/10249).
118+
New tests ensure that fixed-size TLV decoders reject malformed records with
119+
invalid lengths, including roundtrip tests for Fee, Musig2Nonce,
120+
ShortChannelID and Vertex records.
121+
111122
## Database
112123

113124
* Freeze the [graph SQL migration
@@ -122,6 +133,7 @@
122133

123134
* Boris Nagaev
124135
* Elle Mouton
136+
* Erick Cestari
125137
* Mohamed Awnallah
126138
* Nishant Bansal
127139
* Pins

lnwire/musig2.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func nonceTypeEncoder(w io.Writer, val interface{}, _ *[8]byte) error {
5151
func nonceTypeDecoder(r io.Reader, val interface{}, _ *[8]byte,
5252
l uint64) error {
5353

54-
if v, ok := val.(*Musig2Nonce); ok {
54+
if v, ok := val.(*Musig2Nonce); ok && l == musig2.PubNonceSize {
5555
_, err := io.ReadFull(r, v[:])
5656
return err
5757
}

lnwire/musig2_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package lnwire
2+
3+
import (
4+
"testing"
5+
6+
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
// makeNonce creates a test Musig2Nonce with sequential byte values for testing
11+
// TLV encoding/decoding.
12+
func makeNonce() Musig2Nonce {
13+
var n Musig2Nonce
14+
for i := range n {
15+
n[i] = byte(i)
16+
}
17+
18+
return n
19+
}
20+
21+
// TestMusig2NonceEncodeDecode tests that we're able to properly encode and
22+
// decode Musig2Nonce within TLV streams.
23+
func TestMusig2NonceEncodeDecode(t *testing.T) {
24+
t.Parallel()
25+
26+
nonce := makeNonce()
27+
28+
var extraData ExtraOpaqueData
29+
require.NoError(t, extraData.PackRecords(&nonce))
30+
31+
var extractedNonce Musig2Nonce
32+
_, err := extraData.ExtractRecords(&extractedNonce)
33+
require.NoError(t, err)
34+
35+
require.Equal(t, nonce, extractedNonce)
36+
}
37+
38+
// TestMusig2NonceTypeDecodeInvalidLength ensures that decoding a Musig2Nonce
39+
// TLV with an invalid length (anything other than 66 bytes) fails with an
40+
// error.
41+
func TestMusig2NonceTypeDecodeInvalidLength(t *testing.T) {
42+
t.Parallel()
43+
44+
nonce := makeNonce()
45+
46+
var extraData ExtraOpaqueData
47+
require.NoError(t, extraData.PackRecords(&nonce))
48+
49+
// Corrupt the TLV length field to simulate malformed input.
50+
// Byte 1 contains the varint size encoding. Since 66 bytes fits into
51+
// a single varint byte, we can directly modify extraData[1].
52+
extraData[1] = musig2.PubNonceSize + 1
53+
54+
var out Musig2Nonce
55+
_, err := extraData.ExtractRecords(&out)
56+
require.Error(t, err)
57+
58+
extraData[1] = musig2.PubNonceSize - 1
59+
60+
_, err = extraData.ExtractRecords(&out)
61+
require.Error(t, err)
62+
}

lnwire/short_channel_id.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ func DShortChannelID(r io.Reader, val interface{}, buf *[8]byte,
9292

9393
if v, ok := val.(*ShortChannelID); ok {
9494
var scid uint64
95-
err := tlv.DUint64(r, &scid, buf, 8)
95+
// tlv.DUint64 forces the length to be 8 bytes.
96+
err := tlv.DUint64(r, &scid, buf, l)
9697
if err != nil {
9798
return err
9899
}

lnwire/short_channel_id_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,28 @@ func TestScidTypeEncodeDecode(t *testing.T) {
6262
require.Contains(t, tlvs, AliasScidRecordType)
6363
require.Equal(t, aliasScid, aliasScid2)
6464
}
65+
66+
// TestScidTypeDecodeInvalidLength ensures that decoding a ShortChannelID TLV
67+
// with an invalid length (anything other than 8 bytes) fails with an error.
68+
func TestScidTypeDecodeInvalidLength(t *testing.T) {
69+
t.Parallel()
70+
71+
aliasScid := ShortChannelID{
72+
BlockHeight: 1, TxIndex: 1, TxPosition: 1,
73+
}
74+
75+
var extraData ExtraOpaqueData
76+
require.NoError(t, extraData.PackRecords(&aliasScid))
77+
78+
// Corrupt the TLV length field to simulate malformed input.
79+
extraData[1] = 8 + 1
80+
81+
var out ShortChannelID
82+
_, err := extraData.ExtractRecords(&out)
83+
require.Error(t, err)
84+
85+
extraData[1] = 8 - 1
86+
87+
_, err = extraData.ExtractRecords(&out)
88+
require.Error(t, err)
89+
}

lnwire/typed_fee.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func feeEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
4141
// feeDecoder is a custom TLV decoder for the fee record.
4242
func feeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
4343
v, ok := val.(*Fee)
44-
if !ok {
44+
if !ok || l != 8 {
4545
return tlv.NewTypeForDecodingErr(val, "lnwire.Fee", l, 8)
4646
}
4747

lnwire/typed_fee_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,28 @@ func testTypedFee(t *testing.T, fee Fee) { //nolint: thelper
3838

3939
require.Equal(t, fee, extractedFee)
4040
}
41+
42+
// TestTypedFeeTypeDecodeInvalidLength ensures that decoding a Fee TLV
43+
// with an invalid length (anything other than 8 bytes) fails with an error.
44+
func TestTypedFeeTypeDecodeInvalidLength(t *testing.T) {
45+
t.Parallel()
46+
47+
fee := Fee{
48+
BaseFee: 1, FeeRate: 1,
49+
}
50+
51+
var extraData ExtraOpaqueData
52+
require.NoError(t, extraData.PackRecords(&fee))
53+
54+
// Corrupt the TLV length field to simulate malformed input.
55+
extraData[3] = 8 + 1
56+
57+
var out Fee
58+
_, err := extraData.ExtractRecords(&out)
59+
require.Error(t, err)
60+
61+
extraData[3] = 8 - 1
62+
63+
_, err = extraData.ExtractRecords(&out)
64+
require.Error(t, err)
65+
}

routing/route/route.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func encodeVertex(w io.Writer, val interface{}, _ *[8]byte) error {
112112
}
113113

114114
func decodeVertex(r io.Reader, val interface{}, _ *[8]byte, l uint64) error {
115-
if b, ok := val.(*Vertex); ok {
115+
if b, ok := val.(*Vertex); ok && l == VertexSize {
116116
_, err := io.ReadFull(r, b[:])
117117
return err
118118
}

routing/route/route_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/btcsuite/btcd/btcec/v2"
99
"github.com/lightningnetwork/lnd/lnwire"
1010
"github.com/lightningnetwork/lnd/record"
11+
"github.com/lightningnetwork/lnd/tlv"
1112
"github.com/stretchr/testify/require"
1213
)
1314

@@ -430,3 +431,57 @@ func TestBlindedHopFee(t *testing.T) {
430431
require.Equal(t, lnwire.MilliSatoshi(0), route.HopFee(3))
431432
require.Equal(t, lnwire.MilliSatoshi(0), route.HopFee(4))
432433
}
434+
435+
// makeVertex creates a test Vertex with sequential byte values for testing
436+
// TLV encoding/decoding.
437+
func makeVertex() Vertex {
438+
var v Vertex
439+
for i := range v {
440+
v[i] = byte(i)
441+
}
442+
443+
return v
444+
}
445+
446+
// TestVertexTLVEncodeDecode tests that we're able to properly encode and decode
447+
// Vertex within TLV streams.
448+
func TestVertexTLVEncodeDecode(t *testing.T) {
449+
t.Parallel()
450+
451+
vertex := makeVertex()
452+
453+
var extraData lnwire.ExtraOpaqueData
454+
require.NoError(t, extraData.PackRecords(&vertex))
455+
456+
var vertex2 Vertex
457+
tlvs, err := extraData.ExtractRecords(&vertex2)
458+
require.NoError(t, err)
459+
460+
require.Contains(t, tlvs, tlv.Type(0))
461+
require.Equal(t, vertex, vertex2)
462+
}
463+
464+
// TestVertexTypeDecodeInvalidLength ensures that decoding a Vertex TLV
465+
// with an invalid length (anything other than 33) fails with an error.
466+
func TestVertexTypeDecodeInvalidLength(t *testing.T) {
467+
t.Parallel()
468+
469+
vertex := makeVertex()
470+
471+
var extraData lnwire.ExtraOpaqueData
472+
require.NoError(t, extraData.PackRecords(&vertex))
473+
474+
// Corrupt the TLV length field to simulate malformed input.
475+
// Byte 1 contains the varint size encoding. Since 33 bytes fits into
476+
// a single varint byte, we can directly modify extraData[1].
477+
extraData[1] = VertexSize + 1
478+
479+
var out Vertex
480+
_, err := extraData.ExtractRecords(&out)
481+
require.Error(t, err)
482+
483+
extraData[1] = VertexSize - 1
484+
485+
_, err = extraData.ExtractRecords(&out)
486+
require.Error(t, err)
487+
}

tlv/primitive.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ func EBytes33(w io.Writer, val interface{}, _ *[8]byte) error {
257257
// DBytes33 is a Decoder for 33-byte arrays. An error is returned if val is not
258258
// a *[33]byte.
259259
func DBytes33(r io.Reader, val interface{}, _ *[8]byte, l uint64) error {
260-
if b, ok := val.(*[33]byte); ok {
260+
if b, ok := val.(*[33]byte); ok && l == 33 {
261261
_, err := io.ReadFull(r, b[:])
262262
return err
263263
}

0 commit comments

Comments
 (0)