Skip to content

Commit ec58ced

Browse files
Correctly fail back on intro node payload decode failure
1 parent 8c47354 commit ec58ced

File tree

6 files changed

+117
-24
lines changed

6 files changed

+117
-24
lines changed

fuzz/src/router.rs

+1
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
157157
msgs::DecodeError::ShortRead => panic!("We picked the length..."),
158158
msgs::DecodeError::Io(e) => panic!("{:?}", e),
159159
msgs::DecodeError::UnsupportedCompression => return,
160+
msgs::DecodeError::InvalidIntroNodePayload => unreachable!(),
160161
}
161162
}
162163
}}

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;
@@ -674,3 +674,76 @@ fn do_outbound_checks_failure(intro_node_fails: bool) {
674674
expect_payment_failed_conditions(&nodes[0], payment_hash, false,
675675
PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
676676
}
677+
678+
#[test]
679+
fn invalid_intro_node_payload() {
680+
// Ensure we fail back properly if the intro node's onion payload is bogus.
681+
let chanmon_cfgs = create_chanmon_cfgs(3);
682+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
683+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
684+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
685+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
686+
let chan_upd = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
687+
688+
let amt_msat = 5000;
689+
let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
690+
let intermediate_nodes = vec![(nodes[1].node.get_our_node_id(), ForwardTlvs {
691+
short_channel_id: chan_upd.short_channel_id,
692+
payment_relay: PaymentRelay {
693+
cltv_expiry_delta: chan_upd.cltv_expiry_delta,
694+
fee_proportional_millionths: chan_upd.fee_proportional_millionths,
695+
fee_base_msat: chan_upd.fee_base_msat,
696+
},
697+
payment_constraints: PaymentConstraints {
698+
max_cltv_expiry: u32::max_value(),
699+
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
700+
},
701+
features: BlindedHopFeatures::empty(),
702+
})];
703+
let payee_tlvs = ReceiveTlvs {
704+
payment_secret,
705+
payment_constraints: PaymentConstraints {
706+
max_cltv_expiry: u32::max_value(),
707+
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
708+
},
709+
};
710+
let mut secp_ctx = Secp256k1::new();
711+
let mut blinded_path = BlindedPath::new_for_payment(
712+
&intermediate_nodes[..], nodes[2].node.get_our_node_id(), payee_tlvs,
713+
chan_upd.htlc_maximum_msat, &chanmon_cfgs[2].keys_manager, &secp_ctx
714+
).unwrap();
715+
blinded_path.1.blinded_hops[0] = BlindedHop {
716+
blinded_node_id: blinded_path.1.blinded_hops[0].blinded_node_id,
717+
encrypted_payload: vec![0; 32], // Bogus intro node payload
718+
};
719+
720+
let route_params = RouteParameters {
721+
payment_params: PaymentParameters::blinded(vec![blinded_path]),
722+
final_value_msat: amt_msat
723+
};
724+
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(),
725+
PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
726+
check_added_monitors(&nodes[0], 1);
727+
let (update_add, commitment_signed) = {
728+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
729+
assert_eq!(events.len(), 1);
730+
let payment_ev = SendEvent::from_event(events.pop().unwrap());
731+
(payment_ev.msgs[0].clone(), payment_ev.commitment_msg.clone())
732+
};
733+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
734+
do_commitment_signed_dance(&nodes[1], &nodes[0], &commitment_signed, false, true);
735+
let (update_fail, commitment_signed) = {
736+
let events = nodes[1].node.get_and_clear_pending_msg_events();
737+
assert_eq!(events.len(), 1);
738+
match &events[0] {
739+
MessageSendEvent::UpdateHTLCs { updates, .. } => {
740+
(updates.update_fail_htlcs[0].clone(), updates.commitment_signed.clone())
741+
},
742+
_ => panic!()
743+
}
744+
};
745+
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail);
746+
do_commitment_signed_dance(&nodes[0], &nodes[1], &commitment_signed, false, true);
747+
expect_payment_failed_conditions(&nodes[0], payment_hash, false,
748+
PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
749+
}

lightning/src/ln/channelmanager.rs

+9-8
Original file line numberDiff line numberDiff line change
@@ -2979,7 +2979,7 @@ where
29792979
return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
29802980
channel_id: msg.channel_id,
29812981
htlc_id: msg.htlc_id,
2982-
reason: HTLCFailReason::reason($err_code, $data.to_vec())
2982+
reason: HTLCFailReason::reason($err_code, $data)
29832983
.get_encrypted_failure_packet(&shared_secret, &None),
29842984
}));
29852985
}
@@ -2990,7 +2990,7 @@ where
29902990
if msg.blinding_point.is_some() {
29912991
return_malformed_err!($msg, INVALID_ONION_BLINDING);
29922992
} else {
2993-
return_err!($msg, INVALID_ONION_BLINDING, [0; 32]);
2993+
return_err!($msg, INVALID_ONION_BLINDING, vec![0; 32]);
29942994
}
29952995
}
29962996
}
@@ -3007,11 +3007,11 @@ where
30073007
return_malformed_err!(err_msg, err_code);
30083008
}
30093009
},
3010-
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => {
3010+
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, err_data }) => {
30113011
if msg.blinding_point.is_some() {
30123012
return_blinded_htlc_err!(err_msg);
30133013
} else {
3014-
return_err!(err_msg, err_code, &[0; 0]);
3014+
return_err!(err_msg, err_code, err_data);
30153015
}
30163016
},
30173017
};
@@ -3064,7 +3064,8 @@ where
30643064
// inbound channel's state.
30653065
onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)),
30663066
onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::Receive { .. }, .. } => {
3067-
return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0; 0]);
3067+
return_err!("Final Node OnionHopData provided for us as an intermediary node",
3068+
0x4000 | 22, Vec::new());
30683069
},
30693070
onion_utils::Hop::Forward {
30703071
next_hop_data: msgs::InboundOnionPayload::BlindedReceive { .. }, ..
@@ -3205,7 +3206,7 @@ where
32053206
// instead.
32063207
code = 0x2000 | 2;
32073208
}
3208-
return_err!(err, code, &res.0[..]);
3209+
return_err!(err, code, res.0);
32093210
}
32103211
Ok((next_hop, shared_secret, next_packet_pk_opt))
32113212
}
@@ -4062,8 +4063,8 @@ where
40624063
// of the onion.
40634064
failed_payment!(err_msg, err_code, sha256_of_onion.to_vec(), None);
40644065
},
4065-
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => {
4066-
failed_payment!(err_msg, err_code, Vec::new(), Some(phantom_shared_secret));
4066+
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, err_data }) => {
4067+
failed_payment!(err_msg, err_code, err_data, Some(phantom_shared_secret));
40674068
},
40684069
};
40694070
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)