Skip to content

Commit d109b7f

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 b34f5d5 commit d109b7f

File tree

4 files changed

+87
-19
lines changed

4 files changed

+87
-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

@@ -449,7 +449,7 @@ impl Quantity {
449449
}
450450
}
451451

452-
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, {
452+
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
453453
(2, chains: (Vec<ChainHash>, WithoutLength)),
454454
(4, metadata: (Vec<u8>, WithoutLength)),
455455
(6, currency: CurrencyCode),
@@ -467,8 +467,6 @@ impl Bech32Encode for Offer {
467467
const BECH32_HRP: &'static str = "lno";
468468
}
469469

470-
type ParsedOffer = (Vec<u8>, OfferTlvStream);
471-
472470
impl FromStr for Offer {
473471
type Err = ParseError;
474472

@@ -477,11 +475,11 @@ impl FromStr for Offer {
477475
}
478476
}
479477

480-
impl TryFrom<ParsedOffer> for Offer {
478+
impl TryFrom<ParsedMessage<OfferTlvStream>> for Offer {
481479
type Error = ParseError;
482480

483-
fn try_from(offer: ParsedOffer) -> Result<Self, Self::Error> {
484-
let (bytes, tlv_stream) = offer;
481+
fn try_from(offer: ParsedMessage<OfferTlvStream>) -> Result<Self, Self::Error> {
482+
let ParsedMessage { bytes, tlv_stream } = offer;
485483
let contents = OfferContents::try_from(tlv_stream)?;
486484
Ok(Offer { bytes, contents })
487485
}
@@ -548,10 +546,10 @@ mod tests {
548546
use core::num::NonZeroU64;
549547
use core::time::Duration;
550548
use crate::ln::features::OfferFeatures;
551-
use crate::ln::msgs::MAX_VALUE_MSAT;
549+
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
552550
use crate::offers::parse::{ParseError, SemanticError};
553551
use crate::onion_message::{BlindedHop, BlindedPath};
554-
use crate::util::ser::Writeable;
552+
use crate::util::ser::{BigSize, Writeable};
555553
use crate::util::string::PrintableString;
556554

557555
fn pubkey(byte: u8) -> PublicKey {
@@ -988,6 +986,22 @@ mod tests {
988986
},
989987
}
990988
}
989+
990+
#[test]
991+
fn fails_parsing_offer_with_extra_tlv_records() {
992+
let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
993+
994+
let mut encoded_offer = Vec::new();
995+
offer.write(&mut encoded_offer).unwrap();
996+
BigSize(80).write(&mut encoded_offer).unwrap();
997+
BigSize(32).write(&mut encoded_offer).unwrap();
998+
[42u8; 32].write(&mut encoded_offer).unwrap();
999+
1000+
match Offer::try_from(encoded_offer) {
1001+
Ok(_) => panic!("expected error"),
1002+
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
1003+
}
1004+
}
9911005
}
9921006

9931007
#[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 = 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

+28-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,15 @@ 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+
drop(tracking_reader);
240+
241+
// Assumes the type id is minimally encoded, which is enforced on read.
242+
use $crate::util::ser::Writeable;
243+
let bytes_read = t.serialized_length();
244+
$rewind(stream_ref, bytes_read);
245+
break 'tlv_read;
246+
},
228247
}
229248
};
230249

@@ -472,7 +491,7 @@ macro_rules! impl_writeable_tlv_based {
472491
/// [`Readable`]: crate::util::ser::Readable
473492
/// [`Writeable`]: crate::util::ser::Writeable
474493
macro_rules! tlv_stream {
475-
($name:ident, $nameref:ident, {
494+
($name:ident, $nameref:ident, $range:expr, {
476495
$(($type:expr, $field:ident : $fieldty:tt)),* $(,)*
477496
}) => {
478497
#[derive(Debug)]
@@ -497,12 +516,15 @@ macro_rules! tlv_stream {
497516
}
498517
}
499518

500-
impl $crate::util::ser::Readable for $name {
501-
fn read<R: $crate::io::Read>(reader: &mut R) -> Result<Self, $crate::ln::msgs::DecodeError> {
519+
impl $crate::util::ser::SeekReadable for $name {
520+
fn read<R: $crate::io::Read + $crate::io::Seek>(reader: &mut R) -> Result<Self, $crate::ln::msgs::DecodeError> {
502521
$(
503522
init_tlv_field_var!($field, option);
504523
)*
505-
decode_tlv_stream!(reader, {
524+
let rewind = |cursor: &mut R, offset: usize| {
525+
cursor.seek($crate::io::SeekFrom::Current(-(offset as i64))).expect("");
526+
};
527+
decode_tlv_stream_range!(reader, $range, rewind, {
506528
$(($type, $field, (option, encoding: $fieldty))),*
507529
});
508530

0 commit comments

Comments
 (0)