Skip to content

Commit 42710b5

Browse files
committed
Fix long route failure attribution
An out of bound error could occur when attribution data was provided by the downstream hop for an exceptionally long route. The fix limits the verification of attribution data hmacs up to hop 20. If the sender chooses to use a longer route, failures in the final part of the route won't be attributable.
1 parent 3a394df commit 42710b5

File tree

1 file changed

+84
-5
lines changed

1 file changed

+84
-5
lines changed

lightning/src/ln/onion_utils.rs

+84-5
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,10 @@ where
11441144
.expect("Route we used spontaneously grew invalid keys in the middle of it?");
11451145
}
11461146

1147+
// In the best case, paths can be up to 27 hops. But attribution data can only be conveyed back to the sender from
1148+
// the first 20 hops. Determine the number of hops to be used for attribution data.
1149+
let attributable_hop_count = usize::min(path.hops.len(), MAX_HOPS);
1150+
11471151
// Handle packed channel/node updates for passing back for the route handler
11481152
let mut iterator = onion_keys.into_iter().enumerate().peekable();
11491153
while let Some((route_hop_idx, (route_hop_option, shared_secret))) = iterator.next() {
@@ -1215,11 +1219,13 @@ where
12151219
// Check attr error HMACs if present.
12161220
if let Some(ref mut attribution_data) = encrypted_packet.attribution_data {
12171221
// Only consider hops in the regular path for attribution data. Failures in a blinded path are not attributable.
1218-
if route_hop_idx < path.hops.len() {
1219-
// Calculate position relative to the final hop. The final hop is at position 0. The failure node does not
1220-
// need to come from the final node, but we need to look at the chain of HMACs that does include all data up
1221-
// to the final node. For a more nearby failure, the verified HMACs will include some zero padding data.
1222-
let position = path.hops.len() - route_hop_idx - 1;
1222+
if route_hop_idx < attributable_hop_count {
1223+
// Calculate position relative to the last attributable hop. The last attributable hop is at position 0.
1224+
// The failure node does not need to come from the last attributable hop, but we need to look at the
1225+
// chain of HMACs that does include all data up to the last attributable hop. For a more nearby failure,
1226+
// the verified HMACs will include some zero padding data. Failures beyond the last attributable hop
1227+
// will not be attributable.
1228+
let position = attributable_hop_count - route_hop_idx - 1;
12231229
let hold_time = attribution_data.verify(
12241230
&encrypted_packet.data,
12251231
shared_secret.as_ref(),
@@ -3169,6 +3175,79 @@ mod tests {
31693175
assert_eq!(decrypted_failure.short_channel_id, Some(0));
31703176
}
31713177

3178+
#[test]
3179+
fn test_long_route_attributable_failure() {
3180+
// Test a long route that exceeds the reach of attribution data.
3181+
3182+
let secp_ctx = Secp256k1::new();
3183+
const LEGACY_MAX_HOPS: usize = 27;
3184+
3185+
// Construct a route with 27 hops.
3186+
let mut hops = Vec::new();
3187+
for i in 0..LEGACY_MAX_HOPS {
3188+
let mut secret_bytes = [0; 32];
3189+
secret_bytes[0] = (i + 1) as u8;
3190+
let secret_key = SecretKey::from_slice(&secret_bytes).unwrap();
3191+
let pubkey = secret_key.public_key(&secp_ctx);
3192+
3193+
hops.push(RouteHop {
3194+
pubkey,
3195+
channel_features: ChannelFeatures::empty(),
3196+
node_features: NodeFeatures::empty(),
3197+
short_channel_id: i as u64,
3198+
fee_msat: 0,
3199+
cltv_expiry_delta: 0,
3200+
maybe_announced_channel: true,
3201+
});
3202+
}
3203+
let path = Path { hops, blinded_tail: None };
3204+
3205+
// Calculate shared secrets.
3206+
let mut onion_keys = Vec::new();
3207+
let session_key = get_test_session_key();
3208+
construct_onion_keys_generic_callback(
3209+
&secp_ctx,
3210+
&path.hops,
3211+
None,
3212+
&session_key,
3213+
|shared_secret, _, _, _, _| onion_keys.push(shared_secret),
3214+
)
3215+
.unwrap();
3216+
3217+
// Construct the htlc source.
3218+
let logger = TestLogger::new();
3219+
let htlc_source = HTLCSource::OutboundRoute {
3220+
path,
3221+
session_priv: session_key,
3222+
first_hop_htlc_msat: 0,
3223+
payment_id: PaymentId([1; 32]),
3224+
};
3225+
3226+
// Iterate over all possible failure positions and check that the cases that can be attributed are.
3227+
for failure_pos in 0..LEGACY_MAX_HOPS {
3228+
// Create a failure packet with bogus data.
3229+
let packet = vec![1u8; 292];
3230+
let mut onion_error =
3231+
OnionErrorPacket { data: packet, attribution_data: Some(AttributionData::new()) };
3232+
3233+
// Apply the processing that the preceding hops would apply.
3234+
for i in (0..failure_pos).rev() {
3235+
let shared_secret = onion_keys[i].secret_bytes();
3236+
process_failure_packet(&mut onion_error, &shared_secret, 0);
3237+
super::crypt_failure_packet(&shared_secret, &mut onion_error);
3238+
}
3239+
3240+
// Decrypt the failure.
3241+
let decrypted_failure =
3242+
process_onion_failure(&secp_ctx, &&logger, &htlc_source, onion_error);
3243+
3244+
// Expect attribution up to hop 20.
3245+
let expected_failed_chan =
3246+
if failure_pos < MAX_HOPS { Some(failure_pos as u64) } else { None };
3247+
assert_eq!(decrypted_failure.short_channel_id, expected_failed_chan);
3248+
}
3249+
}
3250+
31723251
#[test]
31733252
fn test_unreadable_failure_packet_onion() {
31743253
// Create a failure packet with a valid hmac but unreadable failure message.

0 commit comments

Comments
 (0)