Skip to content

Commit c7c8073

Browse files
authored
Merge pull request #10334 from ziggie1984/fix-decoding-extra-tlv-data
Don't fail on invalid extra tlv data when decoding a payment
2 parents f938e40 + 870d603 commit c7c8073

File tree

5 files changed

+139
-3
lines changed

5 files changed

+139
-3
lines changed

lnwire/channel_update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func (a *ChannelUpdate1) Decode(r io.Reader, _ uint32) error {
169169
var inboundFee = a.InboundFee.Zero()
170170
typeMap, err := tlvRecords.ExtractRecords(&inboundFee)
171171
if err != nil {
172-
return err
172+
return fmt.Errorf("%w: %w", ErrParsingExtraTLVBytes, err)
173173
}
174174

175175
val, ok := typeMap[a.InboundFee.TlvType()]

lnwire/error.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ import (
66
"io"
77
)
88

9+
var (
10+
// ErrParsingExtraTLVBytes is returned when we attempt to parse
11+
// extra opaque bytes as a TLV stream, but the parsing fails due to
12+
// and invalid TLV stream.
13+
ErrParsingExtraTLVBytes = fmt.Errorf("error parsing extra TLV bytes")
14+
)
15+
916
// FundingError represents a set of errors that can be encountered and sent
1017
// during the funding workflow.
1118
type FundingError uint8

lnwire/onion_error.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1361,7 +1361,7 @@ func DecodeFailureMessage(r io.Reader, pver uint32) (FailureMessage, error) {
13611361
case Serializable:
13621362
if err := f.Decode(r, pver); err != nil {
13631363
return nil, fmt.Errorf("unable to decode error "+
1364-
"update (type=%T): %v", failure, err)
1364+
"update (type=%T): %w", failure, err)
13651365
}
13661366
}
13671367

payments/db/kv_store.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2099,9 +2099,19 @@ func deserializeHTLCFailInfo(r io.Reader) (*HTLCFailInfo, error) {
20992099
f.Message, err = lnwire.DecodeFailureMessage(
21002100
bytes.NewReader(failureBytes), 0,
21012101
)
2102-
if err != nil {
2102+
if err != nil &&
2103+
!errors.Is(err, lnwire.ErrParsingExtraTLVBytes) {
2104+
21032105
return nil, err
21042106
}
2107+
2108+
// In case we have an invalid TLV stream regarding the extra
2109+
// tlv data we still continue with the decoding of the
2110+
// HTLCFailInfo.
2111+
if errors.Is(err, lnwire.ErrParsingExtraTLVBytes) {
2112+
log.Warnf("Failed to decode extra TLV bytes for "+
2113+
"failure message: %v", err)
2114+
}
21052115
}
21062116

21072117
var reason byte

payments/db/kv_store_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package paymentsdb
22

33
import (
44
"bytes"
5+
"encoding/binary"
6+
"io"
57
"math"
68
"reflect"
79
"testing"
810
"time"
911

12+
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
13+
"github.com/btcsuite/btcd/wire"
1014
"github.com/btcsuite/btcwallet/walletdb"
1115
"github.com/lightningnetwork/lnd/kvdb"
1216
"github.com/lightningnetwork/lnd/lntypes"
@@ -1070,3 +1074,118 @@ func TestLazySessionKeyDeserialize(t *testing.T) {
10701074
sessionKey := attempt.SessionKey()
10711075
require.Equal(t, priv, sessionKey)
10721076
}
1077+
1078+
// TestDeserializeHTLCFailInfoInvalidTLV tests that deserializeHTLCFailInfo
1079+
// handles invalid extra tlv data gracefully by not failing.
1080+
func TestDeserializeHTLCFailInfoInvalidTLV(t *testing.T) {
1081+
// Create a channel update with valid data first, then encode it.
1082+
testSig := &ecdsa.Signature{}
1083+
sig, _ := lnwire.NewSigFromSignature(testSig)
1084+
chanUpdate := &lnwire.ChannelUpdate1{
1085+
Signature: sig,
1086+
ShortChannelID: lnwire.NewShortChanIDFromInt(1),
1087+
Timestamp: 1,
1088+
MessageFlags: 0,
1089+
ChannelFlags: 1,
1090+
ExtraOpaqueData: make([]byte, 0),
1091+
}
1092+
1093+
var chanUpdateBuf bytes.Buffer
1094+
err := chanUpdate.Encode(&chanUpdateBuf, 0)
1095+
require.NoError(t, err)
1096+
1097+
// Append invalid inbound fee TLV record to the encoded channel update.
1098+
// The inbound fee TLV has type 55555 and should have 8 bytes of data
1099+
// (2 uint32 values: BaseFee and FeeRate). We create an invalid one by
1100+
// using the correct type but with incomplete data (only 6 bytes
1101+
// instead of 8).
1102+
var invalidInboundFeeTLV bytes.Buffer
1103+
1104+
// Write type 55555 as varint: 0xfd + 2 bytes (canonical encoding)
1105+
err = invalidInboundFeeTLV.WriteByte(0xfd)
1106+
require.NoError(t, err)
1107+
1108+
var typeBytes [2]byte
1109+
binary.BigEndian.PutUint16(typeBytes[:], 55555)
1110+
_, err = invalidInboundFeeTLV.Write(typeBytes[:])
1111+
require.NoError(t, err)
1112+
1113+
// Write length as 8 (single byte since 8 < 0xfd, no varint needed)
1114+
err = invalidInboundFeeTLV.WriteByte(8)
1115+
require.NoError(t, err)
1116+
1117+
// Write only 6 bytes of value data (incomplete, should be 8 bytes)
1118+
var valueBytes [6]byte
1119+
binary.BigEndian.PutUint32(valueBytes[0:4], 1)
1120+
binary.BigEndian.PutUint16(valueBytes[4:6], 2)
1121+
_, err = invalidInboundFeeTLV.Write(valueBytes[:])
1122+
require.NoError(t, err)
1123+
1124+
_, err = chanUpdateBuf.Write(invalidInboundFeeTLV.Bytes())
1125+
require.NoError(t, err)
1126+
1127+
// Manually create a TemporaryChannelFailure failure message with the
1128+
// corrupted channel update bytes.
1129+
var failureMsgBuf bytes.Buffer
1130+
1131+
// Write the failure code.
1132+
err = lnwire.WriteUint16(
1133+
&failureMsgBuf, uint16(lnwire.CodeTemporaryChannelFailure),
1134+
)
1135+
require.NoError(t, err)
1136+
1137+
// Write the length of the channel update (including invalid TLV).
1138+
err = lnwire.WriteUint16(&failureMsgBuf, uint16(chanUpdateBuf.Len()))
1139+
require.NoError(t, err)
1140+
1141+
// Write the channel update bytes with invalid TLV appended.
1142+
_, err = failureMsgBuf.Write(chanUpdateBuf.Bytes())
1143+
require.NoError(t, err)
1144+
1145+
_, err = lnwire.DecodeFailureMessage(&failureMsgBuf, 0)
1146+
require.ErrorIs(t, err, lnwire.ErrParsingExtraTLVBytes)
1147+
require.ErrorIs(t, err, io.ErrUnexpectedEOF)
1148+
1149+
// Create an HTLCFailInfo and serialize it with the corrupted failure
1150+
// message.
1151+
failInfo := &HTLCFailInfo{
1152+
FailTime: time.Now(),
1153+
Reason: HTLCFailMessage,
1154+
FailureSourceIndex: 2,
1155+
}
1156+
1157+
var buf bytes.Buffer
1158+
1159+
// Manually serialize the HTLCFailInfo with the corrupted failure bytes.
1160+
err = serializeTime(&buf, failInfo.FailTime)
1161+
require.NoError(t, err)
1162+
1163+
// Write the failure message bytes.
1164+
err = wire.WriteVarBytes(&buf, 0, failureMsgBuf.Bytes())
1165+
require.NoError(t, err)
1166+
1167+
// Write reason and failure source index.
1168+
err = WriteElements(
1169+
&buf, byte(failInfo.Reason), failInfo.FailureSourceIndex,
1170+
)
1171+
require.NoError(t, err)
1172+
1173+
// Now deserialize the HTLCFailInfo - this should NOT fail despite the
1174+
// invalid TLV data.
1175+
deserializedFailInfo, err := deserializeHTLCFailInfo(&buf)
1176+
require.NoError(t, err, "deserializeHTLCFailInfo should not fail "+
1177+
"with invalid TLV data")
1178+
require.NotNil(t, deserializedFailInfo)
1179+
1180+
// Verify the basic fields are correctly deserialized.
1181+
require.Equal(t, failInfo.Reason, deserializedFailInfo.Reason)
1182+
require.Equal(t, failInfo.FailureSourceIndex,
1183+
deserializedFailInfo.FailureSourceIndex)
1184+
1185+
// Verify the failure message is nil because the decoding failed
1186+
// due to invalid TLV data. The important part is that the
1187+
// HTLCFailInfo deserialization still succeeded despite the invalid
1188+
// TLV data in the failure message.
1189+
require.Nil(t, deserializedFailInfo.Message,
1190+
"Message should be nil when TLV parsing fails")
1191+
}

0 commit comments

Comments
 (0)