Skip to content

Commit 5fcd9f3

Browse files
committed
WIP: Limit TLV stream decoding to type ranges
1 parent 16afa02 commit 5fcd9f3

File tree

7 files changed

+128
-21
lines changed

7 files changed

+128
-21
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,12 @@ use bitcoin::secp256k1::schnorr::Signature;
5656
use core::convert::TryFrom;
5757
use io;
5858
use ln::features::OfferFeatures;
59+
use ln::msgs::DecodeError;
5960
use offers::merkle::{SignatureTlvStream, SignatureTlvStreamRef, self};
6061
use offers::offer::{Amount, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
6162
use offers::parse::{ParseError, SemanticError};
6263
use offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
63-
use util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
64+
use util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
6465

6566
use prelude::*;
6667

@@ -341,12 +342,19 @@ impl TryFrom<Vec<u8>> for InvoiceRequest {
341342
type Error = ParseError;
342343

343344
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
344-
let tlv_stream: FullInvoiceRequestTlvStream = Readable::read(&mut &bytes[..])?;
345+
let mut cursor = io::Cursor::new(bytes);
346+
let tlv_stream: FullInvoiceRequestTlvStream = SeekReadable::read(&mut cursor)?;
347+
348+
if cursor.position() < cursor.get_ref().len() as u64 {
349+
return Err(ParseError::Decode(DecodeError::InvalidValue));
350+
}
351+
352+
let bytes = cursor.into_inner();
345353
InvoiceRequest::try_from((bytes, tlv_stream))
346354
}
347355
}
348356

349-
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, {
357+
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
350358
(80, chain: ChainHash),
351359
(82, amount: (u64, HighZeroBytesDroppedBigSize)),
352360
(84, features: OfferFeatures),
@@ -360,6 +368,17 @@ type ParsedInvoiceRequest = (Vec<u8>, FullInvoiceRequestTlvStream);
360368
type FullInvoiceRequestTlvStream =
361369
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream);
362370

371+
impl SeekReadable for FullInvoiceRequestTlvStream {
372+
fn read<R: io::Read + io::Seek>(r: &mut R) -> Result<Self, DecodeError> {
373+
let payer = SeekReadable::read(r)?;
374+
let offer = SeekReadable::read(r)?;
375+
let invoice_request = SeekReadable::read(r)?;
376+
let signature = SeekReadable::read(r)?;
377+
378+
Ok((payer, offer, invoice_request, signature))
379+
}
380+
}
381+
363382
type PartialInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
364383

365384
type PartialInvoiceRequestTlvStreamRef<'a> = (
@@ -448,3 +467,40 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
448467
})
449468
}
450469
}
470+
471+
#[cfg(test)]
472+
mod tests {
473+
use super::InvoiceRequest;
474+
475+
use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey};
476+
use core::convert::TryFrom;
477+
use ln::msgs::DecodeError;
478+
use offers::offer::OfferBuilder;
479+
use offers::parse::ParseError;
480+
use util::ser::{BigSize, Writeable};
481+
482+
#[test]
483+
fn fails_parsing_invoice_request_with_extra_tlv_records() {
484+
let secp_ctx = Secp256k1::new();
485+
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
486+
let invoice_request = OfferBuilder::new("foo".into(), keys.public_key())
487+
.build()
488+
.unwrap()
489+
.request_invoice(keys.public_key())
490+
.build()
491+
.unwrap()
492+
.sign(|digest| secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
493+
.unwrap();
494+
495+
let mut encoded_invoice_request = Vec::new();
496+
invoice_request.write(&mut encoded_invoice_request).unwrap();
497+
BigSize(1002).write(&mut encoded_invoice_request).unwrap();
498+
BigSize(32).write(&mut encoded_invoice_request).unwrap();
499+
[42u8; 32].write(&mut encoded_invoice_request).unwrap();
500+
501+
match InvoiceRequest::try_from(encoded_invoice_request) {
502+
Ok(_) => panic!("expected error"),
503+
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
504+
}
505+
}
506+
}

lightning/src/offers/merkle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use prelude::*;
1919
/// Valid type range for signature TLV records.
2020
const SIGNATURE_TYPES: core::ops::RangeInclusive<u64> = 240..=1000;
2121

22-
tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, {
22+
tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, {
2323
(240, signature: Signature),
2424
});
2525

lightning/src/offers/offer.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ use core::str::FromStr;
7777
use core::time::Duration;
7878
use io;
7979
use ln::features::OfferFeatures;
80-
use ln::msgs::MAX_VALUE_MSAT;
80+
use ln::msgs::{DecodeError, MAX_VALUE_MSAT};
8181
use offers::invoice_request::InvoiceRequestBuilder;
8282
use offers::parse::{Bech32Encode, ParseError, SemanticError};
8383
use onion_message::BlindedPath;
84-
use util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
84+
use util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
8585
use util::string::PrintableString;
8686

8787
use prelude::*;
@@ -499,7 +499,14 @@ impl TryFrom<Vec<u8>> for Offer {
499499
type Error = ParseError;
500500

501501
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
502-
let tlv_stream: OfferTlvStream = Readable::read(&mut &bytes[..])?;
502+
let mut cursor = io::Cursor::new(bytes);
503+
let tlv_stream: OfferTlvStream = SeekReadable::read(&mut cursor)?;
504+
505+
if cursor.position() < cursor.get_ref().len() as u64 {
506+
return Err(ParseError::Decode(DecodeError::InvalidValue));
507+
}
508+
509+
let bytes = cursor.into_inner();
503510
Offer::try_from((bytes, tlv_stream))
504511
}
505512
}
@@ -535,7 +542,7 @@ impl Amount {
535542
/// An ISO 4712 three-letter currency code (e.g., USD).
536543
pub type CurrencyCode = [u8; 3];
537544

538-
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, {
545+
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
539546
(2, chains: (Vec<ChainHash>, WithoutLength)),
540547
(4, metadata: (Vec<u8>, WithoutLength)),
541548
(6, currency: CurrencyCode),
@@ -655,10 +662,10 @@ mod tests {
655662
use core::num::NonZeroU64;
656663
use core::time::Duration;
657664
use ln::features::OfferFeatures;
658-
use ln::msgs::MAX_VALUE_MSAT;
665+
use ln::msgs::{DecodeError, MAX_VALUE_MSAT};
659666
use offers::parse::{ParseError, SemanticError};
660667
use onion_message::{BlindedHop, BlindedPath};
661-
use util::ser::Writeable;
668+
use util::ser::{BigSize, Writeable};
662669
use util::string::PrintableString;
663670

664671
fn pubkey(byte: u8) -> PublicKey {
@@ -1196,6 +1203,22 @@ mod tests {
11961203
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingNodeId)),
11971204
}
11981205
}
1206+
1207+
#[test]
1208+
fn fails_parsing_offer_with_extra_tlv_records() {
1209+
let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
1210+
1211+
let mut encoded_offer = Vec::new();
1212+
offer.write(&mut encoded_offer).unwrap();
1213+
BigSize(80).write(&mut encoded_offer).unwrap();
1214+
BigSize(32).write(&mut encoded_offer).unwrap();
1215+
[42u8; 32].write(&mut encoded_offer).unwrap();
1216+
1217+
match Offer::try_from(encoded_offer) {
1218+
Ok(_) => panic!("expected error"),
1219+
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
1220+
}
1221+
}
11991222
}
12001223

12011224
#[cfg(test)]

lightning/src/offers/parse.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@ use bitcoin::bech32;
1313
use bitcoin::bech32::{FromBase32, ToBase32};
1414
use bitcoin::secp256k1;
1515
use core::fmt;
16+
use io;
1617
use ln::msgs::DecodeError;
17-
use util::ser::Readable;
18+
use util::ser::SeekReadable;
1819

1920
use prelude::*;
2021

2122
/// Indicates a message can be encoded using bech32.
2223
pub(crate) trait Bech32Encode: AsRef<[u8]> {
2324
/// TLV stream that a bech32-encoded message is parsed into.
24-
type TlvStream: Readable;
25+
type TlvStream: SeekReadable;
2526

2627
/// Human readable part of the message's bech32 encoding.
2728
const BECH32_HRP: &'static str;
@@ -44,7 +45,10 @@ pub(crate) trait Bech32Encode: AsRef<[u8]> {
4445
}
4546

4647
let data = Vec::<u8>::from_base32(&data)?;
47-
Ok((Readable::read(&mut &data[..])?, data))
48+
let mut cursor = io::Cursor::new(data);
49+
let tlv_stream = SeekReadable::read(&mut cursor)?;
50+
let bytes = cursor.into_inner();
51+
Ok((tlv_stream, bytes))
4852
}
4953

5054
/// Formats the message using bech32-encoding.

lightning/src/offers/payer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ use prelude::*;
2020
#[derive(Clone, Debug)]
2121
pub(crate) struct PayerContents(pub Option<Vec<u8>>);
2222

23-
tlv_stream!(PayerTlvStream, PayerTlvStreamRef, {
23+
tlv_stream!(PayerTlvStream, PayerTlvStreamRef, 0..1, {
2424
(0, metadata: (Vec<u8>, WithoutLength)),
2525
});

lightning/src/util/ser.rs

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

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

lightning/src/util/ser_macros.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@ macro_rules! decode_tlv {
197197

198198
macro_rules! decode_tlv_stream {
199199
($stream: expr, {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}) => { {
200+
let rewind = |_, _| { unreachable!() };
201+
use core::ops::RangeBounds;
202+
decode_tlv_stream_range!($stream, .., rewind, {$(($type, $field, $fieldty)),*});
203+
} }
204+
}
205+
206+
macro_rules! decode_tlv_stream_range {
207+
($stream: expr, $range: expr, $rewind: ident, {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}) => { {
200208
use ln::msgs::DecodeError;
201209
let mut last_seen_type: Option<u64> = None;
202210
let mut stream_ref = $stream;
@@ -210,7 +218,7 @@ macro_rules! decode_tlv_stream {
210218
// UnexpectedEof. This should in every case be largely cosmetic, but its nice to
211219
// pass the TLV test vectors exactly, which requre this distinction.
212220
let mut tracking_reader = ser::ReadTrackingReader::new(&mut stream_ref);
213-
match ser::Readable::read(&mut tracking_reader) {
221+
match <ser::BigSize as ser::Readable>::read(&mut tracking_reader) {
214222
Err(DecodeError::ShortRead) => {
215223
if !tracking_reader.have_read {
216224
break 'tlv_read;
@@ -219,7 +227,13 @@ macro_rules! decode_tlv_stream {
219227
}
220228
},
221229
Err(e) => return Err(e),
222-
Ok(t) => t,
230+
Ok(t) => if $range.contains(&t.0) { t } else {
231+
use util::ser::Writeable;
232+
drop(tracking_reader);
233+
let bytes_read = t.serialized_length();
234+
$rewind(stream_ref, bytes_read);
235+
break 'tlv_read;
236+
},
223237
}
224238
};
225239

@@ -458,7 +472,7 @@ macro_rules! impl_writeable_tlv_based {
458472
/// [`Readable`]: crate::util::ser::Readable
459473
/// [`Writeable`]: crate::util::ser::Writeable
460474
macro_rules! tlv_stream {
461-
($name:ident, $nameref:ident, {
475+
($name:ident, $nameref:ident, $range:expr, {
462476
$(($type:expr, $field:ident : $fieldty:tt)),* $(,)*
463477
}) => {
464478
#[derive(Debug)]
@@ -483,12 +497,15 @@ macro_rules! tlv_stream {
483497
}
484498
}
485499

486-
impl ::util::ser::Readable for $name {
487-
fn read<R: $crate::io::Read>(reader: &mut R) -> Result<Self, ::ln::msgs::DecodeError> {
500+
impl ::util::ser::SeekReadable for $name {
501+
fn read<R: $crate::io::Read + $crate::io::Seek>(reader: &mut R) -> Result<Self, ::ln::msgs::DecodeError> {
488502
$(
489503
init_tlv_field_var!($field, option);
490504
)*
491-
decode_tlv_stream!(reader, {
505+
let rewind = |cursor: &mut R, offset: usize| {
506+
cursor.seek($crate::io::SeekFrom::Current(-(offset as i64))).expect("");
507+
};
508+
decode_tlv_stream_range!(reader, $range, rewind, {
492509
$(($type, $field, (option, encoding: $fieldty))),*
493510
});
494511

0 commit comments

Comments
 (0)