Skip to content

Commit 93e718f

Browse files
Correctly fail back on intro node payload decode failure
1 parent d187721 commit 93e718f

File tree

5 files changed

+116
-24
lines changed

5 files changed

+116
-24
lines changed

lightning/src/ln/blinded_payment_tests.rs

+74-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// licenses.
99

1010
use bitcoin::secp256k1::Secp256k1;
11-
use crate::blinded_path::BlindedPath;
11+
use crate::blinded_path::{BlindedHop, BlindedPath};
1212
use crate::blinded_path::payment::{ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs};
1313
use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider};
1414
use crate::ln::channelmanager;
@@ -482,3 +482,76 @@ fn fail_blinded_payment() {
482482
_ => panic!("Unexpected event"),
483483
}
484484
}
485+
486+
#[test]
487+
fn invalid_intro_node_payload() {
488+
// Ensure we fail back properly if the intro node's onion payload is bogus.
489+
let chanmon_cfgs = create_chanmon_cfgs(3);
490+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
491+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
492+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
493+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
494+
let chan_upd = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
495+
496+
let amt_msat = 5000;
497+
let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
498+
let intermediate_nodes = vec![(nodes[1].node.get_our_node_id(), ForwardTlvs {
499+
short_channel_id: chan_upd.short_channel_id,
500+
payment_relay: PaymentRelay {
501+
cltv_expiry_delta: chan_upd.cltv_expiry_delta,
502+
fee_proportional_millionths: chan_upd.fee_proportional_millionths,
503+
fee_base_msat: chan_upd.fee_base_msat,
504+
},
505+
payment_constraints: PaymentConstraints {
506+
max_cltv_expiry: u32::max_value(),
507+
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
508+
},
509+
features: BlindedHopFeatures::empty(),
510+
})];
511+
let payee_tlvs = ReceiveTlvs {
512+
payment_secret,
513+
payment_constraints: PaymentConstraints {
514+
max_cltv_expiry: u32::max_value(),
515+
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
516+
},
517+
};
518+
let mut secp_ctx = Secp256k1::new();
519+
let mut blinded_path = BlindedPath::new_for_payment(
520+
&intermediate_nodes[..], nodes[2].node.get_our_node_id(), payee_tlvs,
521+
chan_upd.htlc_maximum_msat, &chanmon_cfgs[2].keys_manager, &secp_ctx
522+
).unwrap();
523+
blinded_path.1.blinded_hops[0] = BlindedHop {
524+
blinded_node_id: blinded_path.1.blinded_hops[0].blinded_node_id,
525+
encrypted_payload: vec![0; 32], // Bogus intro node payload
526+
};
527+
528+
let route_params = RouteParameters {
529+
payment_params: PaymentParameters::blinded(vec![blinded_path]),
530+
final_value_msat: amt_msat
531+
};
532+
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(),
533+
PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
534+
check_added_monitors(&nodes[0], 1);
535+
let (update_add, commitment_signed) = {
536+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
537+
assert_eq!(events.len(), 1);
538+
let payment_ev = SendEvent::from_event(events.pop().unwrap());
539+
(payment_ev.msgs[0].clone(), payment_ev.commitment_msg.clone())
540+
};
541+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
542+
do_commitment_signed_dance(&nodes[1], &nodes[0], &commitment_signed, false, true);
543+
let (update_fail, commitment_signed) = {
544+
let events = nodes[1].node.get_and_clear_pending_msg_events();
545+
assert_eq!(events.len(), 1);
546+
match &events[0] {
547+
MessageSendEvent::UpdateHTLCs { updates, .. } => {
548+
(updates.update_fail_htlcs[0].clone(), updates.commitment_signed.clone())
549+
},
550+
_ => panic!()
551+
}
552+
};
553+
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail);
554+
do_commitment_signed_dance(&nodes[0], &nodes[1], &commitment_signed, false, true);
555+
expect_payment_failed_conditions(&nodes[0], payment_hash, false,
556+
PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
557+
}

lightning/src/ln/channelmanager.rs

+9-8
Original file line numberDiff line numberDiff line change
@@ -2986,7 +2986,7 @@ where
29862986
return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
29872987
channel_id: msg.channel_id,
29882988
htlc_id: msg.htlc_id,
2989-
reason: HTLCFailReason::reason($err_code, $data.to_vec())
2989+
reason: HTLCFailReason::reason($err_code, $data)
29902990
.get_encrypted_failure_packet(&shared_secret, &None),
29912991
}));
29922992
}
@@ -2997,7 +2997,7 @@ where
29972997
if msg.blinding_point.is_some() {
29982998
return_malformed_err!($msg, INVALID_ONION_BLINDING);
29992999
} else {
3000-
return_err!($msg, INVALID_ONION_BLINDING, [0; 32]);
3000+
return_err!($msg, INVALID_ONION_BLINDING, vec![0; 32]);
30013001
}
30023002
}
30033003
}
@@ -3014,11 +3014,11 @@ where
30143014
return_malformed_err!(err_msg, err_code);
30153015
}
30163016
},
3017-
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => {
3017+
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, err_data }) => {
30183018
if msg.blinding_point.is_some() {
30193019
return_blinded_htlc_err!(err_msg);
30203020
} else {
3021-
return_err!(err_msg, err_code, &[0; 0]);
3021+
return_err!(err_msg, err_code, err_data);
30223022
}
30233023
},
30243024
};
@@ -3071,7 +3071,8 @@ where
30713071
// inbound channel's state.
30723072
onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)),
30733073
onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::Receive { .. }, .. } => {
3074-
return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0; 0]);
3074+
return_err!("Final Node OnionHopData provided for us as an intermediary node",
3075+
0x4000 | 22, Vec::new());
30753076
},
30763077
onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::BlindedReceive { .. }, .. } => {
30773078
return_blinded_htlc_err!("Blinded final node onion provided for us as an intermediary node");
@@ -3210,7 +3211,7 @@ where
32103211
// instead.
32113212
code = 0x2000 | 2;
32123213
}
3213-
return_err!(err, code, &res.0[..]);
3214+
return_err!(err, code, res.0);
32143215
}
32153216
Ok((next_hop, shared_secret, next_packet_pk_opt))
32163217
}
@@ -4075,8 +4076,8 @@ where
40754076
// of the onion.
40764077
failed_payment!(err_msg, err_code, sha256_of_onion.to_vec(), None);
40774078
},
4078-
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => {
4079-
failed_payment!(err_msg, err_code, Vec::new(), Some(phantom_shared_secret));
4079+
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, err_data }) => {
4080+
failed_payment!(err_msg, err_code, err_data, Some(phantom_shared_secret));
40804081
},
40814082
};
40824083
match next_hop {

lightning/src/ln/msgs.rs

+21-9
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ pub enum DecodeError {
8383
Io(io::ErrorKind),
8484
/// The message included zlib-compressed values, which we don't support.
8585
UnsupportedCompression,
86+
/// We are the intro node to a blinded path and failed to decode our onion payload. Separated out
87+
/// from [`Self::InvalidValue`] to ensure we abide by the spec when failing backwards.
88+
InvalidIntroNodePayload,
8689
}
8790

8891
/// An [`init`] message to be sent to or received from a peer.
@@ -1570,6 +1573,7 @@ impl fmt::Display for DecodeError {
15701573
DecodeError::BadLengthDescriptor => f.write_str("A length descriptor in the packet didn't describe the later data correctly"),
15711574
DecodeError::Io(ref e) => fmt::Debug::fmt(e, f),
15721575
DecodeError::UnsupportedCompression => f.write_str("We don't support receiving messages with zlib-compressed fields"),
1576+
DecodeError::InvalidIntroNodePayload => f.write_str("Failed to decode blinded payment intro node onion payload"),
15731577
}
15741578
}
15751579
}
@@ -2058,8 +2062,6 @@ impl Writeable for OutboundOnionPayload {
20582062

20592063
impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload where NS::Target: NodeSigner {
20602064
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, &NS)) -> Result<Self, DecodeError> {
2061-
// TODO if we are the intro node and hit an error here, we won't correctly return the malformed
2062-
// error atm
20632065
let (update_add_blinding_point, node_signer) = args;
20642066
let mut amt = None;
20652067
let mut cltv_value = None;
@@ -2093,27 +2095,35 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload w
20932095
Ok(true)
20942096
});
20952097

2098+
let err = intro_node_blinding_point.map_or(DecodeError::InvalidValue,
2099+
|_| { DecodeError::InvalidIntroNodePayload });
20962100
if update_add_blinding_point.is_some() && intro_node_blinding_point.is_some() {
2097-
return Err(DecodeError::InvalidValue)
2101+
// This should be unreachable because we would've attempted to use the
2102+
// update_add_blinding_point to tweak to our onion packet pubkey and subsequently failed the
2103+
// HMAC check.
2104+
debug_assert!(false);
2105+
return Err(err)
20982106
}
2099-
if amt.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
2107+
if amt.unwrap_or(0) > MAX_VALUE_MSAT { return Err(err) }
21002108

21012109
if let Some(blinding_point) = update_add_blinding_point.or(intro_node_blinding_point) {
21022110
if short_id.is_some() || payment_data.is_some() || payment_metadata.is_some() {
2103-
return Err(DecodeError::InvalidValue)
2111+
return Err(err)
21042112
}
2105-
let enc_tlvs = encrypted_tlvs_opt.ok_or(DecodeError::InvalidValue)?.0;
2113+
let enc_tlvs = encrypted_tlvs_opt.ok_or_else(|| err.clone())?.0;
21062114
let enc_tlvs_ss = node_signer.ecdh(Recipient::Node, &blinding_point, None)
2107-
.map_err(|_| DecodeError::InvalidValue)?;
2115+
.map_err(|_| err.clone())?;
21082116
let rho = onion_utils::gen_rho_from_shared_secret(&enc_tlvs_ss.secret_bytes());
21092117
let mut s = Cursor::new(&enc_tlvs);
21102118
let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64);
2111-
match ChaChaPolyReadAdapter::read(&mut reader, rho)? {
2119+
let decoded_blinded_tlvs = ChaChaPolyReadAdapter::read(&mut reader, rho).map_err(|e|
2120+
if intro_node_blinding_point.is_some() { DecodeError::InvalidIntroNodePayload } else { e })?;
2121+
match decoded_blinded_tlvs {
21122122
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Forward(ForwardTlvs {
21132123
short_channel_id, payment_relay, payment_constraints, features
21142124
})} => {
21152125
if amt.is_some() || cltv_value.is_some() || total_msat.is_some() {
2116-
return Err(DecodeError::InvalidValue)
2126+
return Err(err)
21172127
}
21182128
Ok(Self::BlindedForward {
21192129
short_channel_id,
@@ -2126,6 +2136,8 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload w
21262136
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs {
21272137
payment_secret, payment_constraints
21282138
})} => {
2139+
// Per the spec, if we're the intro node and the final node we should error as if we're
2140+
// not part of a blinded path.
21292141
if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
21302142
Ok(Self::BlindedReceive {
21312143
amt_msat: amt.ok_or(DecodeError::InvalidValue)?,

lightning/src/ln/onion_utils.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,7 @@ pub(crate) enum OnionDecodeErr {
894894
Relay {
895895
err_msg: &'static str,
896896
err_code: u16,
897+
err_data: Vec<u8>,
897898
},
898899
}
899900

@@ -936,16 +937,16 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(shared_secret: [u8
936937
let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&hop_data[..]) };
937938
match R::read(&mut chacha_stream, read_args) {
938939
Err(err) => {
939-
let error_code = match err {
940-
msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte
940+
let (err_code, err_data) = match err {
941+
msgs::DecodeError::UnknownVersion => (0x4000 | 1, Vec::new()), // unknown realm byte
941942
msgs::DecodeError::UnknownRequiredFeature|
942943
msgs::DecodeError::InvalidValue|
943-
msgs::DecodeError::ShortRead => 0x4000 | 22, // invalid_onion_payload
944-
_ => 0x2000 | 2, // Should never happen
944+
msgs::DecodeError::ShortRead => (0x4000 | 22, Vec::new()), // invalid_onion_payload
945+
msgs::DecodeError::InvalidIntroNodePayload => (INVALID_ONION_BLINDING, vec![0; 32]),
946+
_ => (0x2000 | 2, Vec::new()), // Should never happen
945947
};
946948
return Err(OnionDecodeErr::Relay {
947-
err_msg: "Unable to decode our hop data",
948-
err_code: error_code,
949+
err_msg: "Unable to decode our hop data", err_code, err_data,
949950
});
950951
},
951952
Ok(msg) => {
@@ -954,6 +955,7 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(shared_secret: [u8
954955
return Err(OnionDecodeErr::Relay {
955956
err_msg: "Unable to decode our hop data",
956957
err_code: 0x4000 | 22,
958+
err_data: Vec::new(),
957959
});
958960
}
959961
if hmac == [0; 32] {

lightning/src/ln/peer_handler.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,10 @@ impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CM
14471447
}
14481448
(msgs::DecodeError::BadLengthDescriptor, _) => return Err(PeerHandleError { }),
14491449
(msgs::DecodeError::Io(_), _) => return Err(PeerHandleError { }),
1450+
(msgs::DecodeError::InvalidIntroNodePayload, _) => {
1451+
debug_assert!(false); // This error can only be hit when decoding an onion payload
1452+
return Err(PeerHandleError { })
1453+
},
14501454
}
14511455
}
14521456
};

0 commit comments

Comments
 (0)