code4rena-2023-11-party-dao-m07
[M-07] ETHCrowdfundBase.sol._processContribution - Possible DoS on finalization of crowdfund under certain conditions
Summary
투자금이 너무 작아 투자로 추가되는 투표권이 0이면 투자 컨트랙트가 취소된다. 최대 가능 투자금과 최소 목표 투자금이 동일한 특수 상황에, 추가로 투자 가능한 금액을 적게 남기면 나눗셈의 라운딩으로 인해 마지막 투자자가 얻을 투표권이 0이 되도록 강제할 수 있다. 최대 가능 펀딩 금액을 넘어가기 때문에 더이상 투자가 불가하지만 최소 목표 투자금을 만족하지 못해 펀딩을 완료할 수도 없다. 이 점을 이용하여 DoS가 가능하다.
Keyword
arithmetic error, rounding error, precision, dos
Vulnerability
크라우드 펀딩 투자를 할 때, 펀딩으로 인해 추가되는 투표권이 0개이면 펀딩 트랜잭션이 취소된다. votingPower = (amount * exchangeRateBps) / 1e4 로 계산되기에 exchangeRateBps < 1e4 인 값으로 설정되었다면 나누기 계산에서 라운딩으로 votingPower의 값이 0이 될 수 있다.
만약 펀딩한 결과 전체 펀딩 금액이 최대 펀딩 금액을 넘어간다면, 차액을 돌려주고 최대 금액까지 남은 금액만큼만 투자금으로 친다. 이 투자금으로 치는 양이 votingPower = (amount * exchangeRateBps) / 1e4에서 버림으로 인해 0이 될만큼 작다면 해당 트랜잭션은 취소된다.
function _processContribution(
address payable contributor,
address delegate,
uint96 amount
) internal returns (uint96 votingPower) {
...
@> if (newTotalContributions >= maxTotalContributions_) {
totalContributions = maxTotalContributions_;
// Finalize the crowdfund.
// This occurs before refunding excess contribution to act as a
// reentrancy guard.
_finalize(maxTotalContributions_);
// Refund excess contribution.
uint96 refundAmount = newTotalContributions - maxTotalContributions;
if (refundAmount > 0) {
@> amount -= refundAmount;
payable(msg.sender).transferEth(refundAmount);
}
} else {
totalContributions = newTotalContributions;
}
...
// Calculate voting power.
@> votingPower = (amount * exchangeRateBps) / 1e4;
@> if (votingPower == 0) revert ZeroVotingPowerError();
}최대 가능 투자금이 최소 목표 투자금보다 큰 일반적인 상황에서는 문제가 되지 않는다. 하지만 최대 가능 투자금과 최소 목표 투자금이 동일한(maxTotalContributions == minTotalContributions) 특수 상황에서 문제가 발생한다. 최소 목표 투자금에 도달하여야 펀딩이 성공하는데, 최대 가능 투자금으로 인해 마지막 투자자의 amount가 작아지고, 이로 인해 votingPower가 0이 되면 마지막 투자자가 투자를 할 수 없다. 마지막 투자자가 투자하지 못하면 최소 목표에 도달하지 못하기 때문에 펀딩은 실패한다.
펀딩 시작 시 minTotalContributions 가 큰지만 확인하므로 최대 가능 투자금과 최소 목표 투자금을 동일하게 설정하는 것은 가능하다.
ETHCrowdfundBase.sol#_initialize
...
// Set the min total contributions.
@> if (opts.minTotalContributions > opts.maxTotalContributions) { // The check isn't strict enough
revert MinGreaterThanMaxError(opts.minTotalContributions, opts.maxTotalContributions);
}크라우드 펀딩에 실패하면 유저는 환불을 요청할 수 있다. minTotalContribution에 도달하지 못했기 때문에 크라우드 펀딩을 조기 종료할 수 없으므로 유저가 환불받기 위해서는 종료 기간까지 기다려야 한다. 이를 통해 DoS가 가능하다.
Impact
DoS, 특수한 조건에 투자가 성공할 수 없도록 강제할 수 있다. 유저가 환불받기 위해 펀딩 종료일까지 기다리게 한다.
Mitigation
maxTotalContributions와 minTotalContributions 를 동일하게 설정할 수 없게 한다. exchangeRateBps < 1e4 로 강제하여 나눗셈 라운딩을 막는다.
tags: bughunting, party dao, smart contract, solidity, rounding error, arithmetic error, dos, severity medium