Skip to content

Commit ca4e31d

Browse files
Merge pull request #1659 from valentinewallace/2022-08-fix-payload-decode
Fix payment onion payload decode
2 parents b414c06 + dfbebbf commit ca4e31d

File tree

3 files changed

+69
-38
lines changed

3 files changed

+69
-38
lines changed

lightning/src/ln/msgs.rs

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use io_extras::read_to_end;
4242

4343
use util::events::MessageSendEventsProvider;
4444
use util::logger;
45-
use util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt, Hostname};
45+
use util::ser::{BigSize, LengthReadable, Readable, ReadableArgs, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname};
4646

4747
use ln::{PaymentPreimage, PaymentHash, PaymentSecret};
4848

@@ -1375,14 +1375,14 @@ impl Writeable for OnionMessage {
13751375
impl Writeable for FinalOnionHopData {
13761376
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
13771377
self.payment_secret.0.write(w)?;
1378-
HighZeroBytesDroppedVarInt(self.total_msat).write(w)
1378+
HighZeroBytesDroppedBigSize(self.total_msat).write(w)
13791379
}
13801380
}
13811381

13821382
impl Readable for FinalOnionHopData {
13831383
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
13841384
let secret: [u8; 32] = Readable::read(r)?;
1385-
let amt: HighZeroBytesDroppedVarInt<u64> = Readable::read(r)?;
1385+
let amt: HighZeroBytesDroppedBigSize<u64> = Readable::read(r)?;
13861386
Ok(Self { payment_secret: PaymentSecret(secret), total_msat: amt.0 })
13871387
}
13881388
}
@@ -1399,15 +1399,15 @@ impl Writeable for OnionHopData {
13991399
},
14001400
OnionHopDataFormat::NonFinalNode { short_channel_id } => {
14011401
encode_varint_length_prefixed_tlv!(w, {
1402-
(2, HighZeroBytesDroppedVarInt(self.amt_to_forward), required),
1403-
(4, HighZeroBytesDroppedVarInt(self.outgoing_cltv_value), required),
1402+
(2, HighZeroBytesDroppedBigSize(self.amt_to_forward), required),
1403+
(4, HighZeroBytesDroppedBigSize(self.outgoing_cltv_value), required),
14041404
(6, short_channel_id, required)
14051405
});
14061406
},
14071407
OnionHopDataFormat::FinalNode { ref payment_data, ref keysend_preimage } => {
14081408
encode_varint_length_prefixed_tlv!(w, {
1409-
(2, HighZeroBytesDroppedVarInt(self.amt_to_forward), required),
1410-
(4, HighZeroBytesDroppedVarInt(self.outgoing_cltv_value), required),
1409+
(2, HighZeroBytesDroppedBigSize(self.amt_to_forward), required),
1410+
(4, HighZeroBytesDroppedBigSize(self.outgoing_cltv_value), required),
14111411
(8, payment_data, option),
14121412
(5482373484, keysend_preimage, option)
14131413
});
@@ -1417,36 +1417,23 @@ impl Writeable for OnionHopData {
14171417
}
14181418
}
14191419

1420-
// ReadableArgs because we need onion_utils::decode_next_hop to accommodate payment packets and
1421-
// onion message packets.
1422-
impl ReadableArgs<()> for OnionHopData {
1423-
fn read<R: Read>(r: &mut R, _arg: ()) -> Result<Self, DecodeError> {
1424-
<Self as Readable>::read(r)
1425-
}
1426-
}
1427-
14281420
impl Readable for OnionHopData {
1429-
fn read<R: Read>(mut r: &mut R) -> Result<Self, DecodeError> {
1430-
use bitcoin::consensus::encode::{Decodable, Error, VarInt};
1431-
let v: VarInt = Decodable::consensus_decode(&mut r)
1432-
.map_err(|e| match e {
1433-
Error::Io(ioe) => DecodeError::from(ioe),
1434-
_ => DecodeError::InvalidValue
1435-
})?;
1421+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
1422+
let b: BigSize = Readable::read(r)?;
14361423
const LEGACY_ONION_HOP_FLAG: u64 = 0;
1437-
let (format, amt, cltv_value) = if v.0 != LEGACY_ONION_HOP_FLAG {
1438-
let mut rd = FixedLengthReader::new(r, v.0);
1439-
let mut amt = HighZeroBytesDroppedVarInt(0u64);
1440-
let mut cltv_value = HighZeroBytesDroppedVarInt(0u32);
1424+
let (format, amt, cltv_value) = if b.0 != LEGACY_ONION_HOP_FLAG {
1425+
let mut rd = FixedLengthReader::new(r, b.0);
1426+
let mut amt = HighZeroBytesDroppedBigSize(0u64);
1427+
let mut cltv_value = HighZeroBytesDroppedBigSize(0u32);
14411428
let mut short_id: Option<u64> = None;
14421429
let mut payment_data: Option<FinalOnionHopData> = None;
14431430
let mut keysend_preimage: Option<PaymentPreimage> = None;
1444-
// The TLV type is chosen to be compatible with lnd and c-lightning.
14451431
decode_tlv_stream!(&mut rd, {
14461432
(2, amt, required),
14471433
(4, cltv_value, required),
14481434
(6, short_id, option),
14491435
(8, payment_data, option),
1436+
// See https://github.com/lightning/blips/blob/master/blip-0003.md
14501437
(5482373484, keysend_preimage, option)
14511438
});
14521439
rd.eat_remaining().map_err(|_| DecodeError::ShortRead)?;
@@ -1488,6 +1475,14 @@ impl Readable for OnionHopData {
14881475
}
14891476
}
14901477

1478+
// ReadableArgs because we need onion_utils::decode_next_hop to accommodate payment packets and
1479+
// onion message packets.
1480+
impl ReadableArgs<()> for OnionHopData {
1481+
fn read<R: Read>(r: &mut R, _arg: ()) -> Result<Self, DecodeError> {
1482+
<Self as Readable>::read(r)
1483+
}
1484+
}
1485+
14911486
impl Writeable for Ping {
14921487
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
14931488
self.ponglen.write(w)?;
@@ -1913,7 +1908,7 @@ mod tests {
19131908
use bitcoin::secp256k1::{PublicKey,SecretKey};
19141909
use bitcoin::secp256k1::{Secp256k1, Message};
19151910

1916-
use io::Cursor;
1911+
use io::{self, Cursor};
19171912
use prelude::*;
19181913
use core::convert::TryFrom;
19191914

@@ -2824,4 +2819,40 @@ mod tests {
28242819
assert_eq!(gossip_timestamp_filter.first_timestamp, 1590000000);
28252820
assert_eq!(gossip_timestamp_filter.timestamp_range, 0xffff_ffff);
28262821
}
2822+
2823+
#[test]
2824+
fn decode_onion_hop_data_len_as_bigsize() {
2825+
// Tests that we can decode an onion payload that is >253 bytes.
2826+
// Previously, receiving a payload of this size could've caused us to fail to decode a valid
2827+
// payload, because we were decoding the length (a BigSize, big-endian) as a VarInt
2828+
// (little-endian).
2829+
2830+
// Encode a test onion payload with a big custom TLV such that it's >253 bytes, forcing the
2831+
// payload length to be encoded over multiple bytes rather than a single u8.
2832+
let big_payload = encode_big_payload().unwrap();
2833+
let mut rd = Cursor::new(&big_payload[..]);
2834+
<msgs::OnionHopData as Readable>::read(&mut rd).unwrap();
2835+
}
2836+
// see above test, needs to be a separate method for use of the serialization macros.
2837+
fn encode_big_payload() -> Result<Vec<u8>, io::Error> {
2838+
use util::ser::HighZeroBytesDroppedBigSize;
2839+
let payload = msgs::OnionHopData {
2840+
format: OnionHopDataFormat::NonFinalNode {
2841+
short_channel_id: 0xdeadbeef1bad1dea,
2842+
},
2843+
amt_to_forward: 1000,
2844+
outgoing_cltv_value: 0xffffffff,
2845+
};
2846+
let mut encoded_payload = Vec::new();
2847+
let test_bytes = vec![42u8; 1000];
2848+
if let OnionHopDataFormat::NonFinalNode { short_channel_id } = payload.format {
2849+
encode_varint_length_prefixed_tlv!(&mut encoded_payload, {
2850+
(1, test_bytes, vec_type),
2851+
(2, HighZeroBytesDroppedBigSize(payload.amt_to_forward), required),
2852+
(4, HighZeroBytesDroppedBigSize(payload.outgoing_cltv_value), required),
2853+
(6, short_channel_id, required)
2854+
});
2855+
}
2856+
Ok(encoded_payload)
2857+
}
28272858
}

lightning/src/util/ser.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ impl Readable for BigSize {
400400
/// variable-length integer which is simply truncated by skipping high zero bytes. This type
401401
/// encapsulates such integers implementing Readable/Writeable for them.
402402
#[cfg_attr(test, derive(PartialEq, Debug))]
403-
pub(crate) struct HighZeroBytesDroppedVarInt<T>(pub T);
403+
pub(crate) struct HighZeroBytesDroppedBigSize<T>(pub T);
404404

405405
macro_rules! impl_writeable_primitive {
406406
($val_type:ty, $len: expr) => {
@@ -410,7 +410,7 @@ macro_rules! impl_writeable_primitive {
410410
writer.write_all(&self.to_be_bytes())
411411
}
412412
}
413-
impl Writeable for HighZeroBytesDroppedVarInt<$val_type> {
413+
impl Writeable for HighZeroBytesDroppedBigSize<$val_type> {
414414
#[inline]
415415
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
416416
// Skip any full leading 0 bytes when writing (in BE):
@@ -425,9 +425,9 @@ macro_rules! impl_writeable_primitive {
425425
Ok(<$val_type>::from_be_bytes(buf))
426426
}
427427
}
428-
impl Readable for HighZeroBytesDroppedVarInt<$val_type> {
428+
impl Readable for HighZeroBytesDroppedBigSize<$val_type> {
429429
#[inline]
430-
fn read<R: Read>(reader: &mut R) -> Result<HighZeroBytesDroppedVarInt<$val_type>, DecodeError> {
430+
fn read<R: Read>(reader: &mut R) -> Result<HighZeroBytesDroppedBigSize<$val_type>, DecodeError> {
431431
// We need to accept short reads (read_len == 0) as "EOF" and handle them as simply
432432
// the high bytes being dropped. To do so, we start reading into the middle of buf
433433
// and then convert the appropriate number of bytes with extra high bytes out of
@@ -443,7 +443,7 @@ macro_rules! impl_writeable_primitive {
443443
let first_byte = $len - ($len - total_read_len);
444444
let mut bytes = [0; $len];
445445
bytes.copy_from_slice(&buf[first_byte..first_byte + $len]);
446-
Ok(HighZeroBytesDroppedVarInt(<$val_type>::from_be_bytes(bytes)))
446+
Ok(HighZeroBytesDroppedBigSize(<$val_type>::from_be_bytes(bytes)))
447447
} else {
448448
// If the encoding had extra zero bytes, return a failure even though we know
449449
// what they meant (as the TLV test vectors require this)

lightning/src/util/ser_macros.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ mod tests {
563563
use io::{self, Cursor};
564564
use prelude::*;
565565
use ln::msgs::DecodeError;
566-
use util::ser::{Writeable, HighZeroBytesDroppedVarInt, VecWriter};
566+
use util::ser::{Writeable, HighZeroBytesDroppedBigSize, VecWriter};
567567
use bitcoin::secp256k1::PublicKey;
568568

569569
// The BOLT TLV test cases don't include any tests which use our "required-value" logic since
@@ -632,9 +632,9 @@ mod tests {
632632
}
633633

634634
// BOLT TLV test cases
635-
fn tlv_reader_n1(s: &[u8]) -> Result<(Option<HighZeroBytesDroppedVarInt<u64>>, Option<u64>, Option<(PublicKey, u64, u64)>, Option<u16>), DecodeError> {
635+
fn tlv_reader_n1(s: &[u8]) -> Result<(Option<HighZeroBytesDroppedBigSize<u64>>, Option<u64>, Option<(PublicKey, u64, u64)>, Option<u16>), DecodeError> {
636636
let mut s = Cursor::new(s);
637-
let mut tlv1: Option<HighZeroBytesDroppedVarInt<u64>> = None;
637+
let mut tlv1: Option<HighZeroBytesDroppedBigSize<u64>> = None;
638638
let mut tlv2: Option<u64> = None;
639639
let mut tlv3: Option<(PublicKey, u64, u64)> = None;
640640
let mut tlv4: Option<u16> = None;
@@ -765,11 +765,11 @@ mod tests {
765765
assert_eq!(stream.0, ::hex::decode("06fd00ff02abcd").unwrap());
766766

767767
stream.0.clear();
768-
encode_varint_length_prefixed_tlv!(&mut stream, {(0, 1u64, required), (42, None::<u64>, option), (0xff, HighZeroBytesDroppedVarInt(0u64), required)});
768+
encode_varint_length_prefixed_tlv!(&mut stream, {(0, 1u64, required), (42, None::<u64>, option), (0xff, HighZeroBytesDroppedBigSize(0u64), required)});
769769
assert_eq!(stream.0, ::hex::decode("0e00080000000000000001fd00ff00").unwrap());
770770

771771
stream.0.clear();
772-
encode_varint_length_prefixed_tlv!(&mut stream, {(0, Some(1u64), option), (0xff, HighZeroBytesDroppedVarInt(0u64), required)});
772+
encode_varint_length_prefixed_tlv!(&mut stream, {(0, Some(1u64), option), (0xff, HighZeroBytesDroppedBigSize(0u64), required)});
773773
assert_eq!(stream.0, ::hex::decode("0e00080000000000000001fd00ff00").unwrap());
774774

775775
Ok(())

0 commit comments

Comments
 (0)