bolt4

Onion routing protocol, per BOLT #4 (docs.ppad.tech/bolt4).
git clone git://git.ppad.tech/bolt4.git
Log | Files | Refs | README | LICENSE

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:
Mlib/Lightning/Protocol/BOLT4/Blinding.hs | 5++++-
Mlib/Lightning/Protocol/BOLT4/Codec.hs | 16++++++++++++----
Mlib/Lightning/Protocol/BOLT4/Types.hs | 2+-
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).