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
Summary
MessageRaceLimits::decidecalculatesbest_target_nonce + 1without checkedarithmetic. 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 returnsNonewhen no nonce remains.PoC Unit Test
Place this in
bridges/relays/messages/src/generated_tests.rsand declare#[cfg(test)] mod generated_tests;fromlib.rsif needed.Reproduction Command
cargo test -p messages-relay \ generated_tests::test_inv_as8w4y36_decide_returns_none_at_max_nonce \ -- --ignored --exact --nocaptureObserved Result
Expected Behavior
Return
Nonewhen the target nonce isMessageNonce::MAX; do not panic or wrap.Likely Fix Direction
Use checked arithmetic for
best_target_nonce + 1and returnNonewhen the target nonce is already the maximum representable value.RWF Metadata
Found by Runtime Whitebox Fuzzer
Finding:
find_hbegp84ajfTarget:
messages-relaySeverity:
mediumCategory:
arithmetic overflow