Platform: Code4rena
Start Date: 01/09/2023
Pot Size: $36,500 USDC
Total HM: 4
Participants: 70
Period: 6 days
Judge: kirk-baird
Id: 281
League: ETH
Rank: 20/70
Findings: 1
Award: $265.68
π Selected for report: 0
π Solo Findings: 0
π Selected for report: Udsen
Also found by: 0xDING99YA, 0xpiken, Inspecktor, SpicyMeatball, adriro, ast3ros, bin2chen, bowtiedvirus, kutugu, pep7siup, seerether
265.675 USDC - $265.68
If a user send token from Chain A to Chain C, and during the waiting for approval the user send the same amount of token from Chain B to Chain C, if Chain A and Chain B have the same nonce, users can lost their token from Chain A to Chain C forever.
_execute() function in DestinationBridge.sol record a transaction from a source chain by txnHash, and txnHash is keccak256(payload). payload is just a combination of VERSION, sender, amount and nonce. So if a user send transactions from two source chains which have the same nonce, the txnHash will be exactly same. Since when a transaction is received it need to wait for approval to complete, if at this time a transaction with same txnHash is sent to the same destination chain, it can overwrite the pending transaction, and user will lose that token forever.
A POC is here to illustrate this, simply add it to forge-tests/bridges/DestinationBridge.t.sol and run
function test_User_can_lost_money() public { // We already add chain support for arbitrum in setUp(), now we add support for optimism vm.prank(guardian); destinationBridge.addChainSupport( "optimism", "0xce16F69375520ab01377ce7B88f5BA8C48F8D612" ); // To better illustrate a new bridged transaction can overwrite the existing bridged transaction, we set the need of // approval larger than 2 amounts.push(500e18); numOfApprovers.push(5); vm.startPrank(guardian); destinationBridge.setThresholds(arb, amounts, numOfApprovers); destinationBridge.setThresholds("optimism", amounts, numOfApprovers); vm.stopPrank(); // First transaction is a bridged transaction from Arbitrum, Alice want to send 100e18 token bytes memory payload = abi.encode(bytes32("1.0"), alice, 100e18, 1); bytes32 cmdId = bytes32( 0x9991faa1f435675159ffae64b66d7ecfdb55c29755869a18db8497b4392347e0 ); string memory srcChain = "arbitrum"; string memory srcAddress = "0xce16F69375520ab01377ce7B88f5BA8C48F8D666"; approve_message( cmdId, srcChain, srcAddress, address(destinationBridge), keccak256(payload) ); destinationBridge.execute(cmdId, srcChain, srcAddress, payload); // ondoSigner0 also approves this arbitrum transaction, so the total approval should be 2 now vm.prank(ondoSigner0); destinationBridge.approve(keccak256(payload)); assertEq(destinationBridge.getNumApproved(keccak256(payload)), 2); // Now while Alice's arbitrum transaction is waiting for approval, there is a exactly same transaction from optimism cmdId = bytes32( 0x9991faa1f435675159ffae64b66d7ecfdb55c29755869a18db8497b4392347e0 ); srcChain = "optimism"; srcAddress = "0xce16F69375520ab01377ce7B88f5BA8C48F8D612"; approve_message( cmdId, srcChain, srcAddress, address(destinationBridge), keccak256(payload) ); destinationBridge.execute(cmdId, srcChain, srcAddress, payload); // Now the new optimism overwrites the pending arbitrum transaction! We can see the transaction is overwritten // and the approval for the same txnHash changes from 2 to 1 assertEq(destinationBridge.getNumApproved(keccak256(payload)), 1); }
Manual Review, Foundry
Add srcChain when creating a payload, this can prevent two source chains have exactly same payload.
Other
#0 - c4-pre-sort
2023-09-07T23:03:55Z
raymondfam marked the issue as primary issue
#1 - c4-pre-sort
2023-09-07T23:05:30Z
raymondfam marked the issue as sufficient quality report
#2 - c4-sponsor
2023-09-11T16:22:58Z
tom2o17 marked the issue as disagree with severity
#3 - tom2o17
2023-09-11T16:26:21Z
So agree with this issue, I am curious given the hoops that a user must jump through to invoke this error if this can be downgraded.
Basically I feel that given a user must wait for the nonce to be the same between src chains and must pass the message using the same wallet and amount.
tldr: ilf the user really needs to put in work to invoke this error.
#4 - c4-sponsor
2023-09-12T16:13:39Z
ypatil12 (sponsor) confirmed
#5 - kirk-baird
2023-09-17T06:38:49Z
I'm inclined to accept this as high severity.
There are requirements for this attack / bug to have the same:
If these conditions are met there will be significant loss of funds.
#6 - c4-judge
2023-09-17T06:39:01Z
kirk-baird marked the issue as satisfactory
#7 - c4-judge
2023-09-17T06:47:37Z
kirk-baird marked issue #391 as primary and marked this issue as a duplicate of 391
#8 - tom2o17
2023-09-19T19:46:02Z
@kirk-baird
Agree with the above more so a comment that the conditions to induce the bug are sufficiently difficult to produce, given that the user will have to be listening to cross chain state and be subject to race conditions, all so that the user ....checks notes.... burns their funds.
Generally think that when working with invalid state paths it is worth considering the difficulty of producing said bug in the wild, when weighting the severity, but defer to y'all. π€
#9 - stalinMacias
2023-09-25T15:47:10Z
@kirk-baird
I think this issue fits more the judging description of a medium severity than a high severity. As explained by @tom2o17 , to me it looks like it matches the exact description of medium severity issue: "the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements."
#10 - 0xnirlin
2023-09-25T16:51:48Z
Came to comment the same as stalin, the condition required to make this happen are not too straight forward.
#11 - kirk-baird
2023-09-26T03:03:33Z
The debate here for this issue and new primary #391 is whether the likelihood of this issue is high enough to qualify for high severity vs medium severity.
Looking at each aspect
burnAndCallAxelar()
to increment the lower nonce such that they match. However, this requires front-running and organizing the mem pool so would realistically need to be a block producer to exploit this.I'm going to consider the opinions from the sponsor and other wardens here, that this is not sufficient likelihood to qualify for a high severity and downgrade it to medium.
#12 - c4-judge
2023-09-26T03:03:43Z
kirk-baird changed the severity to 2 (Med Risk)