President Elector

First Flight #24
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Invalid

Malicious voters can manipulate the results by repeatedly ranking the same candidate including address(0)

Summary

By passing duplicate rankings within the orderedCandidates array, since there's no check to prevent the same candidate from being ranked multiple times by the same voter, malicious voters can easily exploit this to amplify their votes. This could also be done by passing address(0) as a candidate. All this would give disproportionate weight to the chosen candidate.

Vulnerability Details

The recursive election process will tally votes based on how often a candidate appears in a voter's ranked list. Since the malicious voters have submitted the same candidate address for example 10 times, that candidate will get counted 10 times for each malicious voter. In the for loop, the system checks each ranking in s_rankings[VOTERS[i]][s_voteNumber] and increments the candidate’s votes if they are in the candidateList. Because the malicious voters have ranked the same candidate repeatedly, the candidate’s vote count will increase significantly compared to other candidates, artificially boosting their chances of winning.

Impact

This creates an unfair voting outcome, as the malicious voters essentially inflate the vote tally for their preferred candidate. Even though the honest voters are submitting reasonable rankings, the malicious voters manipulate the results by repeatedly ranking the same candidate, potentially leading to a skewed election result.

Additionally, passing address(0) as a candidate the contract could end up counting votes for the zero address, which is not a real candidate. This would skew the election results by including invalid votes and it could end up as the elected "candidate," which would break the election logic, as the zero address is not a valid participant.

POC

Paste the following code in the existing test suite, proving both duplicate and address(0) could be passed:

function test_PassingAddrZeroAsVote() public {
orderedCandidates = [candidates[0], candidates[1], candidates[2]];
vm.startPrank(voters[0]);
rankedChoice.rankCandidates(orderedCandidates);
vm.stopPrank();
orderedCandidates = [candidates[1], candidates[2], candidates[5]];
vm.startPrank(voters[1]);
rankedChoice.rankCandidates(orderedCandidates);
vm.stopPrank();
orderedCandidates = [candidates[3], candidates[4], candidates[8]];
vm.startPrank(voters[3]);
rankedChoice.rankCandidates(orderedCandidates);
vm.stopPrank();
orderedCandidates = [candidates[4], candidates[4], candidates[4]];
vm.startPrank(voters[4]);
rankedChoice.rankCandidates(orderedCandidates);
vm.stopPrank();
orderedCandidates = [address(0), address(0), address(0)];
vm.startPrank(voters[2]);
rankedChoice.rankCandidates(orderedCandidates);
vm.stopPrank();
orderedCandidates = [address(0), address(0), address(0)];
vm.startPrank(voters[6]);
rankedChoice.rankCandidates(orderedCandidates);
vm.stopPrank();
orderedCandidates = [address(0), address(0), address(0)];
vm.startPrank(voters[7]);
rankedChoice.rankCandidates(orderedCandidates);
vm.stopPrank();
vm.warp(block.timestamp + rankedChoice.getDuration());
rankedChoice.selectPresident();
assertEq(rankedChoice.getCurrentPresident(), address(0));
}

Tools Used

Manual Review, Foundry

Recommendations

Validate whether the passed candidate is not the address(0) by adding this check in the _rankCandidatesfunction.

for (uint256 i = 0; i < orderedCandidates.length; i++) {
require(orderedCandidates[i] != address(0), "Invalid candidate: zero address");
}

and ensure that is a proper check for duplicates in the passed candidates for example:

function _validateUniqueCandidates(address[] memory orderedCandidates) internal pure {
for (uint256 i = 0; i < orderedCandidates.length; i++) {
for (uint256 j = i + 1; j < orderedCandidates.length; j++) {
require(orderedCandidates[i] != orderedCandidates[j], "Duplicate candidate detected.");
}
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

rankCandidates() allows duplicate votes inside the `orderedCandidates` array

Appeal created

strapontin Auditor
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

rankCandidates() allows duplicate votes inside the `orderedCandidates` array

Support

FAQs

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