code-423n4 / 2023-09-ondo-findings

7 stars 5 forks source link

`DestinationBridge` contract: transactions can be executed even if the contract is paused #88

Open code423n4 opened 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/bridge/DestinationBridge.sol#L197-L203 https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/bridge/DestinationBridge.sol#L156-L167 https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/bridge/DestinationBridge.sol#L337-L353

Vulnerability details

Impact

Proof of Concept

  1. test_Transaction_Executed_When_Contract_Is_Paused() test is added to forge-tests/bridges/DestinationBridge.t.sol file; with the following scenario:

    • a relayer initiates a transaction execution when the contract is not paused.
    • then the DestinationBridge contract owner pauses the contract.
    • approvers approve the tranaction until it gets executed (bridged tokens minted to the user).
    function test_Transaction_Executed_When_Contract_Is_Paused() public {
    string memory srcChain = "arbitrum";
    string memory srcAddress = "0xce16F69375520ab01377ce7B88f5BA8C48F8D666";
    address srcSender = alice;
    uint256 amt = 100e18;
    address nonApprover = address(500);
    
    //2. a transaction of 100e18 amount is initiated and then executed by the nonApprover as it need one approver only:
    assertEq(destinationBridge.approvers(nonApprover), false);
    assertEq(usdy.balanceOf(srcSender), 0); //the srcSender doesnt have any usdc on the destination chain befor transaction execution
    
    bytes32 cmdId = bytes32(
      0x9991faa1f435675159ffae64b66d7ecfdb55c29755869a18db8497b4392347e0
    );
    uint256 nonce = 1;
    bytes memory payload = abi.encode(VERSION, srcSender, amt, nonce);
    approve_message(
      cmdId,
      srcChain,
      srcAddress,
      address(destinationBridge),
      keccak256(payload)
    );
    destinationBridge.execute(cmdId, srcChain, srcAddress, payload);
    
    //2. Then the admin pauses the destination bridge contract :
    assertEq(destinationBridge.paused(), false);
    vm.prank(guardian);
    destinationBridge.pause();
    assertEq(destinationBridge.paused(), true);
    
    //3. the transaction needs two approvals; the first approval is given by the relayer who initiated the transaction execution,and the second approval is given by the approved ondo signer:
    vm.prank(ondoSigner0);
    destinationBridge.approve(keccak256(payload));
    assertEq(destinationBridge.getNumApproved(keccak256(payload)), 2);
    
    //4. the transaction executed :
    assertEq(usdy.balanceOf(srcSender), amt);
    }
  2. Test result:

    $ forge test --fork-url $(grep -w MAINNET_RPC_URL .env | cut -d '=' -f2) --fork-block-number $(grep -w FORK_FROM_BLOCK_NUMBER_MAINNET .env | cut -d '=' -f2) --nmc Test_Prod --mc '(Test_DestinationBridge)'
    [PASS] test_Transaction_Executed_When_Contract_Is_Paused() (gas: 306194)

Tools Used

Manual Testing & Foundry.

Recommended Mitigation Steps

Update approve function to be called only when the contract is not paused:

- function approve(bytes32 txnHash) external {
+ function approve(bytes32 txnHash) external whenNotPaused {

Assessed type

Context

c4-pre-sort commented 1 year ago

raymondfam marked the issue as duplicate of #529

c4-pre-sort commented 1 year ago

raymondfam marked the issue as low quality report

c4-judge commented 1 year ago

kirk-baird marked the issue as unsatisfactory: Invalid

c4-judge commented 1 year ago

kirk-baird changed the severity to QA (Quality Assurance)

c4-judge commented 1 year ago

kirk-baird marked the issue as grade-c

c4-judge commented 1 year ago

kirk-baird marked the issue as grade-b