code4rena-2023-09-ondo-m03
[M-03] Two different transactions can result in the same txnHash value, thus breaking the approval process of transaction minting
Summary
각 브릿징 요청을 구분하는 트랜잭션 해시값에 포함되는 값 중 출발지 체인에 대한 값은 없다. 따라서 여러 체인에서 동일한 파라미터를 보내면 동일한 트랜잭션 해시값으로 계산된다. 이로 인해 기존에 승인을 기다리고 있던 타 체인에서의 브릿징 요청이 덮어써질 수 있다.
Keyword
lack of input validation, logic flaw, bug, collision, bridge
Vulnerability
- bridge/DestinationBridge.sol#L108-L109
- bridge/DestinationBridge.sol#L137-L140
- bridge/DestinationBridge.sol#L90-L91
목적지 체인에서 메시지를 처리할 때 DestinationBridge._execute 함수를 호출한다. 이 때 각 요청을 구분하는 txnHash는 출발지 체인으로부터 넘어온 페이로드의 해시값이다.
출발지 체인에서 넘어오는 페이로드는 VERSION, msg.sender, amount, nonce 로 구성된다. (bytes memory payload = abi.encode(VERSION, msg.sender, amount, nonce++);)
function _execute(
string calldata srcChain,
string calldata srcAddr,
bytes calldata payload
) internal override whenNotPaused {
(bytes32 version, address srcSender, uint256 amt, uint256 nonce) = abi
.decode(payload, (bytes32, address, uint256, uint256));
if (version != VERSION) {
revert InvalidVersion();
}
if (chainToApprovedSender[srcChain] == bytes32(0)) {
revert ChainNotSupported();
}
if (chainToApprovedSender[srcChain] != keccak256(abi.encode(srcAddr))) {
revert SourceNotSupported();
}
if (isSpentNonce[chainToApprovedSender[srcChain]][nonce]) {
revert NonceSpent();
}
isSpentNonce[chainToApprovedSender[srcChain]][nonce] = true;
@> bytes32 txnHash = keccak256(payload);
@> txnHashToTransaction[txnHash] = Transaction(srcSender, amt);
@> _attachThreshold(amt, txnHash, srcChain);
_approve(txnHash);
_mintIfThresholdMet(txnHash);
emit MessageReceived(srcChain, srcSender, amt, nonce);
}
문제는 페이로드에는 출발지 체인에 대한 정보가 없다는 점이다. 서로 다른 출발지 체인에서 동일한 계정으로 호출, 이동하는 토큰 양과 nonce가 일치한다면 txnHash 역시 일치한다. 따라서 다른 체인에서 브릿징된 txnToThresholdSet를 덮어쓰고, 결과적으로 브릿징 승인 과정이 망가질 것이다. (브릿징 시 바로 토큰이 민팅되는 게 아니라 approver로부터 확인을 받아야 한다. txnHash가 동일하므로 기존 요청이 덮어써져 사라질 것이다.)
function _attachThreshold(
uint256 amount,
bytes32 txnHash,
string memory srcChain
) internal {
Threshold[] memory thresholds = chainToThresholds[srcChain];
for (uint256 i = 0; i < thresholds.length; ++i) {
Threshold memory t = thresholds[i];
if (amount <= t.amount) {
txnToThresholdSet[txnHash] = TxnThreshold(
t.numberOfApprovalsNeeded,
new address[](0)
);
break;
}
}
if (txnToThresholdSet[txnHash].numberOfApprovalsNeeded == 0) {
revert NoThresholdMatch();
}
}Impact
승인을 기다리고 있던 타 체인에서의 브릿징 요청이 덮어써진다.
Mitigation
txnHash를 계산하기 위해 해싱되는 페이로드에 출발지 체인과 출발지 체인 컨트랙트 주소를 포함한다. 이렇게 하면 서로 다른 체인에서 발생한 서로 다른 트랜잭션이 동일한 txnHash로 생성되지 않는다.
tags: bughunting, ondo finance, smart contract, solidity, bridge, lack-of-input-validation-vul, collision, logic flaw, severity medium