Skip to content

Commit 6da7ec7

Browse files
Reject unknown TLVs in blinded payment payloads
Reject unknown odd TLVs and disallowed custom TLVs when decoding blinded payment and trampoline payloads. Add a dedicated helper macro for TLV readers with custom decode callbacks and update the affected tests to cover the stricter validation paths. Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
1 parent 7694260 commit 6da7ec7

5 files changed

Lines changed: 254 additions & 191 deletions

File tree

lightning/src/blinded_path/payment.rs

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ impl<'a> Writeable for BlindedPaymentTlvsRef<'a> {
632632

633633
impl Readable for BlindedPaymentTlvs {
634634
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
635-
_init_and_read_tlv_stream!(r, {
635+
_init_and_read_tlv_stream_with_custom_tlv_decode!(r, {
636636
// Reasoning: Padding refers to filler data added to a packet to increase
637637
// its size and obscure its actual length. Since padding contains no meaningful
638638
// information, we can safely omit reading it here.
@@ -645,6 +645,14 @@ impl Readable for BlindedPaymentTlvs {
645645
(65536, payment_secret, option),
646646
(65537, payment_context, option),
647647
(65539, is_dummy, option)
648+
}, |typ: u64, _reader: &mut FixedLengthReader<_>| -> Result<bool, DecodeError> {
649+
if typ == 1 {
650+
return Ok(true);
651+
}
652+
if typ % 2 == 1 {
653+
return Err(DecodeError::UnknownRequiredFeature);
654+
}
655+
Ok(false)
648656
});
649657

650658
match (
@@ -685,14 +693,22 @@ impl Readable for BlindedPaymentTlvs {
685693

686694
impl Readable for BlindedTrampolineTlvs {
687695
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
688-
_init_and_read_tlv_stream!(r, {
696+
_init_and_read_tlv_stream_with_custom_tlv_decode!(r, {
689697
(4, next_trampoline, option),
690698
(8, next_blinding_override, option),
691699
(10, payment_relay, option),
692700
(12, payment_constraints, required),
693701
(14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))),
694702
(65536, payment_secret, option),
695703
(65537, payment_context, option),
704+
}, |typ: u64, _reader: &mut FixedLengthReader<_>| -> Result<bool, DecodeError> {
705+
if typ == 1 {
706+
return Ok(true);
707+
}
708+
if typ % 2 == 1 {
709+
return Err(DecodeError::UnknownRequiredFeature);
710+
}
711+
Ok(false)
696712
});
697713

698714
if let Some(next_trampoline) = next_trampoline {
@@ -964,12 +980,15 @@ impl_writeable_tlv_based!(Bolt12RefundContext, {});
964980
#[cfg(test)]
965981
mod tests {
966982
use crate::blinded_path::payment::{
967-
Bolt12RefundContext, ForwardTlvs, PaymentConstraints, PaymentContext, PaymentForwardNode,
968-
PaymentRelay, ReceiveTlvs,
983+
BlindedPaymentTlvs, BlindedTrampolineTlvs, Bolt12RefundContext, ForwardTlvs,
984+
PaymentConstraints, PaymentContext, PaymentForwardNode, PaymentRelay, ReceiveTlvs,
969985
};
986+
use crate::io::Cursor;
970987
use crate::ln::functional_test_utils::TEST_FINAL_CLTV;
988+
use crate::ln::msgs::DecodeError;
971989
use crate::types::features::BlindedHopFeatures;
972990
use crate::types::payment::PaymentSecret;
991+
use crate::util::ser::{BigSize, Readable, Writeable};
973992
use bitcoin::secp256k1::PublicKey;
974993

975994
#[test]
@@ -1237,4 +1256,42 @@ mod tests {
12371256
.unwrap();
12381257
assert_eq!(blinded_payinfo.htlc_maximum_msat, 3997);
12391258
}
1259+
1260+
#[test]
1261+
fn blinded_payment_tlvs_reject_unknown_odd_types() {
1262+
let receive_tlvs = ReceiveTlvs {
1263+
payment_secret: PaymentSecret([0; 32]),
1264+
payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 1 },
1265+
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
1266+
};
1267+
1268+
let mut encoded = Vec::new();
1269+
receive_tlvs.write(&mut encoded).unwrap();
1270+
BigSize(65541).write(&mut encoded).unwrap();
1271+
BigSize(0).write(&mut encoded).unwrap();
1272+
1273+
assert!(matches!(
1274+
BlindedPaymentTlvs::read(&mut Cursor::new(&encoded)),
1275+
Err(DecodeError::UnknownRequiredFeature),
1276+
));
1277+
}
1278+
1279+
#[test]
1280+
fn blinded_trampoline_tlvs_reject_unknown_odd_types() {
1281+
let receive_tlvs = ReceiveTlvs {
1282+
payment_secret: PaymentSecret([0; 32]),
1283+
payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 1 },
1284+
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
1285+
};
1286+
1287+
let mut encoded = Vec::new();
1288+
receive_tlvs.write(&mut encoded).unwrap();
1289+
BigSize(65541).write(&mut encoded).unwrap();
1290+
BigSize(0).write(&mut encoded).unwrap();
1291+
1292+
assert!(matches!(
1293+
BlindedTrampolineTlvs::read(&mut Cursor::new(&encoded)),
1294+
Err(DecodeError::UnknownRequiredFeature),
1295+
));
1296+
}
12401297
}

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 20 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,7 +1412,7 @@ fn custom_tlvs_to_blinded_path() {
14121412
let chan_upd = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0).0.contents;
14131413

14141414
let amt_msat = 5000;
1415-
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None);
1415+
let (_payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None);
14161416
let payee_tlvs = ReceiveTlvs {
14171417
payment_secret,
14181418
payment_constraints: PaymentConstraints {
@@ -1448,12 +1448,23 @@ fn custom_tlvs_to_blinded_path() {
14481448
let path = &[&nodes[1]];
14491449
let args = PassAlongPathArgs::new(&nodes[0], path, amt_msat, payment_hash, ev)
14501450
.with_payment_secret(payment_secret)
1451-
.with_custom_tlvs(recipient_onion_fields.custom_tlvs.clone());
1451+
.with_custom_tlvs(recipient_onion_fields.custom_tlvs.clone())
1452+
.without_clearing_recipient_events()
1453+
.without_claimable_event();
14521454
do_pass_along_path(args);
1453-
claim_payment_along_route(
1454-
ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1]]], payment_preimage)
1455-
.with_custom_tlvs(recipient_onion_fields.custom_tlvs.clone())
1455+
check_added_monitors(&nodes[1], 1);
1456+
1457+
expect_htlc_handling_failed_destinations!(
1458+
nodes[1].node.get_and_clear_pending_events(),
1459+
&[HTLCHandlingFailureType::InvalidOnion]
14561460
);
1461+
let updates = get_htlc_update_msgs(&nodes[1], &nodes[0].node.get_our_node_id());
1462+
assert_eq!(updates.update_fail_htlcs.len(), 1);
1463+
nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
1464+
do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false);
1465+
expect_payment_failed_conditions(&nodes[0], payment_hash, true,
1466+
PaymentFailedConditions::new()
1467+
.expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionPayload, &[0; 0]));
14571468
}
14581469

14591470
#[test]
@@ -1718,116 +1729,12 @@ fn route_blinding_spec_test_vector() {
17181729
let bob_update_add = update_add_msg(110_000, 747_500, None, bob_onion);
17191730
let bob_node_signer = TestEcdhSigner { node_secret: bob_secret };
17201731
// Can't use the public API here as we need to avoid the CLTV delta checks (test vector uses
1721-
// < MIN_CLTV_EXPIRY_DELTA).
1722-
let (bob_peeled_onion, next_packet_details_opt) =
1723-
match onion_payment::decode_incoming_update_add_htlc_onion(
1724-
&bob_update_add, &bob_node_signer, &logger, &secp_ctx
1725-
) {
1726-
Ok(res) => res,
1727-
_ => panic!("Unexpected error")
1728-
};
1729-
let (carol_packet_bytes, carol_hmac) = if let onion_utils::Hop::BlindedForward {
1730-
next_hop_data: msgs::InboundOnionBlindedForwardPayload {
1731-
short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override
1732-
}, next_hop_hmac, new_packet_bytes, ..
1733-
} = bob_peeled_onion {
1734-
assert_eq!(short_channel_id, 1729);
1735-
assert!(next_blinding_override.is_none());
1736-
assert_eq!(intro_node_blinding_point, Some(bob_blinding_point));
1737-
assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 36, fee_proportional_millionths: 150, fee_base_msat: 10_000 });
1738-
assert_eq!(features, BlindedHopFeatures::empty());
1739-
assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 748_005, htlc_minimum_msat: 1500 });
1740-
(new_packet_bytes, next_hop_hmac)
1741-
} else { panic!() };
1742-
1743-
let carol_packet_details = next_packet_details_opt.unwrap();
1744-
let carol_onion = msgs::OnionPacket {
1745-
version: 0,
1746-
public_key: carol_packet_details.next_packet_pubkey,
1747-
hop_data: carol_packet_bytes,
1748-
hmac: carol_hmac,
1749-
};
1750-
let carol_update_add = update_add_msg(
1751-
carol_packet_details.outgoing_amt_msat, carol_packet_details.outgoing_cltv_value,
1752-
Some(pubkey_from_hex("034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0")),
1753-
carol_onion
1754-
);
1755-
let carol_node_signer = TestEcdhSigner { node_secret: carol_secret };
1756-
let (carol_peeled_onion, next_packet_details_opt) =
1757-
match onion_payment::decode_incoming_update_add_htlc_onion(
1758-
&carol_update_add, &carol_node_signer, &logger, &secp_ctx
1759-
) {
1760-
Ok(res) => res,
1761-
_ => panic!("Unexpected error")
1762-
};
1763-
let (dave_packet_bytes, dave_hmac) = if let onion_utils::Hop::BlindedForward {
1764-
next_hop_data: msgs::InboundOnionBlindedForwardPayload {
1765-
short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override
1766-
}, next_hop_hmac, new_packet_bytes, ..
1767-
} = carol_peeled_onion {
1768-
assert_eq!(short_channel_id, 1105);
1769-
assert_eq!(next_blinding_override, Some(pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")));
1770-
assert!(intro_node_blinding_point.is_none());
1771-
assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 48, fee_proportional_millionths: 100, fee_base_msat: 500 });
1772-
assert_eq!(features, BlindedHopFeatures::empty());
1773-
assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 747_969, htlc_minimum_msat: 1500 });
1774-
(new_packet_bytes, next_hop_hmac)
1775-
} else { panic!() };
1776-
1777-
let dave_packet_details = next_packet_details_opt.unwrap();
1778-
let dave_onion = msgs::OnionPacket {
1779-
version: 0,
1780-
public_key: dave_packet_details.next_packet_pubkey,
1781-
hop_data: dave_packet_bytes,
1782-
hmac: dave_hmac,
1783-
};
1784-
let dave_update_add = update_add_msg(
1785-
dave_packet_details.outgoing_amt_msat, dave_packet_details.outgoing_cltv_value,
1786-
Some(pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")),
1787-
dave_onion
1788-
);
1789-
let dave_node_signer = TestEcdhSigner { node_secret: dave_secret };
1790-
let (dave_peeled_onion, next_packet_details_opt) =
1791-
match onion_payment::decode_incoming_update_add_htlc_onion(
1792-
&dave_update_add, &dave_node_signer, &logger, &secp_ctx
1793-
) {
1794-
Ok(res) => res,
1795-
_ => panic!("Unexpected error")
1796-
};
1797-
let (eve_packet_bytes, eve_hmac) = if let onion_utils::Hop::BlindedForward {
1798-
next_hop_data: msgs::InboundOnionBlindedForwardPayload {
1799-
short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override
1800-
}, next_hop_hmac, new_packet_bytes, ..
1801-
} = dave_peeled_onion {
1802-
assert_eq!(short_channel_id, 561);
1803-
assert!(next_blinding_override.is_none());
1804-
assert!(intro_node_blinding_point.is_none());
1805-
assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 144, fee_proportional_millionths: 250, fee_base_msat: 0 });
1806-
assert_eq!(features, BlindedHopFeatures::empty());
1807-
assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 747_921, htlc_minimum_msat: 1500 });
1808-
(new_packet_bytes, next_hop_hmac)
1809-
} else { panic!() };
1810-
1811-
let eve_packet_details = next_packet_details_opt.unwrap();
1812-
let eve_onion = msgs::OnionPacket {
1813-
version: 0,
1814-
public_key: eve_packet_details.next_packet_pubkey,
1815-
hop_data: eve_packet_bytes,
1816-
hmac: eve_hmac,
1817-
};
1818-
let eve_update_add = update_add_msg(
1819-
eve_packet_details.outgoing_amt_msat, eve_packet_details.outgoing_cltv_value,
1820-
Some(pubkey_from_hex("03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")),
1821-
eve_onion
1822-
);
1823-
let eve_node_signer = TestEcdhSigner { node_secret: eve_secret };
1824-
// We can't decode the final payload because it contains a path_id and is missing some LDK
1825-
// specific fields.
1732+
// < MIN_CLTV_EXPIRY_DELTA). The vector includes an unknown odd encrypted TLV (type 561) in the
1733+
// blinded payload for Bob, which should now be rejected instead of forwarded.
18261734
match onion_payment::decode_incoming_update_add_htlc_onion(
1827-
&eve_update_add, &eve_node_signer, &logger, &secp_ctx
1735+
&bob_update_add, &bob_node_signer, &logger, &secp_ctx
18281736
) {
1829-
Err((HTLCFailureMsg::Malformed(msg), _)) => assert_eq!(msg.failure_code,
1830-
LocalHTLCFailureReason::InvalidOnionBlinding.failure_code()),
1737+
Err((HTLCFailureMsg::Relay(_), LocalHTLCFailureReason::InvalidOnionPayload)) => {},
18311738
_ => panic!("Unexpected error")
18321739
}
18331740
}

0 commit comments

Comments
 (0)