Skip to content

Commit 1e9f00b

Browse files
committed
Add support for variable-length onion payload reads using TLV
1 parent 910151f commit 1e9f00b

File tree

8 files changed

+184
-48
lines changed

8 files changed

+184
-48
lines changed

fuzz/src/msg_targets/gen_target.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ GEN_TEST NodeAnnouncement test_msg_exact ""
3434

3535
GEN_TEST UpdateAddHTLC test_msg_hole ", 85, 33"
3636
GEN_TEST ErrorMessage test_msg_hole ", 32, 2"
37-
GEN_TEST OnionHopData test_msg_hole ", 1+8+8+4, 12"
3837

3938
GEN_TEST Init test_msg_simple ""
39+
GEN_TEST OnionHopData test_msg_simple ""
4040
GEN_TEST Ping test_msg_simple ""
4141
GEN_TEST Pong test_msg_simple ""

fuzz/src/msg_targets/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub mod msg_channel_update;
2020
pub mod msg_node_announcement;
2121
pub mod msg_update_add_htlc;
2222
pub mod msg_error_message;
23-
pub mod msg_onion_hop_data;
2423
pub mod msg_init;
24+
pub mod msg_onion_hop_data;
2525
pub mod msg_ping;
2626
pub mod msg_pong;

fuzz/src/msg_targets/msg_onion_hop_data.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use msg_targets::utils::VecWriter;
77

88
#[inline]
99
pub fn do_test(data: &[u8]) {
10-
test_msg_hole!(msgs::OnionHopData, data, 1+8+8+4, 12);
10+
test_msg_simple!(msgs::OnionHopData, data);
1111
}
1212

1313
#[no_mangle]

lightning/src/ln/channelmanager.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -929,14 +929,17 @@ impl<ChanSigner: ChannelKeys> ChannelManager<ChanSigner> {
929929
Err(err) => {
930930
let error_code = match err {
931931
msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte
932+
msgs::DecodeError::UnknownRequiredFeature|
933+
msgs::DecodeError::InvalidValue|
934+
msgs::DecodeError::ShortRead => 0x4000 | 22, // invalid_onion_payload
932935
_ => 0x2000 | 2, // Should never happen
933936
};
934937
return_err!("Unable to decode our hop data", error_code, &[0;0]);
935938
},
936939
Ok(msg) => {
937940
let mut hmac = [0; 32];
938941
if let Err(_) = chacha_stream.read_exact(&mut hmac[..]) {
939-
return_err!("Unable to decode our hop data", 0x4000 | 1, &[0;0]);
942+
return_err!("Unable to decode our hop data", 0x4000 | 22, &[0;0]);
940943
}
941944
(msg, hmac)
942945
},
@@ -990,6 +993,15 @@ impl<ChanSigner: ChannelKeys> ChannelManager<ChanSigner> {
990993
} else {
991994
let mut new_packet_data = [0; 20*65];
992995
let read_pos = chacha_stream.read(&mut new_packet_data).unwrap();
996+
#[cfg(debug_assertions)]
997+
{
998+
// Check two things:
999+
// a) that the behavior of our stream here will return Ok(0) even if the TLV
1000+
// read above emptied out our buffer and the unwrap() wont needlessly panic
1001+
// b) that we didn't somehow magically end up with extra data.
1002+
let mut t = [0; 1];
1003+
debug_assert!(chacha_stream.read(&mut t).unwrap() == 0);
1004+
}
9931005
chacha_stream.chacha.process_inline(&mut new_packet_data[read_pos..]);
9941006

9951007
let mut new_pubkey = msg.onion_routing_packet.public_key.unwrap();
@@ -1012,10 +1024,18 @@ impl<ChanSigner: ChannelKeys> ChannelManager<ChanSigner> {
10121024
hmac: next_hop_hmac.clone(),
10131025
};
10141026

1027+
let short_channel_id = match next_hop_data.format {
1028+
msgs::OnionHopDataFormat::Legacy { short_channel_id } => short_channel_id,
1029+
msgs::OnionHopDataFormat::NonFinalNode { short_channel_id } => short_channel_id,
1030+
msgs::OnionHopDataFormat::FinalNode => {
1031+
return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0;0]);
1032+
},
1033+
};
1034+
10151035
PendingHTLCStatus::Forward(PendingForwardHTLCInfo {
10161036
onion_packet: Some(outgoing_packet),
10171037
payment_hash: msg.payment_hash.clone(),
1018-
short_channel_id: next_hop_data.short_channel_id,
1038+
short_channel_id: short_channel_id,
10191039
incoming_shared_secret: shared_secret,
10201040
amt_to_forward: next_hop_data.amt_to_forward,
10211041
outgoing_cltv_value: next_hop_data.outgoing_cltv_value,

lightning/src/ln/features.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,13 +190,21 @@ impl<T: sealed::Context> Features<T> {
190190

191191
pub(crate) fn requires_unknown_bits(&self) -> bool {
192192
self.flags.iter().enumerate().any(|(idx, &byte)| {
193-
( idx != 0 && (byte & 0x55) != 0 ) || ( idx == 0 && (byte & 0x14) != 0 )
193+
(match idx {
194+
0 => (byte & 0b00010100),
195+
1 => (byte & 0b01010100),
196+
_ => (byte & 0b01010101),
197+
}) != 0
194198
})
195199
}
196200

197201
pub(crate) fn supports_unknown_bits(&self) -> bool {
198202
self.flags.iter().enumerate().any(|(idx, &byte)| {
199-
( idx != 0 && byte != 0 ) || ( idx == 0 && (byte & 0xc4) != 0 )
203+
(match idx {
204+
0 => (byte & 0b11000100),
205+
1 => (byte & 0b11111100),
206+
_ => byte,
207+
}) != 0
200208
})
201209
}
202210

lightning/src/ln/functional_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4900,7 +4900,7 @@ fn test_onion_failure() {
49004900
}
49014901
new_payloads[0].data[0] = 1;
49024902
msg.onion_routing_packet = onion_utils::construct_onion_packet_bogus_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash);
4903-
}, ||{}, true, Some(PERM|1), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));//XXX incremented channels idx here
4903+
}, ||{}, true, Some(PERM|22), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));//XXX incremented channels idx here
49044904

49054905
// final node failure
49064906
run_onion_failure_test("invalid_realm", 3, &nodes, &route, &payment_hash, |msg| {
@@ -4914,7 +4914,7 @@ fn test_onion_failure() {
49144914
}
49154915
new_payloads[1].data[0] = 1;
49164916
msg.onion_routing_packet = onion_utils::construct_onion_packet_bogus_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash);
4917-
}, ||{}, false, Some(PERM|1), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
4917+
}, ||{}, false, Some(PERM|22), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
49184918

49194919
// the following three with run_onion_failure_test_with_fail_intercept() test only the origin node
49204920
// receiving simulated fail messages

lightning/src/ln/msgs.rs

Lines changed: 129 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use std::io::Read;
2929
use std::result::Result;
3030

3131
use util::events;
32-
use util::ser::{Readable, Writeable, Writer};
32+
use util::ser::{Readable, Writeable, Writer, FixedLengthReader};
3333

3434
use ln::channelmanager::{PaymentPreimage, PaymentHash};
3535

@@ -39,7 +39,7 @@ pub enum DecodeError {
3939
/// A version byte specified something we don't know how to handle.
4040
/// Includes unknown realm byte in an OnionHopData packet
4141
UnknownVersion,
42-
/// Unknown feature mandating we fail to parse message
42+
/// Unknown feature mandating we fail to parse message (eg TLV with an even, unknown type)
4343
UnknownRequiredFeature,
4444
/// Value was invalid, eg a byte which was supposed to be a bool was something other than a 0
4545
/// or 1, a public key/private key/signature was invalid, text wasn't UTF-8, etc
@@ -610,15 +610,20 @@ mod fuzzy_internal_msgs {
610610
// them from untrusted input):
611611

612612
pub(crate) enum OnionHopDataFormat {
613-
Legacy, // aka Realm-0
613+
Legacy { // aka Realm-0
614+
short_channel_id: u64,
615+
},
616+
NonFinalNode {
617+
short_channel_id: u64,
618+
},
619+
FinalNode,
614620
}
615621

616622
pub struct OnionHopData {
617623
pub(crate) format: OnionHopDataFormat,
618-
pub(crate) short_channel_id: u64,
619624
pub(crate) amt_to_forward: u64,
620625
pub(crate) outgoing_cltv_value: u32,
621-
// 12 bytes of 0-padding
626+
// 12 bytes of 0-padding for Legacy format
622627
}
623628

624629
pub struct DecodedOnionErrorPacket {
@@ -959,33 +964,78 @@ impl Writeable for OnionHopData {
959964
fn write<W: Writer>(&self, w: &mut W) -> Result<(), ::std::io::Error> {
960965
w.size_hint(33);
961966
match self.format {
962-
OnionHopDataFormat::Legacy => 0u8.write(w)?,
967+
OnionHopDataFormat::Legacy { short_channel_id } => {
968+
0u8.write(w)?;
969+
short_channel_id.write(w)?;
970+
self.amt_to_forward.write(w)?;
971+
self.outgoing_cltv_value.write(w)?;
972+
},
973+
OnionHopDataFormat::NonFinalNode { short_channel_id } => {
974+
encode_varint_length_prefixed_tlv!(w, {
975+
(2, self.amt_to_forward),
976+
(4, self.outgoing_cltv_value),
977+
(6, short_channel_id)
978+
});
979+
},
980+
OnionHopDataFormat::FinalNode => {
981+
encode_varint_length_prefixed_tlv!(w, {
982+
(2, self.amt_to_forward),
983+
(4, self.outgoing_cltv_value)
984+
});
985+
},
986+
}
987+
match self.format {
988+
OnionHopDataFormat::Legacy { .. } => {
989+
w.write_all(&[0;12])?;
990+
},
991+
_ => {},
963992
}
964-
self.short_channel_id.write(w)?;
965-
self.amt_to_forward.write(w)?;
966-
self.outgoing_cltv_value.write(w)?;
967-
w.write_all(&[0;12])?;
968993
Ok(())
969994
}
970995
}
971996

972997
impl<R: Read> Readable<R> for OnionHopData {
973-
fn read(r: &mut R) -> Result<Self, DecodeError> {
974-
Ok(OnionHopData {
975-
format: {
976-
let r: u8 = Readable::read(r)?;
977-
if r != 0 {
978-
return Err(DecodeError::UnknownVersion);
998+
fn read(mut r: &mut R) -> Result<Self, DecodeError> {
999+
use bitcoin::consensus::encode::{Decodable, Error, VarInt};
1000+
let v: VarInt = Decodable::consensus_decode(&mut r)
1001+
.map_err(|e| match e {
1002+
Error::Io(ioe) => DecodeError::from(ioe),
1003+
_ => DecodeError::InvalidValue
1004+
})?;
1005+
let (format, amt, cltv_value) = if v.0 != 0 {
1006+
let mut rd = FixedLengthReader { read: r, read_len: 0, max_len: v.0 };
1007+
let mut amt: u64 = 0;
1008+
let mut cltv_value: u32 = 0;
1009+
let mut short_id: Option<u64> = None;
1010+
decode_tlv!(&mut rd, {
1011+
(2, amt),
1012+
(4, cltv_value)
1013+
}, {
1014+
(6, short_id)
1015+
});
1016+
rd.eat_remaining().map_err(|_| DecodeError::ShortRead)?;
1017+
let format = if let Some(short_channel_id) = short_id {
1018+
OnionHopDataFormat::NonFinalNode {
1019+
short_channel_id,
9791020
}
980-
OnionHopDataFormat::Legacy
981-
},
982-
short_channel_id: Readable::read(r)?,
983-
amt_to_forward: Readable::read(r)?,
984-
outgoing_cltv_value: {
985-
let v: u32 = Readable::read(r)?;
986-
r.read_exact(&mut [0; 12])?;
987-
v
988-
},
1021+
} else {
1022+
OnionHopDataFormat::FinalNode
1023+
};
1024+
(format, amt, cltv_value)
1025+
} else {
1026+
let format = OnionHopDataFormat::Legacy {
1027+
short_channel_id: Readable::read(r)?,
1028+
};
1029+
let amt: u64 = Readable::read(r)?;
1030+
let cltv_value: u32 = Readable::read(r)?;
1031+
r.read_exact(&mut [0; 12])?;
1032+
(format, amt, cltv_value)
1033+
};
1034+
1035+
Ok(OnionHopData {
1036+
format,
1037+
amt_to_forward: amt,
1038+
outgoing_cltv_value: cltv_value,
9891039
})
9901040
}
9911041
}
@@ -1271,9 +1321,9 @@ impl_writeable_len_match!(NodeAnnouncement, {
12711321
mod tests {
12721322
use hex;
12731323
use ln::msgs;
1274-
use ln::msgs::{ChannelFeatures, InitFeatures, NodeFeatures, OptionalField, OnionErrorPacket};
1324+
use ln::msgs::{ChannelFeatures, InitFeatures, NodeFeatures, OptionalField, OnionErrorPacket, OnionHopDataFormat};
12751325
use ln::channelmanager::{PaymentPreimage, PaymentHash};
1276-
use util::ser::Writeable;
1326+
use util::ser::{Writeable, Readable};
12771327

12781328
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
12791329
use bitcoin_hashes::hex::FromHex;
@@ -1285,6 +1335,8 @@ mod tests {
12851335
use secp256k1::key::{PublicKey,SecretKey};
12861336
use secp256k1::{Secp256k1, Message};
12871337

1338+
use std::io::Cursor;
1339+
12881340
#[test]
12891341
fn encoding_channel_reestablish_no_secret() {
12901342
let cr = msgs::ChannelReestablish {
@@ -1924,4 +1976,54 @@ mod tests {
19241976
let target_value = hex::decode("004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
19251977
assert_eq!(encoded_value, target_value);
19261978
}
1979+
1980+
#[test]
1981+
fn encoding_legacy_onion_hop_data() {
1982+
let msg = msgs::OnionHopData {
1983+
format: OnionHopDataFormat::Legacy {
1984+
short_channel_id: 0xdeadbeef1bad1dea,
1985+
},
1986+
amt_to_forward: 0x0badf00d01020304,
1987+
outgoing_cltv_value: 0xffffffff,
1988+
};
1989+
let encoded_value = msg.encode();
1990+
let target_value = hex::decode("00deadbeef1bad1dea0badf00d01020304ffffffff000000000000000000000000").unwrap();
1991+
assert_eq!(encoded_value, target_value);
1992+
}
1993+
1994+
#[test]
1995+
fn encoding_nonfinal_onion_hop_data() {
1996+
let mut msg = msgs::OnionHopData {
1997+
format: OnionHopDataFormat::NonFinalNode {
1998+
short_channel_id: 0xdeadbeef1bad1dea,
1999+
},
2000+
amt_to_forward: 0x0badf00d01020304,
2001+
outgoing_cltv_value: 0xffffffff,
2002+
};
2003+
let encoded_value = msg.encode();
2004+
let target_value = hex::decode("1a02080badf00d010203040404ffffffff0608deadbeef1bad1dea").unwrap();
2005+
assert_eq!(encoded_value, target_value);
2006+
msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
2007+
if let OnionHopDataFormat::NonFinalNode { short_channel_id } = msg.format {
2008+
assert_eq!(short_channel_id, 0xdeadbeef1bad1dea);
2009+
} else { panic!(); }
2010+
assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
2011+
assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
2012+
}
2013+
2014+
#[test]
2015+
fn encoding_final_onion_hop_data() {
2016+
let mut msg = msgs::OnionHopData {
2017+
format: OnionHopDataFormat::FinalNode,
2018+
amt_to_forward: 0x0badf00d01020304,
2019+
outgoing_cltv_value: 0xffffffff,
2020+
};
2021+
let encoded_value = msg.encode();
2022+
let target_value = hex::decode("1002080badf00d010203040404ffffffff").unwrap();
2023+
assert_eq!(encoded_value, target_value);
2024+
msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
2025+
if let OnionHopDataFormat::FinalNode = msg.format { } else { panic!(); }
2026+
assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
2027+
assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
2028+
}
19272029
}

0 commit comments

Comments
 (0)