DoSed Voter::finalize() due to unbounded pending removals lacking a batch argument variable
Summary
Voter::finalize() processes in batches to allow finalizing epochs and distributing rewards without running out of gas. However, Voter::_processPendingRemovals() doesn't take a batch variable into account, making it possible for an attacker to register as auto voter with a balance > 0, and then transfer their votes out, being sent to the pending removals array as soon as the epoch is finalized. By spamming this in a huge number of accounts, an attacker is able to DoS epoch finalization for all epochs, effectively making all rewards stuck.
Root Cause
In Voter.sol:386, Voter::_processPendingRemovals() is missing a batch variable argument.
Internal Pre-conditions
None
External Pre-conditions
None
Attack Path
- Attacker calls
Voter::vote()with a wei balance, enabling auto voting, and then transferring the balance out. Attacker does this for multipler addresses such thatVoter::finalize()runs out of gas. - Admin calls
Voter::finalize(), and inVoter::_tallyAutoVotes(), all the accounts are pushed to the pendingRemovals array. Due to the lack of a batch argument in theVoter::_processPendingRemovals()function, it will run out of gas trying to remove all the addresses of the attacker.
Impact
Epoch finalization is impossible and rewards are forever stuck.
PoC
Note Voter.sol::_processPendingRemovals():
function _processPendingRemovals(uint256 ep) internal {
address[] memory toRemove = pendingRemovals[ep];
for (uint256 i = 0; i < toRemove.length; i++) {
address addr = toRemove[i];
uint256 idx = autoIndex[addr];
if (idx > 0) {
_removeAutoVoter(addr, idx - 1);
}
}
delete pendingRemovals[ep];
}Mitigation
Send a batch argument, to avoid OOG revert.