Skip to content

Commit 4544aad

Browse files
committed
Add support for variable-length onion payload reads using TLV
1 parent 98a81a4 commit 4544aad

File tree

7 files changed

+183
-48
lines changed

7 files changed

+183
-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
@@ -926,14 +926,17 @@ impl<ChanSigner: ChannelKeys> ChannelManager<ChanSigner> {
926926
Err(err) => {
927927
let error_code = match err {
928928
msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte
929+
msgs::DecodeError::UnknownRequiredFeature|
930+
msgs::DecodeError::InvalidValue|
931+
msgs::DecodeError::ShortRead => 0x4000 | 22, // invalid_onion_payload
929932
_ => 0x2000 | 2, // Should never happen
930933
};
931934
return_err!("Unable to decode our hop data", error_code, &[0;0]);
932935
},
933936
Ok(msg) => {
934937
let mut hmac = [0; 32];
935938
if let Err(_) = chacha_stream.read_exact(&mut hmac[..]) {
936-
return_err!("Unable to decode our hop data", 0x4000 | 1, &[0;0]);
939+
return_err!("Unable to decode our hop data", 0x4000 | 22, &[0;0]);
937940
}
938941
(msg, hmac)
939942
},
@@ -987,6 +990,15 @@ impl<ChanSigner: ChannelKeys> ChannelManager<ChanSigner> {
987990
} else {
988991
let mut new_packet_data = [0; 20*65];
989992
let read_pos = chacha_stream.read(&mut new_packet_data).unwrap();
993+
#[cfg(debug_assertions)]
994+
{
995+
// Check two things:
996+
// a) that the behavior of our stream here will return Ok(0) even if the TLV
997+
// read above emptied out our buffer and the unwrap() wont needlessly panic
998+
// b) that we didn't somehow magically end up with extra data.
999+
let mut t = [0; 1];
1000+
debug_assert!(chacha_stream.read(&mut t).unwrap() == 0);
1001+
}
9901002
chacha_stream.chacha.process_inline(&mut new_packet_data[read_pos..]);
9911003

9921004
let mut new_pubkey = msg.onion_routing_packet.public_key.unwrap();
@@ -1009,10 +1021,18 @@ impl<ChanSigner: ChannelKeys> ChannelManager<ChanSigner> {
10091021
hmac: next_hop_hmac.clone(),
10101022
};
10111023

1024+
let short_channel_id = match next_hop_data.format {
1025+
msgs::OnionHopDataFormat::Legacy { short_channel_id } => short_channel_id,
1026+
msgs::OnionHopDataFormat::NonFinalNode { short_channel_id } => short_channel_id,
1027+
msgs::OnionHopDataFormat::FinalNode => {
1028+
return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0;0]);
1029+
},
1030+
};
1031+
10121032
PendingHTLCStatus::Forward(PendingForwardHTLCInfo {
10131033
onion_packet: Some(outgoing_packet),
10141034
payment_hash: msg.payment_hash.clone(),
1015-
short_channel_id: next_hop_data.short_channel_id,
1035+
short_channel_id: short_channel_id,
10161036
incoming_shared_secret: shared_secret,
10171037
amt_to_forward: next_hop_data.amt_to_forward,
10181038
outgoing_cltv_value: next_hop_data.outgoing_cltv_value,

lightning/src/ln/functional_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4874,7 +4874,7 @@ fn test_onion_failure() {
48744874
}
48754875
new_payloads[0].data[0] = 1;
48764876
msg.onion_routing_packet = onion_utils::construct_onion_packet_bogus_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash);
4877-
}, ||{}, 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
4877+
}, ||{}, 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
48784878

48794879
// final node failure
48804880
run_onion_failure_test("invalid_realm", 3, &nodes, &route, &payment_hash, |msg| {
@@ -4888,7 +4888,7 @@ fn test_onion_failure() {
48884888
}
48894889
new_payloads[1].data[0] = 1;
48904890
msg.onion_routing_packet = onion_utils::construct_onion_packet_bogus_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash);
4891-
}, ||{}, false, Some(PERM|1), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
4891+
}, ||{}, false, Some(PERM|22), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
48924892

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

lightning/src/ln/msgs.rs

Lines changed: 138 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use std::result::Result;
2828
use std::marker::PhantomData;
2929

3030
use util::events;
31-
use util::ser::{Readable, Writeable, Writer};
31+
use util::ser::{Readable, Writeable, Writer, FixedLengthReader};
3232

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

@@ -38,7 +38,7 @@ pub enum DecodeError {
3838
/// A version byte specified something we don't know how to handle.
3939
/// Includes unknown realm byte in an OnionHopData packet
4040
UnknownVersion,
41-
/// Unknown feature mandating we fail to parse message
41+
/// Unknown feature mandating we fail to parse message (eg TLV with an even, unknown type)
4242
UnknownRequiredFeature,
4343
/// Value was invalid, eg a byte which was supposed to be a bool was something other than a 0
4444
/// or 1, a public key/private key/signature was invalid, text wasn't UTF-8, etc
@@ -183,13 +183,21 @@ impl<T: FeatureContext> Features<T> {
183183

184184
pub(crate) fn requires_unknown_bits(&self) -> bool {
185185
self.flags.iter().enumerate().any(|(idx, &byte)| {
186-
( idx != 0 && (byte & 0x55) != 0 ) || ( idx == 0 && (byte & 0x14) != 0 )
186+
(match idx {
187+
0 => (byte & 0b00010100),
188+
1 => (byte & 0b01010100),
189+
_ => (byte & 0b01010101),
190+
}) != 0
187191
})
188192
}
189193

190194
pub(crate) fn supports_unknown_bits(&self) -> bool {
191195
self.flags.iter().enumerate().any(|(idx, &byte)| {
192-
( idx != 0 && byte != 0 ) || ( idx == 0 && (byte & 0xc4) != 0 )
196+
(match idx {
197+
0 => (byte & 0b11000100),
198+
1 => (byte & 0b11111100),
199+
_ => byte,
200+
}) != 0
193201
})
194202
}
195203

@@ -816,15 +824,20 @@ mod fuzzy_internal_msgs {
816824
// them from untrusted input):
817825

818826
pub(crate) enum OnionHopDataFormat {
819-
Legacy, // aka Realm-0
827+
Legacy { // aka Realm-0
828+
short_channel_id: u64,
829+
},
830+
NonFinalNode {
831+
short_channel_id: u64,
832+
},
833+
FinalNode,
820834
}
821835

822836
pub struct OnionHopData {
823837
pub(crate) format: OnionHopDataFormat,
824-
pub(crate) short_channel_id: u64,
825838
pub(crate) amt_to_forward: u64,
826839
pub(crate) outgoing_cltv_value: u32,
827-
// 12 bytes of 0-padding
840+
// 12 bytes of 0-padding for Legacy format
828841
}
829842

830843
pub struct DecodedOnionErrorPacket {
@@ -1201,33 +1214,78 @@ impl Writeable for OnionHopData {
12011214
fn write<W: Writer>(&self, w: &mut W) -> Result<(), ::std::io::Error> {
12021215
w.size_hint(33);
12031216
match self.format {
1204-
OnionHopDataFormat::Legacy => 0u8.write(w)?,
1217+
OnionHopDataFormat::Legacy { short_channel_id } => {
1218+
0u8.write(w)?;
1219+
short_channel_id.write(w)?;
1220+
self.amt_to_forward.write(w)?;
1221+
self.outgoing_cltv_value.write(w)?;
1222+
},
1223+
OnionHopDataFormat::NonFinalNode { short_channel_id } => {
1224+
encode_varint_length_prefixed_tlv!(w, {
1225+
(2, self.amt_to_forward),
1226+
(4, self.outgoing_cltv_value),
1227+
(6, short_channel_id)
1228+
});
1229+
},
1230+
OnionHopDataFormat::FinalNode => {
1231+
encode_varint_length_prefixed_tlv!(w, {
1232+
(2, self.amt_to_forward),
1233+
(4, self.outgoing_cltv_value)
1234+
});
1235+
},
1236+
}
1237+
match self.format {
1238+
OnionHopDataFormat::Legacy { .. } => {
1239+
w.write_all(&[0;12])?;
1240+
},
1241+
_ => {},
12051242
}
1206-
self.short_channel_id.write(w)?;
1207-
self.amt_to_forward.write(w)?;
1208-
self.outgoing_cltv_value.write(w)?;
1209-
w.write_all(&[0;12])?;
12101243
Ok(())
12111244
}
12121245
}
12131246

12141247
impl<R: Read> Readable<R> for OnionHopData {
1215-
fn read(r: &mut R) -> Result<Self, DecodeError> {
1216-
Ok(OnionHopData {
1217-
format: {
1218-
let r: u8 = Readable::read(r)?;
1219-
if r != 0 {
1220-
return Err(DecodeError::UnknownVersion);
1248+
fn read(mut r: &mut R) -> Result<Self, DecodeError> {
1249+
use bitcoin::consensus::encode::{Decodable, Error, VarInt};
1250+
let v: VarInt = Decodable::consensus_decode(&mut r)
1251+
.map_err(|e| match e {
1252+
Error::Io(ioe) => DecodeError::from(ioe),
1253+
_ => DecodeError::InvalidValue
1254+
})?;
1255+
let (format, amt, value) = if v.0 != 0 {
1256+
let mut rd = FixedLengthReader { read: r, read_len: 0, max_len: v.0 };
1257+
let mut amt: u64 = 0;
1258+
let mut value: u32 = 0;
1259+
let mut short_id: Option<u64> = None;
1260+
decode_tlv!(&mut rd, {
1261+
(2, amt),
1262+
(4, value)
1263+
}, {
1264+
(6, short_id)
1265+
});
1266+
rd.eat_remaining().map_err(|_| DecodeError::ShortRead)?;
1267+
let format = if let Some(short_channel_id) = short_id {
1268+
OnionHopDataFormat::NonFinalNode {
1269+
short_channel_id,
12211270
}
1222-
OnionHopDataFormat::Legacy
1223-
},
1224-
short_channel_id: Readable::read(r)?,
1225-
amt_to_forward: Readable::read(r)?,
1226-
outgoing_cltv_value: {
1227-
let v: u32 = Readable::read(r)?;
1228-
r.read_exact(&mut [0; 12])?;
1229-
v
1230-
},
1271+
} else {
1272+
OnionHopDataFormat::FinalNode
1273+
};
1274+
(format, amt, value)
1275+
} else {
1276+
let format = OnionHopDataFormat::Legacy {
1277+
short_channel_id: Readable::read(r)?,
1278+
};
1279+
let amt: u64 = Readable::read(r)?;
1280+
let value: u32 = Readable::read(r)?;
1281+
r.read_exact(&mut [0; 12])?;
1282+
(format, amt, value)
1283+
};
1284+
1285+
Ok(OnionHopData {
1286+
format,
1287+
amt_to_forward: amt,
1288+
outgoing_cltv_value: value,
12311289
})
12321290
}
12331291
}
@@ -1513,9 +1571,9 @@ impl_writeable_len_match!(NodeAnnouncement, {
15131571
mod tests {
15141572
use hex;
15151573
use ln::msgs;
1516-
use ln::msgs::{Features, FeatureContextChannel, FeatureContextNode, OptionalField, OnionErrorPacket};
1574+
use ln::msgs::{Features, FeatureContextChannel, FeatureContextNode, OptionalField, OnionErrorPacket, OnionHopDataFormat};
15171575
use ln::channelmanager::{PaymentPreimage, PaymentHash};
1518-
use util::ser::Writeable;
1576+
use util::ser::{Writeable, Readable};
15191577

15201578
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
15211579
use bitcoin_hashes::hex::FromHex;
@@ -1528,6 +1586,7 @@ mod tests {
15281586
use secp256k1::{Secp256k1, Message};
15291587

15301588
use std::marker::PhantomData;
1589+
use std::io::Cursor;
15311590

15321591
#[test]
15331592
fn encoding_channel_reestablish_no_secret() {
@@ -2178,4 +2237,54 @@ mod tests {
21782237
let target_value = hex::decode("004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
21792238
assert_eq!(encoded_value, target_value);
21802239
}
2240+
2241+
#[test]
2242+
fn encoding_legacy_onion_hop_data() {
2243+
let msg = msgs::OnionHopData {
2244+
format: OnionHopDataFormat::Legacy {
2245+
short_channel_id: 0xdeadbeef1bad1dea,
2246+
},
2247+
amt_to_forward: 0x0badf00d01020304,
2248+
outgoing_cltv_value: 0xffffffff,
2249+
};
2250+
let encoded_value = msg.encode();
2251+
let target_value = hex::decode("00deadbeef1bad1dea0badf00d01020304ffffffff000000000000000000000000").unwrap();
2252+
assert_eq!(encoded_value, target_value);
2253+
}
2254+
2255+
#[test]
2256+
fn encoding_nonfinal_onion_hop_data() {
2257+
let mut msg = msgs::OnionHopData {
2258+
format: OnionHopDataFormat::NonFinalNode {
2259+
short_channel_id: 0xdeadbeef1bad1dea,
2260+
},
2261+
amt_to_forward: 0x0badf00d01020304,
2262+
outgoing_cltv_value: 0xffffffff,
2263+
};
2264+
let encoded_value = msg.encode();
2265+
let target_value = hex::decode("1a02080badf00d010203040404ffffffff0608deadbeef1bad1dea").unwrap();
2266+
assert_eq!(encoded_value, target_value);
2267+
msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
2268+
if let OnionHopDataFormat::NonFinalNode { short_channel_id } = msg.format {
2269+
assert_eq!(short_channel_id, 0xdeadbeef1bad1dea);
2270+
} else { panic!(); }
2271+
assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
2272+
assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
2273+
}
2274+
2275+
#[test]
2276+
fn encoding_final_onion_hop_data() {
2277+
let mut msg = msgs::OnionHopData {
2278+
format: OnionHopDataFormat::FinalNode,
2279+
amt_to_forward: 0x0badf00d01020304,
2280+
outgoing_cltv_value: 0xffffffff,
2281+
};
2282+
let encoded_value = msg.encode();
2283+
let target_value = hex::decode("1002080badf00d010203040404ffffffff").unwrap();
2284+
assert_eq!(encoded_value, target_value);
2285+
msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
2286+
if let OnionHopDataFormat::FinalNode = msg.format { } else { panic!(); }
2287+
assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
2288+
assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
2289+
}
21812290
}

0 commit comments

Comments
 (0)