commit 2a99b5c9df493f27989383f9b0fef80fb78b0569
parent 501b5455752bd45997c9fa5d747ff67c52ebb10e
Author: Jared Tobin <jared@jtobin.io>
Date: Sat, 10 Oct 2026 13:35:04 +0400
lib: harden TLV decoding
decodeTlv converted the record length to Int before checking it, so
a length of 2^64 - 1 wrapped to -1, passed the bounds check and
decoded as an empty value. Compare in Word64 instead.
Unknown even TLV types must cause a failure. decodeHopPayload kept
them in hpUnknownTlvs and decodeBlindedHopData skipped them; both now
fail. Hop payload types 16 (payment_metadata) and 18
(total_amount_msat) are defined by the spec but not yet typed, so they
are still accepted into hpUnknownTlvs.
Diffstat:
3 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/lib/Lightning/Protocol/BOLT4/Blinding.hs b/lib/Lightning/Protocol/BOLT4/Blinding.hs
@@ -209,6 +209,8 @@ encodeBlindedHopData !bhd = encodeTlvStream (buildTlvs bhd)
{-# INLINE encodeBlindedHopData #-}
-- | Decode TLV stream to BlindedHopData.
+--
+-- Fails on unknown even TLV types; unknown odd types are skipped.
decodeBlindedHopData :: BS.ByteString -> Maybe BlindedHopData
decodeBlindedHopData !bs = do
tlvs <- decodeTlvStream bs
@@ -238,7 +240,8 @@ parseBlindedHopData = go emptyHopData
pc <- decodePaymentConstraints val
go bhd { bhdPaymentConstraints = Just pc } rest
14 -> go bhd { bhdAllowedFeatures = Just val } rest
- _ -> go bhd rest -- Skip unknown TLVs
+ _ | even typ -> Nothing
+ | otherwise -> go bhd rest
-- PaymentRelay encoding/decoding --------------------------------------------
diff --git a/lib/Lightning/Protocol/BOLT4/Codec.hs b/lib/Lightning/Protocol/BOLT4/Codec.hs
@@ -125,11 +125,12 @@ decodeTlv :: BS.ByteString -> Maybe (TlvRecord, BS.ByteString)
decodeTlv !bs = do
(typ, r1) <- decodeBigSize bs
(len, r2) <- decodeBigSize r1
- let !len' = fromIntegral len
- if BS.length r2 < len'
+ -- compare in Word64 so that large lengths cannot wrap
+ if len > fromIntegral (BS.length r2)
then Nothing
else do
- let !val = BS.take len' r2
+ let !len' = fromIntegral len
+ !val = BS.take len' r2
!rest = BS.drop len' r2
Just (TlvRecord typ val, rest)
{-# INLINE decodeTlv #-}
@@ -199,6 +200,10 @@ encodeHopPayload !hp = encodeTlvStream (buildTlvs hp)
in amt' ++ cltv' ++ sci' ++ pd' ++ ed' ++ cpk' ++ unk
-- | Decode HopPayload from bytes.
+--
+-- Fails on unknown even TLV types. Types 16 (payment_metadata) and 18
+-- (total_amount_msat) are accepted and kept, with unknown odd types,
+-- in 'hpUnknownTlvs'.
decodeHopPayload :: BS.ByteString -> Maybe HopPayload
decodeHopPayload !bs = do
tlvs <- decodeTlvStream bs
@@ -227,7 +232,10 @@ parseHopPayload = go emptyHop
go hp { hpPaymentData = Just pd } rest
10 -> go hp { hpEncryptedData = Just val } rest
12 -> go hp { hpCurrentPathKey = Just val } rest
- _ -> go hp { hpUnknownTlvs = TlvRecord typ val : hpUnknownTlvs hp } rest
+ _ | even typ && typ /= 16 && typ /= 18 -> Nothing
+ | otherwise ->
+ go hp { hpUnknownTlvs = TlvRecord typ val : hpUnknownTlvs hp }
+ rest
-- ShortChannelId -----------------------------------------------------------
diff --git a/lib/Lightning/Protocol/BOLT4/Types.hs b/lib/Lightning/Protocol/BOLT4/Types.hs
@@ -163,7 +163,7 @@ data HopPayload = HopPayload
, hpPaymentData :: !(Maybe PaymentData) -- ^ TLV type 8
, hpEncryptedData :: !(Maybe BS.ByteString) -- ^ TLV type 10
, hpCurrentPathKey :: !(Maybe BS.ByteString) -- ^ TLV type 12
- , hpUnknownTlvs :: ![TlvRecord] -- ^ Unknown types
+ , hpUnknownTlvs :: ![TlvRecord] -- ^ 16, 18 and unknown odd
} deriving (Eq, Show, Generic)
-- | Payment data for final hop (TLV type 8).