Lucene search

K
code423n4Code4renaCODE423N4:2023-07-AXELAR-FINDINGS-ISSUES-374
HistoryJul 21, 2023 - 12:00 a.m.

InterchainTokenService.expressReceiveTokenWithData does not apply Check-Effect-Interaction mode

2023-07-2100:00:00
Code4rena
github.com
4
interchaintokenservice
expressreceivetokenwithdata
vulnerability
fund loss
check-effect-interaction
malicious contract
token transfer

7.1 High

AI Score

Confidence

High

Lines of code

Vulnerability details

Impact

InterchainTokenService.expressReceiveTokenWithData does not apply the Check-Effect-Interaction pattern. In some edge cases, the caller can lose funds.

Proof of Concept

The life cycle of sending token across chains contains 3 phases:

//P1: launch on source chain
TokenManager.callContractWithInterchainToken
  interchainTokenService.transmitSendToken
    interchainTokenService._callContract
      gateway.callContract
        emit ContractCall
        
//P2: The off-chain program collects all events of the target chain, 
//builds the Command array, and operators sign for them. 
//Call AxelarGateway.execute of the target chain to set each command to be executed to true.
AxelarGateway.execute
  IAxelarAuth(AUTH_MODULE).validateProof
  //Loop through setting each command to true
  _setCommandExecuted(commandId, true);
  _setCommandExecuted(commandId, true);
  ......
  
//P3: The relayer calls InterchainTokenService.execute for each command, 
//or it can be called by the user himself.
InterchainTokenService.execute  //we only consider SELECTOR_SEND_TOKEN_WITH_DATA here.
  gateway.validateContractCall
  InterchainTokenService._execute
    _processSendTokenWithDataPayload
      _popExpressReceiveTokenWithData	
      tokenManager.giveToken
      destinationAddress.executeWithInterchainToken

expressReceiveTokenWithData uses the caller’s tokens to fullfill a callContractWithInterchainToken ahead of time. It is only called before the command’s execution status was set to true, due to this check. That is, before the completion of P2 in the above life cycle. It first transfers the token from the caller to destinationAddress, then calls destinationAddress.expressExecuteWithInterchainToken, and finally calls _setExpressReceiveTokenWithData to set the value in the corresponding slot to the caller address(called expressCaller).Such a process is actually the Check-Interaction-Effect mode. destinationAddress can be a malicious contract, and it is very dangerous to interact with the malicious contract before setting the slot.

Suppose the following scenario:

  1. Bob deploys a malicious contract A and calls TokenManager.callContractWithInterchainToken to send 100e18 token.

  2. Alice’s program periodically (every 1 minute) checks whether there is an outgoing sendToken. No such events were detected this time.

  3. When the off-chain program calls AxelarGateway.execute, this tx1 enters the memory pool, pending.

  4. Alice’s program checks again. This time the command initiated by contract A in step 1 is detected. Therefore, call InterchainTokenService.expressReceiveTokenWithData. tx2 enters the mempool.

  5. Bob notices the parameter input of tx1. Call A.setParram to submit this parameter to contract A (higher gas). tx3 enters the memory pool.

  6. tx3 is prioritized due to higher gas payment.

  7. When tx2 is executed, expressReceiveTokenWithData first transfers the token of 100e18 from alice to A, and then calls A.expressExecuteWithInterchainToken, where there are 2 parts.

* AxelarGateway.execute(input), this input is obtained in step 5. This is to [set all commands](<https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/cgp/AxelarGateway.sol#L372-L377>) contained in input as executable.
* InterchainTokenService.execute executes the command initiated in step 1. Since [expressCaller](https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/its/interchain-token-service/InterchainTokenService.sol#L643>) has not been set at this time, 100e18 token is sent to A [here](<https://github.com/code-423n4/2023-07-axelar/blob/main/contracts/its/interchain-token-service/InterchainTokenService.sol#L657).

Execution flow returns here, where expressCaller is set to alice. However, alice will never be able to obtain the 100e18 token. A gets 200e18 tokens.

  1. tx1 is executed without affecting the result.

In summary, A gets an extra 100e18, which belongs to alice. This scenario is not common. It’s an edge case.

Tools Used

Manual Review

Recommended Mitigation Steps

File: contracts\its\interchain-token-service\InterchainTokenService.sol
467:     function expressReceiveTokenWithData(
468:         bytes32 tokenId,
469:         string memory sourceChain,
470:         bytes memory sourceAddress,
471:         address destinationAddress,
472:         uint256 amount,
473:         bytes calldata data,
474:         bytes32 commandId
475:     ) external {
476:         if (gateway.isCommandExecuted(commandId)) revert AlreadyExecuted(commandId);
477: 
478:         address caller = msg.sender;
479:         ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId));
480:         IERC20 token = IERC20(tokenManager.tokenAddress());
481: 
482:         SafeTokenTransferFrom.safeTransferFrom(token, caller, destinationAddress, amount);
483:         
484:-        _expressExecuteWithInterchainTokenToken(tokenId, destinationAddress, sourceChain, sourceAddress, data, amount);
484:+        _setExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, caller);
485: 
486:-        _setExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, caller);
486:+        _expressExecuteWithInterchainTokenToken(tokenId, destinationAddress, sourceChain, sourceAddress, data, amount);
487:     }

Assessed type

Reentrancy


The text was updated successfully, but these errors were encountered:

All reactions

7.1 High

AI Score

Confidence

High