Avoid `Vec::with_capacity(huge)` on empty `Route`s
What changed, and why it matters
This commit fixes a bug where a deliberately empty payment route could cause the software to try to reserve an enormous amount of memory. The fix makes the onion-packet builder reject empty routes immediately, preventing a potential denial-of-service or crash from an oversized memory allocation.
Treat as a low-to-moderate hardening fix. Review callers of `create_payment_onion` to ensure empty routes are not constructed elsewhere, and consider backporting to stable branches if the onion-building utilities are exposed to untrusted input.
Security signals we found
Denial-of-service vector: empty route could trigger massive memory allocation
Input validation added for empty payload vector
Error message changed to disclose that empty routes are now rejected
Reported by an external security team (Block's Security Team)
Evidence from the diff
The patch adds an early if payloads.is_empty() { return Err(()); } guard in construct_onion_packet_with_init_noise and updates create_payment_onion_internal so that an empty Path (hops: vec![]) returns APIError::InvalidRoute instead of reaching the Vec::with_capacity calculation that could request a huge buffer. Tests are updated to expect the new error message.
Changed components
lightning/src/ln/onion_utils.rsconstruct_onion_packet_with_init_noisecreate_payment_onion_internalcreate_payment_onionInspect captured patch +37 / −4
diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs
index 45640d3..0515a52 100644
--- a/lightning/src/ln/max_payment_path_len_tests.rs
+++ b/lightning/src/ln/max_payment_path_len_tests.rs
@@ -149,7 +149,7 @@ fn large_payment_metadata() {
.unwrap_err();
match err {
APIError::InvalidRoute { err } => {
- assert_eq!(err, "Route size too large considering onion data");
+ assert_eq!(err, "Route size too large (or empty) considering onion data");
},
_ => panic!(),
}
@@ -441,7 +441,7 @@ fn blinded_path_with_custom_tlv() {
.unwrap_err();
match err {
APIError::InvalidRoute { err } => {
- assert_eq!(err, "Route size too large considering onion data");
+ assert_eq!(err, "Route size too large (or empty) considering onion data");
},
_ => panic!(),
}
diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs
index 9b1b009..602d731 100644
--- a/lightning/src/ln/onion_utils.rs
+++ b/lightning/src/ln/onion_utils.rs
@@ -832,6 +832,10 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
mut payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, mut packet_data: P::Data,
associated_data: Option<&PaymentHash>,
) -> Result<P, ()> {
+ if payloads.is_empty() {
+ return Err(());
+ }
+
let filler = {
let packet_data = packet_data.as_mut();
const ONION_HOP_DATA_LEN: usize = 65; // We may decrease this eventually after TLV is common
@@ -2682,7 +2686,7 @@ pub(crate) fn create_payment_onion_internal<T: secp256k1::Signing>(
None,
)
.map_err(|_| APIError::InvalidRoute {
- err: "Route size too large considering onion data".to_owned(),
+ err: "Route size too large (or empty) considering onion data".to_owned(),
})?;
(&trampoline_outer_onion, Some(trampoline_packet))
@@ -2706,7 +2710,7 @@ pub(crate) fn create_payment_onion_internal<T: secp256k1::Signing>(
let onion_keys = construct_onion_keys(&secp_ctx, &path, session_priv);
let onion_packet = construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash)
.map_err(|_| APIError::InvalidRoute {
- err: "Route size too large considering onion data".to_owned(),
+ err: "Route size too large (or empty) considering onion data".to_owned(),
})?;
Ok((onion_packet, htlc_msat, htlc_cltv))
}
@@ -4104,4 +4108,33 @@ mod tests {
assert_eq!(buffer.len(), 65535);
}
+
+ #[test]
+ fn create_payment_onion_fails_for_empty_route() {
+ let secp_ctx = Secp256k1::new();
+ let session_priv = get_test_session_key();
+ let recipient_onion = RecipientOnionFields::spontaneous_empty(1000);
+ let payment_hash = PaymentHash([0; 32]);
+ let empty_path = Path { hops: vec![], blinded_tail: None };
+
+ let err = super::create_payment_onion(
+ &secp_ctx,
+ &empty_path,
+ &session_priv,
+ &recipient_onion,
+ 100,
+ &payment_hash,
+ &None,
+ None,
+ [0; 32],
+ )
+ .unwrap_err();
+
+ match err {
+ APIError::InvalidRoute { err } => {
+ assert_eq!(err, "Route size too large (or empty) considering onion data");
+ },
+ _ => panic!("Expected InvalidRoute error, got {:?}", err),
+ }
+ }
}
Why this scored 44/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.