Skip to content

Commit 3c9e8c9

Browse files
authored
Merge pull request #434 from TheBlueMatt/2019-12-var-len-onion
TLV-based Variable Length Onion
2 parents 1619d08 + bec0a26 commit 3c9e8c9

13 files changed

+977
-184
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]

fuzz/src/msg_targets/utils.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ impl Writer for VecWriter {
1313
}
1414
}
1515

16+
// We attempt to test the strictest behavior we can for a given message, however, some messages
17+
// have different expected behavior. You can see which messages have which behavior in
18+
// gen_target.sh, but, in general, the *_announcement messages have to round-trip exactly (as
19+
// otherwise we'd invalidate the signatures), most messages just need to round-trip up to the
20+
// amount of data we know how to interpret, and some messages we may throw out invalid stuff (eg
21+
// if an error message isn't valid UTF-8 we cant String-ize it), so they wont roundtrip correctly.
22+
23+
// Tests a message that must survive roundtrip exactly, though may not empty the read buffer
24+
// entirely
1625
#[macro_export]
1726
macro_rules! test_msg {
1827
($MsgType: path, $data: ident) => {
@@ -31,6 +40,8 @@ macro_rules! test_msg {
3140
}
3241
}
3342

43+
// Tests a message that may lose data on roundtrip, but shoulnd't lose data compared to our
44+
// re-serialization.
3445
#[macro_export]
3546
macro_rules! test_msg_simple {
3647
($MsgType: path, $data: ident) => {
@@ -40,11 +51,18 @@ macro_rules! test_msg_simple {
4051
if let Ok(msg) = <$MsgType as Readable<::std::io::Cursor<&[u8]>>>::read(&mut r) {
4152
let mut w = VecWriter(Vec::new());
4253
msg.write(&mut w).unwrap();
54+
55+
let msg = <$MsgType as Readable<::std::io::Cursor<&[u8]>>>::read(&mut ::std::io::Cursor::new(&w.0)).unwrap();
56+
let mut w_two = VecWriter(Vec::new());
57+
msg.write(&mut w_two).unwrap();
58+
assert_eq!(&w.0[..], &w_two.0[..]);
4359
}
4460
}
4561
}
4662
}
4763

64+
// Tests a message that must survive roundtrip exactly, and must exactly empty the read buffer and
65+
// split it back out on re-serialization.
4866
#[macro_export]
4967
macro_rules! test_msg_exact {
5068
($MsgType: path, $data: ident) => {
@@ -54,13 +72,14 @@ macro_rules! test_msg_exact {
5472
if let Ok(msg) = <$MsgType as Readable<::std::io::Cursor<&[u8]>>>::read(&mut r) {
5573
let mut w = VecWriter(Vec::new());
5674
msg.write(&mut w).unwrap();
57-
5875
assert_eq!(&r.into_inner()[..], &w.0[..]);
5976
}
6077
}
6178
}
6279
}
6380

81+
// Tests a message that must survive roundtrip exactly, modulo one "hole" which may be set to 0s on
82+
// re-serialization.
6483
#[macro_export]
6584
macro_rules! test_msg_hole {
6685
($MsgType: path, $data: ident, $hole: expr, $hole_len: expr) => {

lightning/src/ln/channelmanager.rs

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,19 @@ use chain::keysinterface::{ChannelKeys, KeysInterface, InMemoryChannelKeys};
3838
use util::config::UserConfig;
3939
use util::{byte_utils, events};
4040
use util::ser::{Readable, ReadableArgs, Writeable, Writer};
41-
use util::chacha20::ChaCha20;
41+
use util::chacha20::{ChaCha20, ChaChaReader};
4242
use util::logger::Logger;
4343
use util::errors::APIError;
4444

4545
use std::{cmp, mem};
4646
use std::collections::{HashMap, hash_map, HashSet};
47-
use std::io::Cursor;
47+
use std::io::{Cursor, Read};
4848
use std::sync::{Arc, Mutex, MutexGuard, RwLock};
4949
use std::sync::atomic::{AtomicUsize, Ordering};
5050
use std::time::Duration;
5151
use std::marker::{Sync, Send};
5252
use std::ops::Deref;
5353

54-
const SIXTY_FIVE_ZEROS: [u8; 65] = [0; 65];
55-
5654
// We hold various information about HTLC relay in the HTLC objects in Channel itself:
5755
//
5856
// Upon receipt of an HTLC from a peer, we'll give it a PendingHTLCStatus indicating if it should
@@ -906,22 +904,30 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
906904
}
907905

908906
let mut chacha = ChaCha20::new(&rho, &[0u8; 8]);
909-
let next_hop_data = {
910-
let mut decoded = [0; 65];
911-
chacha.process(&msg.onion_routing_packet.hop_data[0..65], &mut decoded);
912-
match msgs::OnionHopData::read(&mut Cursor::new(&decoded[..])) {
907+
let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&msg.onion_routing_packet.hop_data[..]) };
908+
let (next_hop_data, next_hop_hmac) = {
909+
match msgs::OnionHopData::read(&mut chacha_stream) {
913910
Err(err) => {
914911
let error_code = match err {
915912
msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte
913+
msgs::DecodeError::UnknownRequiredFeature|
914+
msgs::DecodeError::InvalidValue|
915+
msgs::DecodeError::ShortRead => 0x4000 | 22, // invalid_onion_payload
916916
_ => 0x2000 | 2, // Should never happen
917917
};
918918
return_err!("Unable to decode our hop data", error_code, &[0;0]);
919919
},
920-
Ok(msg) => msg
920+
Ok(msg) => {
921+
let mut hmac = [0; 32];
922+
if let Err(_) = chacha_stream.read_exact(&mut hmac[..]) {
923+
return_err!("Unable to decode hop data", 0x4000 | 22, &[0;0]);
924+
}
925+
(msg, hmac)
926+
},
921927
}
922928
};
923929

924-
let pending_forward_info = if next_hop_data.hmac == [0; 32] {
930+
let pending_forward_info = if next_hop_hmac == [0; 32] {
925931
#[cfg(test)]
926932
{
927933
// In tests, make sure that the initial onion pcket data is, at least, non-0.
@@ -931,10 +937,11 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
931937
// as-is (and were originally 0s).
932938
// Of course reverse path calculation is still pretty easy given naive routing
933939
// algorithms, but this fixes the most-obvious case.
934-
let mut new_packet_data = [0; 19*65];
935-
chacha.process(&msg.onion_routing_packet.hop_data[65..], &mut new_packet_data[0..19*65]);
936-
assert_ne!(new_packet_data[0..65], [0; 65][..]);
937-
assert_ne!(new_packet_data[..], [0; 19*65][..]);
940+
let mut next_bytes = [0; 32];
941+
chacha_stream.read_exact(&mut next_bytes).unwrap();
942+
assert_ne!(next_bytes[..], [0; 32][..]);
943+
chacha_stream.read_exact(&mut next_bytes).unwrap();
944+
assert_ne!(next_bytes[..], [0; 32][..]);
938945
}
939946

940947
// OUR PAYMENT!
@@ -943,11 +950,11 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
943950
return_err!("The final CLTV expiry is too soon to handle", 17, &[0;0]);
944951
}
945952
// final_incorrect_htlc_amount
946-
if next_hop_data.data.amt_to_forward > msg.amount_msat {
953+
if next_hop_data.amt_to_forward > msg.amount_msat {
947954
return_err!("Upstream node sent less than we were supposed to receive in payment", 19, &byte_utils::be64_to_array(msg.amount_msat));
948955
}
949956
// final_incorrect_cltv_expiry
950-
if next_hop_data.data.outgoing_cltv_value != msg.cltv_expiry {
957+
if next_hop_data.outgoing_cltv_value != msg.cltv_expiry {
951958
return_err!("Upstream node set CLTV to the wrong value", 18, &byte_utils::be32_to_array(msg.cltv_expiry));
952959
}
953960

@@ -961,13 +968,24 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
961968
payment_hash: msg.payment_hash.clone(),
962969
short_channel_id: 0,
963970
incoming_shared_secret: shared_secret,
964-
amt_to_forward: next_hop_data.data.amt_to_forward,
965-
outgoing_cltv_value: next_hop_data.data.outgoing_cltv_value,
971+
amt_to_forward: next_hop_data.amt_to_forward,
972+
outgoing_cltv_value: next_hop_data.outgoing_cltv_value,
966973
})
967974
} else {
968975
let mut new_packet_data = [0; 20*65];
969-
chacha.process(&msg.onion_routing_packet.hop_data[65..], &mut new_packet_data[0..19*65]);
970-
chacha.process(&SIXTY_FIVE_ZEROS[..], &mut new_packet_data[19*65..]);
976+
let read_pos = chacha_stream.read(&mut new_packet_data).unwrap();
977+
#[cfg(debug_assertions)]
978+
{
979+
// Check two things:
980+
// a) that the behavior of our stream here will return Ok(0) even if the TLV
981+
// read above emptied out our buffer and the unwrap() wont needlessly panic
982+
// b) that we didn't somehow magically end up with extra data.
983+
let mut t = [0; 1];
984+
debug_assert!(chacha_stream.read(&mut t).unwrap() == 0);
985+
}
986+
// Once we've emptied the set of bytes our peer gave us, encrypt 0 bytes until we
987+
// fill the onion hop data we'll forward to our next-hop peer.
988+
chacha_stream.chacha.process_in_place(&mut new_packet_data[read_pos..]);
971989

972990
let mut new_pubkey = msg.onion_routing_packet.public_key.unwrap();
973991

@@ -986,16 +1004,24 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
9861004
version: 0,
9871005
public_key,
9881006
hop_data: new_packet_data,
989-
hmac: next_hop_data.hmac.clone(),
1007+
hmac: next_hop_hmac.clone(),
1008+
};
1009+
1010+
let short_channel_id = match next_hop_data.format {
1011+
msgs::OnionHopDataFormat::Legacy { short_channel_id } => short_channel_id,
1012+
msgs::OnionHopDataFormat::NonFinalNode { short_channel_id } => short_channel_id,
1013+
msgs::OnionHopDataFormat::FinalNode => {
1014+
return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0;0]);
1015+
},
9901016
};
9911017

9921018
PendingHTLCStatus::Forward(PendingForwardHTLCInfo {
9931019
onion_packet: Some(outgoing_packet),
9941020
payment_hash: msg.payment_hash.clone(),
995-
short_channel_id: next_hop_data.data.short_channel_id,
1021+
short_channel_id: short_channel_id,
9961022
incoming_shared_secret: shared_secret,
997-
amt_to_forward: next_hop_data.data.amt_to_forward,
998-
outgoing_cltv_value: next_hop_data.data.outgoing_cltv_value,
1023+
amt_to_forward: next_hop_data.amt_to_forward,
1024+
outgoing_cltv_value: next_hop_data.outgoing_cltv_value,
9991025
})
10001026
};
10011027

@@ -1137,6 +1163,9 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
11371163
let onion_keys = secp_call!(onion_utils::construct_onion_keys(&self.secp_ctx, &route, &session_priv),
11381164
APIError::RouteError{err: "Pubkey along hop was maliciously selected"});
11391165
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height)?;
1166+
if onion_utils::route_size_insane(&onion_payloads) {
1167+
return Err(APIError::RouteError{err: "Route size too large considering onion data"});
1168+
}
11401169
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, prng_seed, &payment_hash);
11411170

11421171
let _ = self.total_consistency_lock.read().unwrap();

lightning/src/ln/features.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ mod sealed { // You should just use the type aliases instead.
2929
pub trait UpfrontShutdownScript: Context {}
3030
impl UpfrontShutdownScript for InitContext {}
3131
impl UpfrontShutdownScript for NodeContext {}
32+
33+
pub trait VariableLengthOnion: Context {}
34+
impl VariableLengthOnion for InitContext {}
35+
impl VariableLengthOnion for NodeContext {}
3236
}
3337

3438
/// Tracks the set of features which a node implements, templated by the context in which it
@@ -69,7 +73,7 @@ impl InitFeatures {
6973
/// Create a Features with the features we support
7074
pub fn supported() -> InitFeatures {
7175
InitFeatures {
72-
flags: vec![2 | 1 << 5],
76+
flags: vec![2 | 1 << 5, 1 << (9-8)],
7377
mark: PhantomData,
7478
}
7579
}
@@ -132,14 +136,14 @@ impl NodeFeatures {
132136
#[cfg(not(feature = "fuzztarget"))]
133137
pub(crate) fn supported() -> NodeFeatures {
134138
NodeFeatures {
135-
flags: vec![2 | 1 << 5],
139+
flags: vec![2 | 1 << 5, 1 << (9-8)],
136140
mark: PhantomData,
137141
}
138142
}
139143
#[cfg(feature = "fuzztarget")]
140144
pub fn supported() -> NodeFeatures {
141145
NodeFeatures {
142-
flags: vec![2 | 1 << 5],
146+
flags: vec![2 | 1 << 5, 1 << (9-8)],
143147
mark: PhantomData,
144148
}
145149
}
@@ -182,13 +186,21 @@ impl<T: sealed::Context> Features<T> {
182186

183187
pub(crate) fn requires_unknown_bits(&self) -> bool {
184188
self.flags.iter().enumerate().any(|(idx, &byte)| {
185-
( idx != 0 && (byte & 0x55) != 0 ) || ( idx == 0 && (byte & 0x14) != 0 )
189+
(match idx {
190+
0 => (byte & 0b00010100),
191+
1 => (byte & 0b01010100),
192+
_ => (byte & 0b01010101),
193+
}) != 0
186194
})
187195
}
188196

189197
pub(crate) fn supports_unknown_bits(&self) -> bool {
190198
self.flags.iter().enumerate().any(|(idx, &byte)| {
191-
( idx != 0 && byte != 0 ) || ( idx == 0 && (byte & 0xc4) != 0 )
199+
(match idx {
200+
0 => (byte & 0b11000100),
201+
1 => (byte & 0b11111100),
202+
_ => byte,
203+
}) != 0
192204
})
193205
}
194206

@@ -232,6 +244,12 @@ impl<T: sealed::UpfrontShutdownScript> Features<T> {
232244
}
233245
}
234246

247+
impl<T: sealed::VariableLengthOnion> Features<T> {
248+
pub(crate) fn supports_variable_length_onion(&self) -> bool {
249+
self.flags.len() > 1 && (self.flags[1] & 3) != 0
250+
}
251+
}
252+
235253
impl<T: sealed::InitialRoutingSync> Features<T> {
236254
pub(crate) fn initial_routing_sync(&self) -> bool {
237255
self.flags.len() > 0 && (self.flags[0] & (1 << 3)) != 0

lightning/src/ln/functional_tests.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use util::enforcing_trait_impls::EnforcingChannelKeys;
1818
use util::test_utils;
1919
use util::events::{Event, EventsProvider, MessageSendEvent, MessageSendEventsProvider};
2020
use util::errors::APIError;
21-
use util::ser::{Writeable, ReadableArgs};
21+
use util::ser::{Writeable, Writer, ReadableArgs};
2222
use util::config::UserConfig;
2323
use util::logger::Logger;
2424

@@ -44,7 +44,7 @@ use std::collections::{BTreeSet, HashMap, HashSet};
4444
use std::default::Default;
4545
use std::sync::{Arc, Mutex};
4646
use std::sync::atomic::Ordering;
47-
use std::mem;
47+
use std::{mem, io};
4848

4949
use rand::{thread_rng, Rng};
5050

@@ -5007,6 +5007,20 @@ impl msgs::ChannelUpdate {
50075007
}
50085008
}
50095009

5010+
struct BogusOnionHopData {
5011+
data: Vec<u8>
5012+
}
5013+
impl BogusOnionHopData {
5014+
fn new(orig: msgs::OnionHopData) -> Self {
5015+
Self { data: orig.encode() }
5016+
}
5017+
}
5018+
impl Writeable for BogusOnionHopData {
5019+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
5020+
writer.write_all(&self.data[..])
5021+
}
5022+
}
5023+
50105024
#[test]
50115025
fn test_onion_failure() {
50125026
use ln::msgs::ChannelUpdate;
@@ -5036,19 +5050,31 @@ fn test_onion_failure() {
50365050
let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
50375051
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
50385052
let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height).unwrap();
5039-
onion_payloads[0].realm = 3;
5040-
msg.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
5041-
}, ||{}, 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
5053+
let mut new_payloads = Vec::new();
5054+
for payload in onion_payloads.drain(..) {
5055+
new_payloads.push(BogusOnionHopData::new(payload));
5056+
}
5057+
// break the first (non-final) hop payload by swapping the realm (0) byte for a byte
5058+
// describing a length-1 TLV payload, which is obviously bogus.
5059+
new_payloads[0].data[0] = 1;
5060+
msg.onion_routing_packet = onion_utils::construct_onion_packet_bogus_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash);
5061+
}, ||{}, 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
50425062

50435063
// final node failure
50445064
run_onion_failure_test("invalid_realm", 3, &nodes, &route, &payment_hash, |msg| {
50455065
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
50465066
let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
50475067
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
50485068
let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height).unwrap();
5049-
onion_payloads[1].realm = 3;
5050-
msg.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
5051-
}, ||{}, false, Some(PERM|1), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
5069+
let mut new_payloads = Vec::new();
5070+
for payload in onion_payloads.drain(..) {
5071+
new_payloads.push(BogusOnionHopData::new(payload));
5072+
}
5073+
// break the last-hop payload by swapping the realm (0) byte for a byte describing a
5074+
// length-1 TLV payload, which is obviously bogus.
5075+
new_payloads[1].data[0] = 1;
5076+
msg.onion_routing_packet = onion_utils::construct_onion_packet_bogus_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash);
5077+
}, ||{}, false, Some(PERM|22), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
50525078

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

0 commit comments

Comments
 (0)