Skip to content

Commit c15b93d

Browse files
committed
Limit TLV stream decoding to type ranges
BOLT 12 messages are limited to a range of TLV record types. Refactor decode_tlv_stream into a decode_tlv_stream_range macro for limiting which types are parsed. Requires a SeekReadable trait for rewinding when a type outside of the range is seen. This allows for composing TLV streams of different ranges. Updates offer parsing accordingly and adds a test demonstrating failure if a type outside of the range is included.
1 parent 0af1aff commit c15b93d

File tree

4 files changed

+85
-19
lines changed

4 files changed

+85
-19
lines changed

lightning/src/offers/offer.rs

+26-12
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ use core::time::Duration;
7676
use crate::io;
7777
use crate::ln::features::OfferFeatures;
7878
use crate::ln::msgs::MAX_VALUE_MSAT;
79-
use crate::offers::parse::{Bech32Encode, ParseError, SemanticError};
79+
use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
8080
use crate::onion_message::BlindedPath;
81-
use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
81+
use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
8282
use crate::util::string::PrintableString;
8383

8484
use crate::prelude::*;
@@ -398,8 +398,8 @@ impl TryFrom<Vec<u8>> for Offer {
398398
type Error = ParseError;
399399

400400
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
401-
let tlv_stream: OfferTlvStream = Readable::read(&mut &bytes[..])?;
402-
Offer::try_from((bytes, tlv_stream))
401+
let parsed_message = ParsedMessage::<OfferTlvStream>::try_from(bytes)?;
402+
Offer::try_from(parsed_message)
403403
}
404404
}
405405

@@ -458,7 +458,7 @@ impl Quantity {
458458
}
459459
}
460460

461-
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, {
461+
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
462462
(2, chains: (Vec<ChainHash>, WithoutLength)),
463463
(4, metadata: (Vec<u8>, WithoutLength)),
464464
(6, currency: CurrencyCode),
@@ -476,8 +476,6 @@ impl Bech32Encode for Offer {
476476
const BECH32_HRP: &'static str = "lno";
477477
}
478478

479-
type ParsedOffer = (Vec<u8>, OfferTlvStream);
480-
481479
impl FromStr for Offer {
482480
type Err = ParseError;
483481

@@ -486,11 +484,11 @@ impl FromStr for Offer {
486484
}
487485
}
488486

489-
impl TryFrom<ParsedOffer> for Offer {
487+
impl TryFrom<ParsedMessage<OfferTlvStream>> for Offer {
490488
type Error = ParseError;
491489

492-
fn try_from(offer: ParsedOffer) -> Result<Self, Self::Error> {
493-
let (bytes, tlv_stream) = offer;
490+
fn try_from(offer: ParsedMessage<OfferTlvStream>) -> Result<Self, Self::Error> {
491+
let ParsedMessage { bytes, tlv_stream } = offer;
494492
let contents = OfferContents::try_from(tlv_stream)?;
495493
Ok(Offer { bytes, contents })
496494
}
@@ -560,10 +558,10 @@ mod tests {
560558
use core::num::NonZeroU64;
561559
use core::time::Duration;
562560
use crate::ln::features::OfferFeatures;
563-
use crate::ln::msgs::MAX_VALUE_MSAT;
561+
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
564562
use crate::offers::parse::{ParseError, SemanticError};
565563
use crate::onion_message::{BlindedHop, BlindedPath};
566-
use crate::util::ser::Writeable;
564+
use crate::util::ser::{BigSize, Writeable};
567565
use crate::util::string::PrintableString;
568566

569567
fn pubkey(byte: u8) -> PublicKey {
@@ -999,6 +997,22 @@ mod tests {
999997
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingNodeId)),
1000998
}
1001999
}
1000+
1001+
#[test]
1002+
fn fails_parsing_offer_with_extra_tlv_records() {
1003+
let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
1004+
1005+
let mut encoded_offer = Vec::new();
1006+
offer.write(&mut encoded_offer).unwrap();
1007+
BigSize(80).write(&mut encoded_offer).unwrap();
1008+
BigSize(32).write(&mut encoded_offer).unwrap();
1009+
[42u8; 32].write(&mut encoded_offer).unwrap();
1010+
1011+
match Offer::try_from(encoded_offer) {
1012+
Ok(_) => panic!("expected error"),
1013+
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
1014+
}
1015+
}
10021016
}
10031017

10041018
#[cfg(test)]

lightning/src/offers/parse.rs

+25
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ use bitcoin::bech32;
1313
use bitcoin::bech32::{FromBase32, ToBase32};
1414
use core::convert::TryFrom;
1515
use core::fmt;
16+
use crate::io;
1617
use crate::ln::msgs::DecodeError;
18+
use crate::util::ser::SeekReadable;
1719

1820
use crate::prelude::*;
1921

@@ -52,6 +54,29 @@ pub(crate) trait Bech32Encode: AsRef<[u8]> + TryFrom<Vec<u8>, Error=ParseError>
5254
}
5355
}
5456

57+
/// A wrapper for reading a message as a TLV stream `T` from a byte sequence, while still
58+
/// maintaining ownership of the bytes for later use.
59+
pub(crate) struct ParsedMessage<T: SeekReadable> {
60+
pub bytes: Vec<u8>,
61+
pub tlv_stream: T,
62+
}
63+
64+
impl<T: SeekReadable> TryFrom<Vec<u8>> for ParsedMessage<T> {
65+
type Error = DecodeError;
66+
67+
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
68+
let mut cursor = crate::io::Cursor::new(bytes);
69+
let tlv_stream: T = SeekReadable::read(&mut cursor)?;
70+
71+
if cursor.position() < cursor.get_ref().len() as u64 {
72+
return Err(DecodeError::InvalidValue);
73+
}
74+
75+
let bytes = cursor.into_inner();
76+
Ok(Self { bytes, tlv_stream })
77+
}
78+
}
79+
5580
/// Error when parsing a bech32 encoded message using [`str::parse`].
5681
#[derive(Debug, PartialEq)]
5782
pub enum ParseError {

lightning/src/util/ser.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//! as ChannelsManagers and ChannelMonitors.
1212
1313
use crate::prelude::*;
14-
use crate::io::{self, Read, Write};
14+
use crate::io::{self, Read, Seek, Write};
1515
use crate::io_extras::{copy, sink};
1616
use core::hash::Hash;
1717
use crate::sync::Mutex;
@@ -219,6 +219,13 @@ pub trait Readable
219219
fn read<R: Read>(reader: &mut R) -> Result<Self, DecodeError>;
220220
}
221221

222+
/// A trait that various rust-lightning types implement allowing them to be read in from a
223+
/// `Read + Seek`.
224+
pub(crate) trait SeekReadable where Self: Sized {
225+
/// Reads a Self in from the given Read
226+
fn read<R: Read + Seek>(reader: &mut R) -> Result<Self, DecodeError>;
227+
}
228+
222229
/// A trait that various higher-level rust-lightning types implement allowing them to be read in
223230
/// from a Read given some additional set of arguments which is required to deserialize.
224231
///

lightning/src/util/ser_macros.rs

+26-6
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,17 @@ macro_rules! decode_tlv {
201201
// `Ok(false)` if the message type is unknown, and `Err(DecodeError)` if parsing fails.
202202
macro_rules! decode_tlv_stream {
203203
($stream: expr, {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}
204+
$(, $decode_custom_tlv: expr)?) => { {
205+
let rewind = |_, _| { unreachable!() };
206+
use core::ops::RangeBounds;
207+
decode_tlv_stream_range!(
208+
$stream, .., rewind, {$(($type, $field, $fieldty)),*} $(, $decode_custom_tlv)?
209+
);
210+
} }
211+
}
212+
213+
macro_rules! decode_tlv_stream_range {
214+
($stream: expr, $range: expr, $rewind: ident, {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}
204215
$(, $decode_custom_tlv: expr)?) => { {
205216
use $crate::ln::msgs::DecodeError;
206217
let mut last_seen_type: Option<u64> = None;
@@ -215,7 +226,7 @@ macro_rules! decode_tlv_stream {
215226
// UnexpectedEof. This should in every case be largely cosmetic, but its nice to
216227
// pass the TLV test vectors exactly, which requre this distinction.
217228
let mut tracking_reader = ser::ReadTrackingReader::new(&mut stream_ref);
218-
match $crate::util::ser::Readable::read(&mut tracking_reader) {
229+
match <$crate::util::ser::BigSize as $crate::util::ser::Readable>::read(&mut tracking_reader) {
219230
Err(DecodeError::ShortRead) => {
220231
if !tracking_reader.have_read {
221232
break 'tlv_read;
@@ -224,7 +235,13 @@ macro_rules! decode_tlv_stream {
224235
}
225236
},
226237
Err(e) => return Err(e),
227-
Ok(t) => t,
238+
Ok(t) => if $range.contains(&t.0) { t } else {
239+
use $crate::util::ser::Writeable;
240+
drop(tracking_reader);
241+
let bytes_read = t.serialized_length();
242+
$rewind(stream_ref, bytes_read);
243+
break 'tlv_read;
244+
},
228245
}
229246
};
230247

@@ -472,7 +489,7 @@ macro_rules! impl_writeable_tlv_based {
472489
/// [`Readable`]: crate::util::ser::Readable
473490
/// [`Writeable`]: crate::util::ser::Writeable
474491
macro_rules! tlv_stream {
475-
($name:ident, $nameref:ident, {
492+
($name:ident, $nameref:ident, $range:expr, {
476493
$(($type:expr, $field:ident : $fieldty:tt)),* $(,)*
477494
}) => {
478495
#[derive(Debug)]
@@ -497,12 +514,15 @@ macro_rules! tlv_stream {
497514
}
498515
}
499516

500-
impl $crate::util::ser::Readable for $name {
501-
fn read<R: $crate::io::Read>(reader: &mut R) -> Result<Self, $crate::ln::msgs::DecodeError> {
517+
impl $crate::util::ser::SeekReadable for $name {
518+
fn read<R: $crate::io::Read + $crate::io::Seek>(reader: &mut R) -> Result<Self, $crate::ln::msgs::DecodeError> {
502519
$(
503520
init_tlv_field_var!($field, option);
504521
)*
505-
decode_tlv_stream!(reader, {
522+
let rewind = |cursor: &mut R, offset: usize| {
523+
cursor.seek($crate::io::SeekFrom::Current(-(offset as i64))).expect("");
524+
};
525+
decode_tlv_stream_range!(reader, $range, rewind, {
506526
$(($type, $field, (option, encoding: $fieldty))),*
507527
});
508528

0 commit comments

Comments
 (0)