President Elector

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

Denial of Service (DoS) Vulnerability in _selectPresidentRecursive Function

Summary

The _selectPresidentRecursive function used in the ranked choice voting contract is vulnerable to a Denial of Service (DoS) attack due to inefficient handling of recursive calls and large candidate lists. The recursive structure, combined with nested loops, can lead to gas exhaustion, which may prevent the selectPresident() function from successfully completing and selecting a new president.

Vulnerability Details

The _selectPresidentRecursive function is designed to eliminate candidates with the fewest votes in each round until only one candidate remains. However, the use of recursion and nested loops makes the function highly susceptible to gas exhaustion, particularly when there is a large number of voters and candidates.

The key issues contributing to the vulnerability are:

  1. Recursion Depth:

    • The function calls itself recursively for each round of candidate elimination. In each round, one candidate is eliminated, and the function is called again with a reduced candidate list.

    • If the number of candidates is large, this recursive process can exhaust the available gas, resulting in the function failing to complete.

  2. Inefficient Looping:

    • The outer loop iterates over all voters, and the inner loop iterates over each voter's ranking of candidates. This nested loop structure is inefficient and can consume significant amounts of gas when the number of voters or ranked candidates is large.

  3. No Gas Optimization:

    • There are no gas-saving mechanisms (such as batching or limiting the number of candidates processed in a single transaction), which increases the likelihood of running out of gas as the system scales.

function _selectPresidentRecursive(
address[] memory candidateList,
uint256 roundNumber
) internal returns (address[] memory) {
if (candidateList.length == 1) {
return candidateList;
}
// @audit DDOS
// Tally up the picks
for (uint256 i = 0; i < VOTERS.length; i++) {
for (
uint256 j = 0;
j < s_rankings[VOTERS[i]][s_voteNumber].length;
j++
) {
address candidate = s_rankings[VOTERS[i]][s_voteNumber][j];
if (_isInArray(candidateList, candidate)) {
s_candidateVotesByRound[candidate][s_voteNumber][
roundNumber
] += 1;
break;
} else {
continue;
}
}
}
// Remove the lowest candidate or break
address fewestVotesCandidate = candidateList[0];
uint256 fewestVotes = s_candidateVotesByRound[fewestVotesCandidate][
s_voteNumber
][roundNumber];
for (uint256 i = 1; i < candidateList.length; i++) {
uint256 votes = s_candidateVotesByRound[candidateList[i]][
s_voteNumber
][roundNumber];
if (votes < fewestVotes) {
fewestVotes = votes;
fewestVotesCandidate = candidateList[i];
}
}
address[] memory newCandidateList = new address[](
candidateList.length - 1
);
bool passedCandidate = false;
for (uint256 i; i < candidateList.length; i++) {
if (passedCandidate) {
newCandidateList[i - 1] = candidateList[i];
} else if (candidateList[i] == fewestVotesCandidate) {
passedCandidate = true;
} else {
newCandidateList[i] = candidateList[i];
}
}
return _selectPresidentRecursive(newCandidateList, roundNumber + 1);
}

Impact

The selectPresident() function is a critical part of the contract's functionality, and gas exhaustion in _selectPresidentRecursive can lead to the function being unusable. As a result, the voting process could be blocked, leaving the contract in a state where no new president can be selected.

This creates a DoS scenario where users can no longer interact with the contract as intended, undermining the core purpose of the voting system.

Tools Used

Manual Review

Recommendations

Replace Recursive Calls with Iterative Logic:

Refactor the _selectPresidentRecursive function to use an iterative process instead of recursion. This will prevent the risk of running out of gas due to deep recursion.

Optimize Looping:

Optimize the nested loop structure to reduce gas consumption. Consider using mappings or other data structures to avoid iterating through large arrays multiple times.

Implement gas-efficient mechanisms to tally votes, such as batch processing or limiting the number of candidates processed in a single transaction.

Introduce Batching:

Implement a system where candidate elimination can be broken down into multiple transactions, allowing the process to continue across multiple blocks without exhausting gas in a single transaction.

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 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.