# Reentrancy in MessageProxyForSchain leads to replay attacks

MessageProxyForSchain updates incomingMessageCounter after calling attacker-controlled receiver, letting reentrancy replay the same cross-chain messages and double-spend transfers.

- Payout: Audit contest
- Severity: high
- Program: SKALE
- Found by: cmichel
- Disclosed: 2022-11-11
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-02-skale-findings/issues/24
- Library page: https://bugbountylibrary.com/r/c4%3A2022-02-skale%3AH-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:reentrancy vuln:signature-replay

**Target:** MessageProxyForSchain.postIncomingMessages cross-chain message proxy
**Root cause:** postIncomingMessages invokes _callReceiverContract on attacker-controlled contracts before incrementing connectedChains[fromChainHash].incomingMessageCounter, so the replay-protection counter is stale during the external call.
**Steps:**
1. Attacker queues two cross-chain messages: `transfer 1000 USDC` and a call to an attacker contract that re-enters on transfer.
2. Node submits `postIncomingMessages(params)`; USDC transfer executes.
3. During `_callReceiverContract`, attacker contract calls `postIncomingMessages(params)` again with same params.
4. Counter not yet incremented, so replay passes validation and 1000 USDC transfers a second time; recursion stops.
5. Outer call then increments counter by messages.length.
**How found:** Code reading of the message loop: noticed _callReceiverContract external call precedes the incomingMessageCounter update, and that startingCounter is the sole replay protection with no per-message nonce.
**Exploitation:** Attacker controls a receiver contract invoked mid-loop; reenters postIncomingMessages with identical params before incomingMessageCounter increments, replaying the same message batch to duplicate token transfers.
**Insight:** When a replay guard is a monotonic counter updated after processing a batch, check whether any external call inside the loop lets the receiver reenter before the counter write; a single attacker-controlled message in the batch is enough.
