President Elector

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

[M-1] Duplicate Candidate Ranking in Ranked-Choice Voting Allows Manipulation of Election Results

Summary

The _rankCandidates function does not prevent the voter for voting the same candidate multiple times

https://github.com/Cyfrin/2024-09-president-elector/blob/main/src/RankedChoice.sol#L159

function _rankCandidates(
address[] memory orderedCandidates,
address voter
) internal {
// Checks
if (orderedCandidates.length > MAX_CANDIDATES) {
revert RankedChoice__InvalidInput();
}
if (!_isInArray(VOTERS, voter)) {
revert RankedChoice__InvalidVoter();
}
// Internal Effects
s_rankings[voter][s_voteNumber] = orderedCandidates;
}

Vulnerability Details

Voters can submit rankings with the same candidate listed multiple times. This could result in a single candidate being "ranked" higher than they should be and potentially skew the results of the ranked-choice voting process.

Impact

Duplicate rankings could lead to erroneous vote tallies, where a candidate receives more "points" or votes than they should. The POC was made to demonstrate the vulnerability and it will be placed as function in RankedChoiceTest.t.sol. The testDuplicateCandidateRanking function simulates the scenario where a voter (voters[0]) ranks the same candidate twice. The test checks if the duplicate ranking was stored in the contract. If the vulnerability exists, the same candidate will appear twice in the ranking array. If the vulnerability is present, the second assertion (assertEq(rankedCandidates[1], candidates[0]);) will pass, indicating that duplicate candidates were accepted.

For demo please run command forge test --match-test testDuplicateCandidateRankingand the test will fail.

// Vulnerability validation: Checking if duplicate rankings are allowed
function testDuplicateCandidateRanking() public {
// Attempt to rank the same candidate multiple times
orderedCandidates = [candidates[0], candidates[0], candidates[2]];
vm.prank(voters[0]);
rankedChoice.rankCandidates(orderedCandidates);
// Validate that duplicate ranking was recorded
address[] memory rankedCandidates = rankedChoice.getUserCurrentVote(voters[0]);
// Duplicate should be recorded if vulnerability exists
assertEq(rankedCandidates[0], candidates[0]);
assertEq(rankedCandidates[1], candidates[0]); // This should not happen!
assertEq(rankedCandidates[2], candidates[2]);
}

Tools Used

Manual Review

Recommendations

Please add duplication check for preventing candidates getting ranked multiple times. It will check if a voter has ranked the same candidate more than once within their list.

function _rankCandidates(address[] memory orderedCandidates, address voter) internal {
// Checks
if (orderedCandidates.length > MAX_CANDIDATES) {
revert RankedChoice__InvalidInput();
}
if (!_isInArray(VOTERS, voter)) {
revert RankedChoice__InvalidVoter();
}
//Checking if all candidates ranking is unique and there are no duplicate candidates.
for (uint256 i l= 0; i < orderedCandidates.length; i++) {
for (uint256 j = i + 1; j < orderedCandidates.length; j++) {
if (orderedCandidates[i] == orderedCandidates[j]) {
revert RankedChoice__InvalidInput(); // Duplicate candidates
}
}
}
// Internal Effects
s_rankings[voter][s_voteNumber] = orderedCandidates;
}
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

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.