Skip to content

Commit d708afe

Browse files
committed
WIP: Limit TLV stream decoding to type ranges
1 parent 21684be commit d708afe

File tree

6 files changed

+124
-18
lines changed

6 files changed

+124
-18
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

@@ -339,12 +340,19 @@ impl TryFrom<Vec<u8>> for InvoiceRequest {
339340
type Error = ParseError;
340341

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

347-
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, {
355+
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
348356
(80, chain: ChainHash),
349357
(82, amount: (u64, HighZeroBytesDroppedBigSize)),
350358
(84, features: OfferFeatures),
@@ -358,6 +366,17 @@ type ParsedInvoiceRequest = (Vec<u8>, FullInvoiceRequestTlvStream);
358366
type FullInvoiceRequestTlvStream =
359367
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream);
360368

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

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

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
@@ -75,11 +75,11 @@ use core::str::FromStr;
7575
use core::time::Duration;
7676
use io;
7777
use ln::features::OfferFeatures;
78-
use ln::msgs::MAX_VALUE_MSAT;
78+
use ln::msgs::{DecodeError, MAX_VALUE_MSAT};
7979
use offers::invoice_request::InvoiceRequestBuilder;
8080
use offers::parse::{Bech32Encode, ParseError, SemanticError};
8181
use onion_message::BlindedPath;
82-
use util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
82+
use util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
8383
use util::string::PrintableString;
8484

8585
use prelude::*;
@@ -447,7 +447,14 @@ impl TryFrom<Vec<u8>> for Offer {
447447
type Error = ParseError;
448448

449449
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
450-
let tlv_stream: OfferTlvStream = Readable::read(&mut &bytes[..])?;
450+
let mut cursor = io::Cursor::new(bytes);
451+
let tlv_stream: OfferTlvStream = SeekReadable::read(&mut cursor)?;
452+
453+
if cursor.position() < cursor.get_ref().len() as u64 {
454+
return Err(ParseError::Decode(DecodeError::InvalidValue));
455+
}
456+
457+
let bytes = cursor.into_inner();
451458
Offer::try_from((bytes, tlv_stream))
452459
}
453460
}
@@ -512,7 +519,7 @@ impl Quantity {
512519
}
513520
}
514521

515-
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, {
522+
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
516523
(2, chains: (Vec<ChainHash>, WithoutLength)),
517524
(4, metadata: (Vec<u8>, WithoutLength)),
518525
(6, currency: CurrencyCode),
@@ -611,10 +618,10 @@ mod tests {
611618
use core::num::NonZeroU64;
612619
use core::time::Duration;
613620
use ln::features::OfferFeatures;
614-
use ln::msgs::MAX_VALUE_MSAT;
621+
use ln::msgs::{DecodeError, MAX_VALUE_MSAT};
615622
use offers::parse::{ParseError, SemanticError};
616623
use onion_message::{BlindedHop, BlindedPath};
617-
use util::ser::Writeable;
624+
use util::ser::{BigSize, Writeable};
618625
use util::string::PrintableString;
619626

620627
fn pubkey(byte: u8) -> PublicKey {
@@ -1033,6 +1040,22 @@ mod tests {
10331040
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingNodeId)),
10341041
}
10351042
}
1043+
1044+
#[test]
1045+
fn fails_parsing_offer_with_extra_tlv_records() {
1046+
let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
1047+
1048+
let mut encoded_offer = Vec::new();
1049+
offer.write(&mut encoded_offer).unwrap();
1050+
BigSize(80).write(&mut encoded_offer).unwrap();
1051+
BigSize(32).write(&mut encoded_offer).unwrap();
1052+
[42u8; 32].write(&mut encoded_offer).unwrap();
1053+
1054+
match Offer::try_from(encoded_offer) {
1055+
Ok(_) => panic!("expected error"),
1056+
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
1057+
}
1058+
}
10361059
}
10371060

10381061
#[cfg(test)]

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: 26 additions & 6 deletions
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 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 ser::Readable::read(&mut tracking_reader) {
229+
match <ser::BigSize as 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 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 ::util::ser::Readable for $name {
501-
fn read<R: $crate::io::Read>(reader: &mut R) -> Result<Self, ::ln::msgs::DecodeError> {
517+
impl ::util::ser::SeekReadable for $name {
518+
fn read<R: $crate::io::Read + $crate::io::Seek>(reader: &mut R) -> Result<Self, ::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)