code4rena-2024-08-chakra-h11

[H-11] There is no refund mechanism in ChakraSettlement.processCrossChainCallback or ChakraSettlementHandler.receive_cross_chain_callback function

보고서

Summary

목적지에서 여러 이유로 브릿지 작업을 실패했을 때, 출발지에서 이미 소각 또는 전송했던 토큰을 돌려받을 수 있는 방법이 없다. 따라서 유저는 토큰을 잃게 된다.

Keyword

business logic vul, asset lock, cross chain, bridge

Vulnerability

Settlement.receive_cross_chain_msg 는 서명을 확인한 뒤 목적지 핸들러에게 메시지를 넘긴다. 만약 핸들러에서 핸들링할 수 없을 때는 false 를 리턴하여 출발지 체인에게 실패를 알리고 처리하도록 한다.

function receive_cross_chain_msg(
    uint256 txid,
    string memory from_chain,
    uint256 from_address,
    uint256 from_handler,
    address to_handler,
    PayloadType payload_type,
    bytes calldata payload,
    uint8 sign_type, // validators signature type /  multisig or bls sr25519
    bytes calldata signatures // signature array
) external {
    {
        // verify signature
        bytes32 message_hash = keccak256(
            abi.encodePacked(
                txid,
                from_chain,
                from_address,
                from_handler,
                to_handler,
                keccak256(payload)
            )
        );
 
        require(
            signature_verifier.verify(message_hash, signatures, sign_type),
            "Invalid signature"
        );
 
        require(
            receive_cross_txs[txid].status == CrossChainMsgStatus.Unknow,
            "Invalid transaction status"
        );
    }
 
    receive_cross_txs[txid] = ReceivedCrossChainTx(
        txid,
        from_chain,
        contract_chain_name,
        from_address,
        from_handler,
        address(this),
        payload,
        CrossChainMsgStatus.Pending
    );
 
@>  bool result = ISettlementHandler(to_handler).receive_cross_chain_msg(
        txid,
        from_chain,
        from_address,
        from_handler,
        payload_type,
        payload,
        sign_type,
        signatures
    );
 
    CrossChainMsgStatus status = CrossChainMsgStatus.Failed;
    if (result == true) {
        status = CrossChainMsgStatus.Success;
        receive_cross_txs[txid].status = CrossChainMsgStatus.Success;
    } else {
@>      receive_cross_txs[txid].status = CrossChainMsgStatus.Failed;
    }
 
    emit CrossChainHandleResult(
        txid,
@>      status,
        contract_chain_name,
        from_chain,
        address(to_handler),
        from_handler,
        payload_type
    );
}

그런데 low level call 또는 try-catch를 사용하지 않으므로 컨트랙트 주소가 유효하지 않아 컨트랙트 콜이 실패한다면 트랜잭션이 revert 된다. 트랜잭션이 revert 되면 이벤트를 발생시킬 수 없어 실패를 알릴 수 없다. 또한 모종의 이유로 핸들러에서 트랜잭션이 revert 되었을 때 출발지 체인에게 실패를 알릴 수 없다. 또한 화이트리스트에서 빠지는 등의 이유로도 실패할 수 있으며, 이 때 이미 출발지에서 소각 또는 이동된 토큰을 돌려받을 수 없다.

동일한 문제가 Cairo 컨트랙트에도 존재한다. to_handler 가 유효하지 않은 주소이거나 핸들러에서 revert 된다면 Settlement.receive_cross_chain_msg 가 실패하고, 이벤트를 발생시킬 수 없어 실패를 알릴 수 없다.

  fn receive_cross_chain_msg(
      ref self: ContractState,
      cross_chain_msg_id: u256,
      from_chain: felt252,
      to_chain: felt252,
      from_handler: u256,
      to_handler: ContractAddress,
      sign_type: u8,
      signatures: Array<(felt252, felt252, bool)>,
      payload: Array<u8>,
      payload_type: u8,
  ) -> bool {
      assert(to_chain == self.chain_name.read(), 'error to_chain');
 
      // verify signatures
      let mut message_hash: felt252 = LegacyHash::hash(from_chain, (cross_chain_msg_id, to_chain, from_handler, to_handler));
      let payload_span = payload.span();
      let mut i = 0;
      loop {
          if i > payload_span.len()-1{
              break;
          }
          message_hash = LegacyHash::hash(message_hash, * payload_span.at(i));
          i += 1;
      };
      self.check_chakra_signatures(message_hash, signatures);
 
      // call handler receive_cross_chain_msg
      let handler = IHandlerDispatcher{contract_address: to_handler};
@>    let success = handler.receive_cross_chain_msg(cross_chain_msg_id, from_chain, to_chain, from_handler, to_handler , payload);
 
      let mut status = CrossChainMsgStatus::SUCCESS;
      if success{
          status = CrossChainMsgStatus::SUCCESS;
      }else{
@>        status = CrossChainMsgStatus::FAILED;
      }
 
      self.received_tx.write(cross_chain_msg_id, ReceivedTx{
          tx_id:cross_chain_msg_id,
          from_chain: from_chain,
          from_handler: from_handler,
          to_chain: to_chain,
          to_handler: to_handler,
          tx_status: status
      });
 
      // emit event
      self.emit(CrossChainHandleResult{
          cross_chain_settlement_id: cross_chain_msg_id,
          from_chain: to_chain,
          from_handler: to_handler,
          to_chain: from_chain,
          to_handler: from_handler,
@>        cross_chain_msg_status: status,
          payload_type: payload_type
      });
      return true;
  }

Impact

목적지 체인에서의 실패를 핸들링할 수 없어 유저가 토큰을 잃게 된다.

Mitigation

목적지 체인에서 실패 시 출발지 체인에서 지불했던 토큰을 환불할 수 있는 로직을 갖춘다.


tags: bughunting, chakra, smart contract, starknet, cairo, solidity, bridge, cross chain, asset lock, business-logic-vul, severity high