Skip to content

[RWF] messages-relay: MessageRaceLimits overflows when target nonce is maximum #12721

Description

@xlc

Summary

MessageRaceLimits::decide calculates best_target_nonce + 1 without checked
arithmetic. At MessageNonce::MAX, debug builds panic and release builds wrap to zero,
despite there being no representable nonce left to deliver.

This finding was found by Runtime Whitebox Fuzzer.

Severity / Sensitivity

Medium severity, non-sensitive. This is a boundary arithmetic defect in public relayer code and is suitable for a public issue.

Source Evidence

  • bridges/relays/messages/src/message_race_limits.rs:78-81: unchecked addition.
  • bridges/relays/messages/src/message_race_strategy.rs:478-497: existing behavior returns None when no nonce remains.

PoC Unit Test

Place this in bridges/relays/messages/src/generated_tests.rs and declare
#[cfg(test)] mod generated_tests; from lib.rs if needed.

use super::*;
use bp_messages::{MessageNonce, Weight};
use crate::message_lane::MessageLane;
use crate::message_lane_loop::{MessageDetails, MessageDetailsMap};
use crate::message_lane_loop::tests::{header_id, TestMessageLane};
use crate::message_race_limits::{MessageRaceLimits, RelayMessagesBatchReference};

fn max_nonce_reference(
	best_target_nonce: MessageNonce,
) -> RelayMessagesBatchReference<TestMessageLane> {
	let mut details = MessageDetailsMap::new();
	details.insert(
		MessageNonce::MAX,
		MessageDetails { dispatch_weight: Weight::from_parts(1, 0), size: 1, reward: 0 },
	);
	RelayMessagesBatchReference::<TestMessageLane> {
		max_messages_in_this_batch: 10,
		max_messages_weight_in_single_batch: Weight::from_parts(100, 0),
		max_messages_size_in_single_batch: 1_000,
		best_target_nonce,
		nonces_queue: vec![(header_id(1), details)].into_iter().collect(),
		nonces_queue_range: 0..=0,
	}
}

// RWF-INVARIANTS: inv_as8w4y36
// RWF-RISKS: risk_yq5p6bas
#[ignore]
#[tokio::test]
async fn test_inv_as8w4y36_decide_returns_none_at_max_nonce() {
	let reference = max_nonce_reference(MessageNonce::MAX);
	assert_eq!(
		MessageRaceLimits::decide(reference).await,
		None,
		"nothing is deliverable when best_target_nonce is already MessageNonce::MAX"
	);
}

Reproduction Command

cargo test -p messages-relay \
  generated_tests::test_inv_as8w4y36_decide_returns_none_at_max_nonce \
  -- --ignored --exact --nocapture

Observed Result

panicked at bridges/relays/messages/src/message_race_limits.rs:79:13:
attempt to add with overflow

Expected Behavior

Return None when the target nonce is MessageNonce::MAX; do not panic or wrap.

Likely Fix Direction

Use checked arithmetic for best_target_nonce + 1 and return None when the target nonce is already the maximum representable value.

RWF Metadata

  • Found by Runtime Whitebox Fuzzer

  • Finding: find_hbegp84ajf

  • Target: messages-relay

  • Severity: medium

  • Category: arithmetic overflow

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions