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