Skip to content

Commit 025fdc1

Browse files
Correctly fail back on intro node payload decode failure
1 parent 2db51c0 commit 025fdc1

File tree

6 files changed

+117
-24
lines changed

6 files changed

+117
-24
lines changed

fuzz/src/router.rs

Lines changed: 1 addition & 0 deletions
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

Lines changed: 74 additions & 1 deletion
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;
@@ -801,3 +801,76 @@ fn do_outbound_checks_failure(intro_node_fails: bool) {
801801
expect_payment_failed_conditions(&nodes[0], payment_hash, false,
802802
PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
803803
}
804+
805+
#[test]
806+
fn invalid_intro_node_payload() {
807+
// Ensure we fail back properly if the intro node's onion payload is bogus.
808+
let chanmon_cfgs = create_chanmon_cfgs(3);
809+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
810+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
811+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
812+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
813+
let chan_upd = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
814+
815+
let amt_msat = 5000;
816+
let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
817+
let intermediate_nodes = vec![(nodes[1].node.get_our_node_id(), ForwardTlvs {
818+
short_channel_id: chan_upd.short_channel_id,
819+
payment_relay: PaymentRelay {
820+
cltv_expiry_delta: chan_upd.cltv_expiry_delta,
821+
fee_proportional_millionths: chan_upd.fee_proportional_millionths,
822+
fee_base_msat: chan_upd.fee_base_msat,
823+
},
824+
payment_constraints: PaymentConstraints {
825+
max_cltv_expiry: u32::max_value(),
826+
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
827+
},
828+
features: BlindedHopFeatures::empty(),
829+
})];
830+
let payee_tlvs = ReceiveTlvs {
831+
payment_secret,
832+
payment_constraints: PaymentConstraints {
833+
max_cltv_expiry: u32::max_value(),
834+
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
835+
},
836+
};
837+
let mut secp_ctx = Secp256k1::new();
838+
let mut blinded_path = BlindedPath::new_for_payment(
839+
&intermediate_nodes[..], nodes[2].node.get_our_node_id(), payee_tlvs,
840+
chan_upd.htlc_maximum_msat, &chanmon_cfgs[2].keys_manager, &secp_ctx
841+
).unwrap();
842+
blinded_path.1.blinded_hops[0] = BlindedHop {
843+
blinded_node_id: blinded_path.1.blinded_hops[0].blinded_node_id,
844+
encrypted_payload: vec![0; 32], // Bogus intro node payload
845+
};
846+
847+
let route_params = RouteParameters {
848+
payment_params: PaymentParameters::blinded(vec![blinded_path]),
849+
final_value_msat: amt_msat
850+
};
851+
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(),
852+
PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
853+
check_added_monitors(&nodes[0], 1);
854+
let (update_add, commitment_signed) = {
855+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
856+
assert_eq!(events.len(), 1);
857+
let payment_ev = SendEvent::from_event(events.pop().unwrap());
858+
(payment_ev.msgs[0].clone(), payment_ev.commitment_msg.clone())
859+
};
860+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
861+
do_commitment_signed_dance(&nodes[1], &nodes[0], &commitment_signed, false, true);
862+
let (update_fail, commitment_signed) = {
863+
let events = nodes[1].node.get_and_clear_pending_msg_events();
864+
assert_eq!(events.len(), 1);
865+
match &events[0] {
866+
MessageSendEvent::UpdateHTLCs { updates, .. } => {
867+
(updates.update_fail_htlcs[0].clone(), updates.commitment_signed.clone())
868+
},
869+
_ => panic!()
870+
}
871+
};
872+
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail);
873+
do_commitment_signed_dance(&nodes[0], &nodes[1], &commitment_signed, false, true);
874+
expect_payment_failed_conditions(&nodes[0], payment_hash, false,
875+
PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
876+
}

lightning/src/ln/channelmanager.rs

Lines changed: 9 additions & 8 deletions
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
};
@@ -3059,7 +3059,8 @@ where
30593059
// inbound channel's state.
30603060
onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)),
30613061
onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::Receive { .. }, .. } => {
3062-
return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0; 0]);
3062+
return_err!("Final Node OnionHopData provided for us as an intermediary node",
3063+
0x4000 | 22, Vec::new());
30633064
},
30643065
onion_utils::Hop::Forward {
30653066
next_hop_data: msgs::InboundOnionPayload::BlindedReceive { .. }, ..
@@ -3200,7 +3201,7 @@ where
32003201
// instead.
32013202
code = 0x2000 | 2;
32023203
}
3203-
return_err!(err, code, &res.0[..]);
3204+
return_err!(err, code, res.0);
32043205
}
32053206
Ok((next_hop, shared_secret, next_packet_pk_opt))
32063207
}
@@ -4057,8 +4058,8 @@ where
40574058
// of the onion.
40584059
failed_payment!(err_msg, err_code, sha256_of_onion.to_vec(), None);
40594060
},
4060-
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => {
4061-
failed_payment!(err_msg, err_code, Vec::new(), Some(phantom_shared_secret));
4061+
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, err_data }) => {
4062+
failed_payment!(err_msg, err_code, err_data, Some(phantom_shared_secret));
40624063
},
40634064
};
40644065
match next_hop {

lightning/src/ln/msgs.rs

Lines changed: 21 additions & 9 deletions
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

Lines changed: 8 additions & 6 deletions
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

Lines changed: 4 additions & 0 deletions
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)