Skip to content

Commit 94a07d9

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 03d0a4b commit 94a07d9

File tree

4 files changed

+87
-26
lines changed

4 files changed

+87
-26
lines changed

lightning/src/offers/offer.rs

+25-19
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::*;
@@ -394,15 +394,6 @@ impl Writeable for OfferContents {
394394
}
395395
}
396396

397-
impl TryFrom<Vec<u8>> for Offer {
398-
type Error = ParseError;
399-
400-
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))
403-
}
404-
}
405-
406397
/// The minimum amount required for an item in an [`Offer`], denominated in either bitcoin or
407398
/// another currency.
408399
#[derive(Clone, Debug, PartialEq)]
@@ -449,7 +440,7 @@ impl Quantity {
449440
}
450441
}
451442

452-
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, {
443+
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
453444
(2, chains: (Vec<ChainHash>, WithoutLength)),
454445
(4, metadata: (Vec<u8>, WithoutLength)),
455446
(6, currency: CurrencyCode),
@@ -467,8 +458,6 @@ impl Bech32Encode for Offer {
467458
const BECH32_HRP: &'static str = "lno";
468459
}
469460

470-
type ParsedOffer = (Vec<u8>, OfferTlvStream);
471-
472461
impl FromStr for Offer {
473462
type Err = ParseError;
474463

@@ -477,11 +466,12 @@ impl FromStr for Offer {
477466
}
478467
}
479468

480-
impl TryFrom<ParsedOffer> for Offer {
469+
impl TryFrom<Vec<u8>> for Offer {
481470
type Error = ParseError;
482471

483-
fn try_from(offer: ParsedOffer) -> Result<Self, Self::Error> {
484-
let (bytes, tlv_stream) = offer;
472+
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
473+
let offer = ParsedMessage::<OfferTlvStream>::try_from(bytes)?;
474+
let ParsedMessage { bytes, tlv_stream } = offer;
485475
let contents = OfferContents::try_from(tlv_stream)?;
486476
Ok(Offer { bytes, contents })
487477
}
@@ -551,10 +541,10 @@ mod tests {
551541
use core::num::NonZeroU64;
552542
use core::time::Duration;
553543
use crate::ln::features::OfferFeatures;
554-
use crate::ln::msgs::MAX_VALUE_MSAT;
544+
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
555545
use crate::offers::parse::{ParseError, SemanticError};
556546
use crate::onion_message::{BlindedHop, BlindedPath};
557-
use crate::util::ser::Writeable;
547+
use crate::util::ser::{BigSize, Writeable};
558548
use crate::util::string::PrintableString;
559549

560550
fn pubkey(byte: u8) -> PublicKey {
@@ -1003,6 +993,22 @@ mod tests {
1003993
},
1004994
}
1005995
}
996+
997+
#[test]
998+
fn fails_parsing_offer_with_extra_tlv_records() {
999+
let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
1000+
1001+
let mut encoded_offer = Vec::new();
1002+
offer.write(&mut encoded_offer).unwrap();
1003+
BigSize(80).write(&mut encoded_offer).unwrap();
1004+
BigSize(32).write(&mut encoded_offer).unwrap();
1005+
[42u8; 32].write(&mut encoded_offer).unwrap();
1006+
1007+
match Offer::try_from(encoded_offer) {
1008+
Ok(_) => panic!("expected error"),
1009+
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
1010+
}
1011+
}
10061012
}
10071013

10081014
#[cfg(test)]

lightning/src/offers/parse.rs

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

@@ -74,6 +76,30 @@ impl<'a> AsRef<str> for Bech32String<'a> {
7476
}
7577
}
7678

79+
/// A wrapper for reading a message as a TLV stream `T` from a byte sequence, while still
80+
/// maintaining ownership of the bytes for later use.
81+
pub(crate) struct ParsedMessage<T: SeekReadable> {
82+
pub bytes: Vec<u8>,
83+
pub tlv_stream: T,
84+
}
85+
86+
impl<T: SeekReadable> TryFrom<Vec<u8>> for ParsedMessage<T> {
87+
type Error = DecodeError;
88+
89+
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
90+
let mut cursor = io::Cursor::new(bytes);
91+
let tlv_stream: T = SeekReadable::read(&mut cursor)?;
92+
93+
// Ensure that there are no more TLV records left to parse.
94+
if cursor.position() < cursor.get_ref().len() as u64 {
95+
return Err(DecodeError::InvalidValue);
96+
}
97+
98+
let bytes = cursor.into_inner();
99+
Ok(Self { bytes, tlv_stream })
100+
}
101+
}
102+
77103
/// Error when parsing a bech32 encoded message using [`str::parse`].
78104
#[derive(Debug, PartialEq)]
79105
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)