Skip to content

Commit 635d1df

Browse files
committed
WIP: Limit TLV stream decoding to type ranges
1 parent e39da9a commit 635d1df

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 crate::io;
5858
use crate::ln::features::OfferFeatures;
59+
use crate::ln::msgs::DecodeError;
5960
use crate::offers::merkle::{SignatureTlvStream, SignatureTlvStreamRef, self};
6061
use crate::offers::offer::{Amount, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
6162
use crate::offers::parse::{ParseError, SemanticError};
6263
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
63-
use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
64+
use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
6465

6566
use crate::prelude::*;
6667

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

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

346-
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, {
354+
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
347355
(80, chain: ChainHash),
348356
(82, amount: (u64, HighZeroBytesDroppedBigSize)),
349357
(84, features: OfferFeatures),
@@ -357,6 +365,17 @@ type ParsedInvoiceRequest = (Vec<u8>, FullInvoiceRequestTlvStream);
357365
type FullInvoiceRequestTlvStream =
358366
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream);
359367

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

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

lightning/src/offers/merkle.rs

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

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

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

8585
use crate::prelude::*;
@@ -474,7 +474,14 @@ impl TryFrom<Vec<u8>> for Offer {
474474
type Error = ParseError;
475475

476476
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
477-
let tlv_stream: OfferTlvStream = Readable::read(&mut &bytes[..])?;
477+
let mut cursor = io::Cursor::new(bytes);
478+
let tlv_stream: OfferTlvStream = SeekReadable::read(&mut cursor)?;
479+
480+
if cursor.position() < cursor.get_ref().len() as u64 {
481+
return Err(ParseError::Decode(DecodeError::InvalidValue));
482+
}
483+
484+
let bytes = cursor.into_inner();
478485
Offer::try_from((bytes, tlv_stream))
479486
}
480487
}
@@ -534,7 +541,7 @@ impl Quantity {
534541
}
535542
}
536543

537-
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, {
544+
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
538545
(2, chains: (Vec<ChainHash>, WithoutLength)),
539546
(4, metadata: (Vec<u8>, WithoutLength)),
540547
(6, currency: CurrencyCode),
@@ -636,10 +643,10 @@ mod tests {
636643
use core::num::NonZeroU64;
637644
use core::time::Duration;
638645
use crate::ln::features::OfferFeatures;
639-
use crate::ln::msgs::MAX_VALUE_MSAT;
646+
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
640647
use crate::offers::parse::{ParseError, SemanticError};
641648
use crate::onion_message::{BlindedHop, BlindedPath};
642-
use crate::util::ser::Writeable;
649+
use crate::util::ser::{BigSize, Writeable};
643650
use crate::util::string::PrintableString;
644651

645652
fn pubkey(byte: u8) -> PublicKey {
@@ -1081,6 +1088,22 @@ mod tests {
10811088
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingNodeId)),
10821089
}
10831090
}
1091+
1092+
#[test]
1093+
fn fails_parsing_offer_with_extra_tlv_records() {
1094+
let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
1095+
1096+
let mut encoded_offer = Vec::new();
1097+
offer.write(&mut encoded_offer).unwrap();
1098+
BigSize(80).write(&mut encoded_offer).unwrap();
1099+
BigSize(32).write(&mut encoded_offer).unwrap();
1100+
[42u8; 32].write(&mut encoded_offer).unwrap();
1101+
1102+
match Offer::try_from(encoded_offer) {
1103+
Ok(_) => panic!("expected error"),
1104+
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
1105+
}
1106+
}
10841107
}
10851108

10861109
#[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 crate::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 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;
@@ -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 $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)