Skip to content

Commit f066b4c

Browse files
committed
Correctly handle BADONION onion errors
Currently we entirely ignore the BADONION bit when deciding how to handle HTLC failures. This opens us up to an attack where a malicious node always fails HTLCs backwards via `update_fail_malformed_htlc` with an error code of `BADONION|NODE|PERM|X`. In this case, we may decide to interpret this as a permanent node failure for the node encrypting the onion, i.e. the counterparty of the node who sent the `update_fail_malformed_htlc` message and ultimately failed the HTLC. Thus, any node we route through could cause us to fully remove its counterparty from our network graph. Luckily we do not do any persistent tracking of removed nodes, and thus will re-add the removed node once it is re-announced or on restart, however we are likely to add such persistent tracking (at least in-memory) in the future.
1 parent 301efc8 commit f066b4c

File tree

1 file changed

+17
-8
lines changed

1 file changed

+17
-8
lines changed

lightning/src/ln/onion_utils.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
425425

426426
if fixed_time_eq(&Hmac::from_engine(hmac).into_inner(), &err_packet.hmac) {
427427
if let Some(error_code_slice) = err_packet.failuremsg.get(0..2) {
428+
const BADONION: u16 = 0x8000;
428429
const PERM: u16 = 0x4000;
429430
const NODE: u16 = 0x2000;
430431
const UPDATE: u16 = 0x1000;
@@ -445,21 +446,32 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
445446
let mut network_update = None;
446447
let mut short_channel_id = None;
447448

448-
if error_code & NODE == NODE {
449+
if error_code & BADONION == BADONION {
450+
// If the error code has the BADONION bit set, always blame the channel
451+
// from the node "originating" the error to its next hop. The
452+
// "originator" is ultimately actually claiming that its counterparty
453+
// is the one who is failing the HTLC.
454+
// If the "originator" here isn't lying we should really mark the
455+
// next-hop node as failed entirely, but we can't be confident in that,
456+
// as it would allow any node to get us to completely ban one of its
457+
// counterpartyies. Instead, we simply remove the channel in question.
458+
network_update = Some(NetworkUpdate::ChannelFailure {
459+
short_channel_id: failing_route_hop.short_channel_id,
460+
is_permanent: true,
461+
});
462+
} else if error_code & NODE == NODE {
449463
let is_permanent = error_code & PERM == PERM;
450464
network_update = Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent });
451465
short_channel_id = Some(route_hop.short_channel_id);
452-
}
453-
else if error_code & PERM == PERM {
466+
} else if error_code & PERM == PERM {
454467
if !payment_failed {
455468
network_update = Some(NetworkUpdate::ChannelFailure {
456469
short_channel_id: failing_route_hop.short_channel_id,
457470
is_permanent: true,
458471
});
459472
short_channel_id = Some(failing_route_hop.short_channel_id);
460473
}
461-
}
462-
else if error_code & UPDATE == UPDATE {
474+
} else if error_code & UPDATE == UPDATE {
463475
if let Some(update_len_slice) = err_packet.failuremsg.get(debug_field_size+2..debug_field_size+4) {
464476
let update_len = u16::from_be_bytes(update_len_slice.try_into().expect("len is 2")) as usize;
465477
if let Some(mut update_slice) = err_packet.failuremsg.get(debug_field_size + 4..debug_field_size + 4 + update_len) {
@@ -545,9 +557,6 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
545557
short_channel_id = Some(route_hop.short_channel_id);
546558
}
547559

548-
// TODO: Here (and a few other places) we assume that BADONION errors
549-
// are always "sourced" from the node previous to the one which failed
550-
// to decode the onion.
551560
res = Some((network_update, short_channel_id, !(error_code & PERM == PERM && is_from_final_node)));
552561

553562
let (description, title) = errors::get_onion_error_description(error_code);

0 commit comments

Comments
 (0)