Skip to content

Commit fc7df54

Browse files
authored
Merge pull request #748 from TheBlueMatt/2020-11-router-fuzzer
Make router_target a bit easier for fuzzers to explore and fix two found bugs
2 parents 3c4a0c1 + b56b4ad commit fc7df54

File tree

3 files changed

+155
-72
lines changed

3 files changed

+155
-72
lines changed

fuzz/src/router.rs

+46-41
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,22 @@ use bitcoin::blockdata::script::Builder;
1111
use bitcoin::blockdata::transaction::TxOut;
1212
use bitcoin::hash_types::BlockHash;
1313

14+
use bitcoin::secp256k1;
15+
1416
use lightning::chain;
1517
use lightning::ln::channelmanager::ChannelDetails;
1618
use lightning::ln::features::InitFeatures;
1719
use lightning::ln::msgs;
18-
use lightning::ln::msgs::RoutingMessageHandler;
1920
use lightning::routing::router::{get_route, RouteHint};
2021
use lightning::util::logger::Logger;
2122
use lightning::util::ser::Readable;
22-
use lightning::routing::network_graph::{NetGraphMsgHandler, RoutingFees};
23+
use lightning::routing::network_graph::{NetworkGraph, RoutingFees};
2324

2425
use bitcoin::secp256k1::key::PublicKey;
2526

2627
use utils::test_logger;
2728

29+
use std::collections::HashSet;
2830
use std::sync::Arc;
2931
use std::sync::atomic::{AtomicUsize, Ordering};
3032

@@ -150,16 +152,12 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
150152
}
151153

152154
let logger: Arc<dyn Logger> = Arc::new(test_logger::TestLogger::new("".to_owned(), out));
153-
let chain_source = if get_slice!(1)[0] % 2 == 0 {
154-
None
155-
} else {
156-
Some(Arc::new(FuzzChainSource {
157-
input: Arc::clone(&input),
158-
}))
159-
};
160155

161156
let our_pubkey = get_pubkey!();
162-
let net_graph_msg_handler = NetGraphMsgHandler::new(chain_source, Arc::clone(&logger));
157+
let mut net_graph = NetworkGraph::new();
158+
159+
let mut node_pks = HashSet::new();
160+
let mut scid = 42;
163161

164162
loop {
165163
match get_slice!(1)[0] {
@@ -169,39 +167,44 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
169167
if addr_len > (37+1)*4 {
170168
return;
171169
}
172-
let _ = net_graph_msg_handler.handle_node_announcement(&decode_msg_with_len16!(msgs::NodeAnnouncement, 64, 288));
170+
let msg = decode_msg_with_len16!(msgs::NodeAnnouncement, 64, 288);
171+
node_pks.insert(msg.contents.node_id);
172+
let _ = net_graph.update_node_from_announcement::<secp256k1::VerifyOnly>(&msg, None);
173173
},
174174
1 => {
175-
let _ = net_graph_msg_handler.handle_channel_announcement(&decode_msg_with_len16!(msgs::ChannelAnnouncement, 64*4, 32+8+33*4));
175+
let msg = decode_msg_with_len16!(msgs::ChannelAnnouncement, 64*4, 32+8+33*4);
176+
node_pks.insert(msg.contents.node_id_1);
177+
node_pks.insert(msg.contents.node_id_2);
178+
let _ = net_graph.update_channel_from_announcement::<secp256k1::VerifyOnly>(&msg, None, None);
176179
},
177180
2 => {
178-
let _ = net_graph_msg_handler.handle_channel_update(&decode_msg!(msgs::ChannelUpdate, 136));
181+
let msg = decode_msg_with_len16!(msgs::ChannelAnnouncement, 64*4, 32+8+33*4);
182+
node_pks.insert(msg.contents.node_id_1);
183+
node_pks.insert(msg.contents.node_id_2);
184+
let val = slice_to_be64(get_slice!(8));
185+
let _ = net_graph.update_channel_from_announcement::<secp256k1::VerifyOnly>(&msg, Some(val), None);
179186
},
180187
3 => {
181-
match get_slice!(1)[0] {
182-
0 => {
183-
net_graph_msg_handler.handle_htlc_fail_channel_update(&msgs::HTLCFailChannelUpdate::ChannelUpdateMessage {msg: decode_msg!(msgs::ChannelUpdate, 136)});
184-
},
185-
1 => {
186-
let short_channel_id = slice_to_be64(get_slice!(8));
187-
net_graph_msg_handler.handle_htlc_fail_channel_update(&msgs::HTLCFailChannelUpdate::ChannelClosed {short_channel_id, is_permanent: false});
188-
},
189-
_ => return,
190-
}
188+
let _ = net_graph.update_channel(&decode_msg!(msgs::ChannelUpdate, 136), None);
191189
},
192190
4 => {
193-
let target = get_pubkey!();
191+
let short_channel_id = slice_to_be64(get_slice!(8));
192+
net_graph.close_channel_from_update(short_channel_id, false);
193+
},
194+
_ if node_pks.is_empty() => {},
195+
_ => {
194196
let mut first_hops_vec = Vec::new();
195197
let first_hops = match get_slice!(1)[0] {
196198
0 => None,
197-
1 => {
198-
let count = slice_to_be16(get_slice!(2));
199+
count => {
199200
for _ in 0..count {
201+
scid += 1;
202+
let rnid = node_pks.iter().skip(slice_to_be16(get_slice!(2))as usize % node_pks.len()).next().unwrap();
200203
first_hops_vec.push(ChannelDetails {
201204
channel_id: [0; 32],
202-
short_channel_id: Some(slice_to_be64(get_slice!(8))),
203-
remote_network_id: get_pubkey!(),
204-
counterparty_features: InitFeatures::empty(),
205+
short_channel_id: Some(scid),
206+
remote_network_id: *rnid,
207+
counterparty_features: InitFeatures::known(),
205208
channel_value_satoshis: slice_to_be64(get_slice!(8)),
206209
user_id: 0,
207210
inbound_capacity_msat: 0,
@@ -211,15 +214,16 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
211214
}
212215
Some(&first_hops_vec[..])
213216
},
214-
_ => return,
215217
};
216218
let mut last_hops_vec = Vec::new();
217-
let last_hops = {
218-
let count = slice_to_be16(get_slice!(2));
219+
{
220+
let count = get_slice!(1)[0];
219221
for _ in 0..count {
222+
scid += 1;
223+
let rnid = node_pks.iter().skip(slice_to_be16(get_slice!(2))as usize % node_pks.len()).next().unwrap();
220224
last_hops_vec.push(RouteHint {
221-
src_node_id: get_pubkey!(),
222-
short_channel_id: slice_to_be64(get_slice!(8)),
225+
src_node_id: *rnid,
226+
short_channel_id: scid,
223227
fees: RoutingFees {
224228
base_msat: slice_to_be32(get_slice!(4)),
225229
proportional_millionths: slice_to_be32(get_slice!(4)),
@@ -228,14 +232,15 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
228232
htlc_minimum_msat: slice_to_be64(get_slice!(8)),
229233
});
230234
}
231-
&last_hops_vec[..]
232-
};
233-
let _ = get_route(&our_pubkey, &net_graph_msg_handler.network_graph.read().unwrap(), &target,
234-
first_hops.map(|c| c.iter().collect::<Vec<_>>()).as_ref().map(|a| a.as_slice()),
235-
&last_hops.iter().collect::<Vec<_>>(),
236-
slice_to_be64(get_slice!(8)), slice_to_be32(get_slice!(4)), Arc::clone(&logger));
235+
}
236+
let last_hops = &last_hops_vec[..];
237+
for target in node_pks.iter() {
238+
let _ = get_route(&our_pubkey, &net_graph, target,
239+
first_hops.map(|c| c.iter().collect::<Vec<_>>()).as_ref().map(|a| a.as_slice()),
240+
&last_hops.iter().collect::<Vec<_>>(),
241+
slice_to_be64(get_slice!(8)), slice_to_be32(get_slice!(4)), Arc::clone(&logger));
242+
}
237243
},
238-
_ => return,
239244
}
240245
}
241246
}

lightning/src/routing/network_graph.rs

+36-12
Original file line numberDiff line numberDiff line change
@@ -562,9 +562,14 @@ impl NetworkGraph {
562562
}
563563
}
564564

565-
/// For an already known node (from channel announcements), update its stored properties from a given node announcement
565+
/// For an already known node (from channel announcements), update its stored properties from a given node announcement.
566+
///
567+
/// You probably don't want to call this directly, instead relying on a NetGraphMsgHandler's
568+
/// RoutingMessageHandler implementation to call it indirectly. This may be useful to accept
569+
/// routing messages without checking their signatures.
570+
///
566571
/// Announcement signatures are checked here only if Secp256k1 object is provided.
567-
fn update_node_from_announcement(&mut self, msg: &msgs::NodeAnnouncement, secp_ctx: Option<&Secp256k1<secp256k1::VerifyOnly>>) -> Result<bool, LightningError> {
572+
pub fn update_node_from_announcement<T: secp256k1::Verification>(&mut self, msg: &msgs::NodeAnnouncement, secp_ctx: Option<&Secp256k1<T>>) -> Result<bool, LightningError> {
568573
if let Some(sig_verifier) = secp_ctx {
569574
let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]);
570575
secp_verify_sig!(sig_verifier, &msg_hash, &msg.signature, &msg.contents.node_id);
@@ -594,13 +599,27 @@ impl NetworkGraph {
594599
}
595600
}
596601

597-
/// For a new or already known (from previous announcement) channel, store or update channel info.
598-
/// Also store nodes (if not stored yet) the channel is between, and make node aware of this channel.
599-
/// Checking utxo on-chain is useful if we receive an update for already known channel id,
600-
/// which is probably result of a reorg. In that case, we update channel info only if the
601-
/// utxo was checked, otherwise stick to the existing update, to prevent DoS risks.
602+
/// Store or update channel info from a channel announcement.
603+
///
604+
/// You probably don't want to call this directly, instead relying on a NetGraphMsgHandler's
605+
/// RoutingMessageHandler implementation to call it indirectly. This may be useful to accept
606+
/// routing messages without checking their signatures.
607+
///
608+
/// If the channel has been confirmed to exist on chain (with correctly-formatted scripts on
609+
/// chain), set utxo_value to the value of the output on chain, otherwise leave it as None.
610+
/// The UTXO value is then used in routing calculation if we have no better information on the
611+
/// maximum HTLC value that can be sent over the channel.
612+
///
613+
/// Further, setting utxo_value to Some indicates that the announcement message is genuine,
614+
/// allowing us to update existing channel data in the case of reorgs or to replace bogus
615+
/// channel data generated by a DoS attacker.
616+
///
602617
/// Announcement signatures are checked here only if Secp256k1 object is provided.
603-
fn update_channel_from_announcement(&mut self, msg: &msgs::ChannelAnnouncement, utxo_value: Option<u64>, secp_ctx: Option<&Secp256k1<secp256k1::VerifyOnly>>) -> Result<bool, LightningError> {
618+
pub fn update_channel_from_announcement<T: secp256k1::Verification>(&mut self, msg: &msgs::ChannelAnnouncement, utxo_value: Option<u64>, secp_ctx: Option<&Secp256k1<T>>) -> Result<bool, LightningError> {
619+
if msg.contents.node_id_1 == msg.contents.node_id_2 || msg.contents.bitcoin_key_1 == msg.contents.bitcoin_key_2 {
620+
return Err(LightningError{err: "Channel announcement node had a channel with itself".to_owned(), action: ErrorAction::IgnoreError});
621+
}
622+
604623
if let Some(sig_verifier) = secp_ctx {
605624
let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]);
606625
secp_verify_sig!(sig_verifier, &msg_hash, &msg.node_signature_1, &msg.contents.node_id_1);
@@ -699,8 +718,13 @@ impl NetworkGraph {
699718
}
700719

701720
/// For an already known (from announcement) channel, update info about one of the directions of a channel.
721+
///
722+
/// You probably don't want to call this directly, instead relying on a NetGraphMsgHandler's
723+
/// RoutingMessageHandler implementation to call it indirectly. This may be useful to accept
724+
/// routing messages without checking their signatures.
725+
///
702726
/// Announcement signatures are checked here only if Secp256k1 object is provided.
703-
fn update_channel(&mut self, msg: &msgs::ChannelUpdate, secp_ctx: Option<&Secp256k1<secp256k1::VerifyOnly>>) -> Result<bool, LightningError> {
727+
pub fn update_channel(&mut self, msg: &msgs::ChannelUpdate, secp_ctx: Option<&Secp256k1<secp256k1::VerifyOnly>>) -> Result<bool, LightningError> {
704728
let dest_node_id;
705729
let chan_enabled = msg.contents.flags & (1 << 1) != (1 << 1);
706730
let chan_was_enabled;
@@ -716,8 +740,8 @@ impl NetworkGraph {
716740
if let Some(capacity_sats) = channel.capacity_sats {
717741
// It's possible channel capacity is available now, although it wasn't available at announcement (so the field is None).
718742
// Don't query UTXO set here to reduce DoS risks.
719-
if htlc_maximum_msat > capacity_sats * 1000 {
720-
return Err(LightningError{err: "htlc_maximum_msat is larger than channel capacity".to_owned(), action: ErrorAction::IgnoreError});
743+
if capacity_sats > MAX_VALUE_MSAT / 1000 || htlc_maximum_msat > capacity_sats * 1000 {
744+
return Err(LightningError{err: "htlc_maximum_msat is larger than channel capacity or capacity is bogus".to_owned(), action: ErrorAction::IgnoreError});
721745
}
722746
}
723747
}
@@ -1302,7 +1326,7 @@ mod tests {
13021326

13031327
match net_graph_msg_handler.handle_channel_update(&valid_channel_update) {
13041328
Ok(_) => panic!(),
1305-
Err(e) => assert_eq!(e.err, "htlc_maximum_msat is larger than channel capacity")
1329+
Err(e) => assert_eq!(e.err, "htlc_maximum_msat is larger than channel capacity or capacity is bogus")
13061330
};
13071331
unsigned_channel_update.htlc_maximum_msat = OptionalField::Absent;
13081332

lightning/src/routing/router.rs

+73-19
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,12 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
237237
let mut total_fee = $starting_fee_msat as u64;
238238
let hm_entry = dist.entry(&$src_node_id);
239239
let old_entry = hm_entry.or_insert_with(|| {
240-
let node = network.get_nodes().get(&$src_node_id).unwrap();
241240
let mut fee_base_msat = u32::max_value();
242241
let mut fee_proportional_millionths = u32::max_value();
243-
if let Some(fees) = node.lowest_inbound_channel_fees {
242+
if let Some(fees) = network.get_nodes().get(&$src_node_id).and_then(|node| node.lowest_inbound_channel_fees) {
244243
fee_base_msat = fees.base_msat;
245244
fee_proportional_millionths = fees.proportional_millionths;
246-
};
245+
}
247246
(u64::max_value(),
248247
fee_base_msat,
249248
fee_proportional_millionths,
@@ -342,21 +341,26 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
342341
}
343342

344343
for hop in last_hops.iter() {
345-
if first_hops.is_none() || hop.src_node_id != *our_node_id { // first_hop overrules last_hops
346-
if network.get_nodes().get(&hop.src_node_id).is_some() {
347-
if first_hops.is_some() {
348-
if let Some(&(ref first_hop, ref features)) = first_hop_targets.get(&hop.src_node_id) {
349-
// Currently there are no channel-context features defined, so we are a
350-
// bit lazy here. In the future, we should pull them out via our
351-
// ChannelManager, but there's no reason to waste the space until we
352-
// need them.
353-
add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, features.to_context(), 0);
354-
}
355-
}
356-
// BOLT 11 doesn't allow inclusion of features for the last hop hints, which
357-
// really sucks, cause we're gonna need that eventually.
358-
add_entry!(hop.short_channel_id, hop.src_node_id, target, hop, ChannelFeatures::empty(), 0);
359-
}
344+
let have_hop_src_in_graph =
345+
if let Some(&(ref first_hop, ref features)) = first_hop_targets.get(&hop.src_node_id) {
346+
// If this hop connects to a node with which we have a direct channel, ignore the
347+
// network graph and add both the hop and our direct channel to the candidate set:
348+
//
349+
// Currently there are no channel-context features defined, so we are a
350+
// bit lazy here. In the future, we should pull them out via our
351+
// ChannelManager, but there's no reason to waste the space until we
352+
// need them.
353+
add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, features.to_context(), 0);
354+
true
355+
} else {
356+
// In any other case, only add the hop if the source is in the regular network
357+
// graph:
358+
network.get_nodes().get(&hop.src_node_id).is_some()
359+
};
360+
if have_hop_src_in_graph {
361+
// BOLT 11 doesn't allow inclusion of features for the last hop hints, which
362+
// really sucks, cause we're gonna need that eventually.
363+
add_entry!(hop.short_channel_id, hop.src_node_id, target, hop, ChannelFeatures::empty(), 0);
360364
}
361365
}
362366

@@ -412,7 +416,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
412416
#[cfg(test)]
413417
mod tests {
414418
use routing::router::{get_route, RouteHint, RoutingFees};
415-
use routing::network_graph::NetGraphMsgHandler;
419+
use routing::network_graph::{NetworkGraph, NetGraphMsgHandler};
416420
use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
417421
use ln::msgs::{ErrorAction, LightningError, OptionalField, UnsignedChannelAnnouncement, ChannelAnnouncement, RoutingMessageHandler,
418422
NodeAnnouncement, UnsignedNodeAnnouncement, ChannelUpdate, UnsignedChannelUpdate};
@@ -1222,4 +1226,54 @@ mod tests {
12221226
assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::<u8>::new()); // We dont pass flags in from invoices yet
12231227
assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
12241228
}
1229+
1230+
#[test]
1231+
fn unannounced_path_test() {
1232+
// We should be able to send a payment to a destination without any help of a routing graph
1233+
// if we have a channel with a common counterparty that appears in the first and last hop
1234+
// hints.
1235+
let source_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 41).repeat(32)).unwrap()[..]).unwrap());
1236+
let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap());
1237+
let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
1238+
1239+
// If we specify a channel to a middle hop, that overrides our local channel view and that gets used
1240+
let last_hops = vec![RouteHint {
1241+
src_node_id: middle_node_id,
1242+
short_channel_id: 8,
1243+
fees: RoutingFees {
1244+
base_msat: 1000,
1245+
proportional_millionths: 0,
1246+
},
1247+
cltv_expiry_delta: (8 << 8) | 1,
1248+
htlc_minimum_msat: 0,
1249+
}];
1250+
let our_chans = vec![channelmanager::ChannelDetails {
1251+
channel_id: [0; 32],
1252+
short_channel_id: Some(42),
1253+
remote_network_id: middle_node_id,
1254+
counterparty_features: InitFeatures::from_le_bytes(vec![0b11]),
1255+
channel_value_satoshis: 100000,
1256+
user_id: 0,
1257+
outbound_capacity_msat: 100000,
1258+
inbound_capacity_msat: 100000,
1259+
is_live: true,
1260+
}];
1261+
let route = get_route(&source_node_id, &NetworkGraph::new(), &target_node_id, Some(&our_chans.iter().collect::<Vec<_>>()), &last_hops.iter().collect::<Vec<_>>(), 100, 42, Arc::new(test_utils::TestLogger::new())).unwrap();
1262+
1263+
assert_eq!(route.paths[0].len(), 2);
1264+
1265+
assert_eq!(route.paths[0][0].pubkey, middle_node_id);
1266+
assert_eq!(route.paths[0][0].short_channel_id, 42);
1267+
assert_eq!(route.paths[0][0].fee_msat, 1000);
1268+
assert_eq!(route.paths[0][0].cltv_expiry_delta, (8 << 8) | 1);
1269+
assert_eq!(route.paths[0][0].node_features.le_flags(), &[0b11]);
1270+
assert_eq!(route.paths[0][0].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
1271+
1272+
assert_eq!(route.paths[0][1].pubkey, target_node_id);
1273+
assert_eq!(route.paths[0][1].short_channel_id, 8);
1274+
assert_eq!(route.paths[0][1].fee_msat, 100);
1275+
assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
1276+
assert_eq!(route.paths[0][1].node_features.le_flags(), &[0; 0]); // We dont pass flags in from invoices yet
1277+
assert_eq!(route.paths[0][1].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
1278+
}
12251279
}

0 commit comments

Comments
 (0)