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: 23/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
Effected Contract: DestinationBridge.sol
The hashed payload sent to _execute
in DestinationBridge can result in a collision that overwrites a previously saved but not yet approved transaction if two transactions are sent from two seperate chains, but with the same payload details.
In this report, I will detail the root cause of the issue, which a user could accidentally encounter, then detail a possible front-running grief exploit utilizing this vulnerability.
The payload in DestinationBridge uses information that is not mutually exclusive between chains, resulting in a possible hash collision that silenty overwrites incomplete transactions, resulting in permanently lost funds. Nonce is a particularly tricky part of the payload, since two distinct chains could have the same nonce in their Source Bridge.
overlapping_nonce
.burnAndCallAxelar(100 ether, address(DC))
on SA and SB at the same time.keccak256(VERSION, alice, 100 ether, overlapping_nonce)
, but awaiting another approval before minting the 100 tokens.// DestinationBridge.t.sol function test_Receiver_can_be_overwritten_if_same_payload_is_sent_from_another_chain() public { vm.label(alice, "alice"); bytes memory payload = abi.encode(VERSION, alice, 100e18, 1); bytes32 txnHash = keccak256(payload); bytes memory payloadOtherChain = abi.encode(VERSION, alice, 100e18, 1); bytes32 txnHashOtherChain = keccak256(payloadOtherChain); // The contents of the payload are the same. Therefore the hashes are the same. assertEq( txnHashOtherChain, txnHash ); bytes32 cmdIdPolygon = bytes32( 0x1234faa1f435675159ffae64b66d7ecfdb55c29755869a18db8497b43923adf9 ); string memory srcPolygonChain = "Polygon"; string memory srcPolygonAddress = "0xPolygonSourceChainAddressPolygonSourcePo"; approve_message( cmdIdPolygon, srcPolygonChain, srcPolygonAddress, address(destinationBridge), keccak256(payload) ); vm.expectEmit(true, true, true, true); emit ChainIdSupported( "Polygon", "0xPolygonSourceChainAddressPolygonSourcePo" ); initializeArrayValues(); vm.startPrank(guardian); destinationBridge.addChainSupport(srcPolygonChain, srcPolygonAddress); destinationBridge.setThresholds(srcPolygonChain, amounts, numOfApprovers); vm.stopPrank(); // Send msg from Chain A. vm.prank(address(gateway_eth)); destinationBridge.execute(cmdIdPolygon, srcPolygonChain, srcPolygonAddress, payload); // Send msg from Chain B, which overwrites the txn with the same payload details. vm.prank(address(gateway_eth)); sendMessage(payloadOtherChain); vm.expectEmit(true, true, true, true); emit BridgeCompleted(alice, 100e18); vm.prank(ondoSigner0); destinationBridge.approve(keccak256(payloadOtherChain)); // Oh no! We shouldn't be reverting, we need to approve the original tx!!! vm.expectRevert(); vm.prank(ondoSigner0); destinationBridge.approve(keccak256(payload)); // Should be 200e18, but since _execute overwrote our original tx, we lose those assets forever. assertEq(usdy.balanceOf(alice), 100e18); }
burnAndCallAxelar
transactions on multiple blockchains, each with a nonce that are fairly close to each other, the same amount of tokens, and the same destination chain.burnAndCallAxelar
himself multiple times to increment the nonce of the SourceBridges.A user can permanently lose tokens sent to a DestinationBridge.
Manual Review
// DestinationBridge.sol 110| bytes32 txnHash = keccak256(abi.encodePacked(payload, srcChain));
This information is already available in the _execute()
transaction, but tests utilizing approve()
will need to be modified to accomodate the modified hash.
Math
#0 - c4-pre-sort
2023-09-08T16:13:37Z
raymondfam marked the issue as duplicate of #162
#1 - c4-pre-sort
2023-09-08T16:13:42Z
raymondfam marked the issue as sufficient quality report
#2 - c4-judge
2023-09-17T06:47:27Z
kirk-baird marked the issue as satisfactory
#3 - c4-judge
2023-09-26T03:03:41Z
kirk-baird changed the severity to 2 (Med Risk)