Inconsistent Index Management in removeVoters Function #333
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Vulnerability Summary
The removeVoters function in the ParanetNeuroIncentivesPool contract fails to properly update the votersIndexes mapping when voters are removed from the voters array. This oversight can lead to incorrect data access, logic errors, and potential exploits due to stale or incorrect index references.
Vulnerable Code
https://github.com/OriginTrail/dkg-evm-module/blob/main/contracts/paranets/ParanetNeuroIncentivesPool.sol
function removeVoters(uint256 limit) external onlyVotersRegistrar {
require(voters.length >= limit, "Limit exceeds the num of voters");
for (uint256 i; i < limit; ) {
cumulativeVotersWeight -= uint16(voters[voters.length - 1 - i].weight);
}
}
Detailed Description
The removeVoters function is intended to remove a specified number of voters from the voters array. However, the function only deletes the mapping entry for the removed voter without updating the indexes of the remaining voters. This can lead to several issues:
Stale Indexes: When a voter is removed, the indexes of the remaining voters in the votersIndexes mapping are not updated. This means that the mapping may point to incorrect positions, leading to data retrieval errors.
Logic Errors: Functions that rely on the votersIndexes mapping to access voter data may operate on incorrect data, leading to unintended behavior or incorrect reward calculations.
Potential Exploits: An attacker could exploit this inconsistency to manipulate the contract's state or claim rewards they are not entitled to, by creating scenarios where the contract logic fails due to incorrect indexing.
Maintenance Challenges: The inconsistency between the voters array and the votersIndexes mapping makes the contract harder to maintain and debug, as the actual state may not match the expected state based on the mappings.
Impact
Data Inconsistency: Incorrect index references can lead to data retrieval errors and logic failures.
Incorrect Logic Execution: Functions relying on the mapping may execute incorrectly, leading to potential financial discrepancies.
Potential Exploits: The inconsistency can be exploited to perform unauthorized actions or claim rewards.
Maintenance and Debugging Issues: The contract becomes more challenging to maintain and debug due to inconsistent state representation.
Severity
critical: it can cause significant operational issues and potential exploits if not addressed. The severity is critical due to the potential for incorrect logic execution and data inconsistency.
Proof of Concept (PoC) Using Foundry's Forge
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/ParanetNeuroIncentivesPool.sol"; // Adjust the import path as necessary
contract ParanetNeuroIncentivesPoolTest is Test {
ParanetNeuroIncentivesPool pool;
function setUp() public {
// Initialize the contract with dummy data
pool = new ParanetNeuroIncentivesPool(
address(this), // hubAddress
address(this), // paranetsRegistryAddress
address(this), // knowledgeMinersRegistryAddress
bytes32(0), // paranetId
1e12, // tracToNeuroEmissionMultiplier
1000, // paranetOperatorRewardPercentage
1000 // paranetIncentivizationProposalVotersRewardPercentage
);
}
function testVoterRemovalIssue() public {
// Remove a voter
pool.removeVoters(1);
}
}
Recommended Fix
To address the issue, update the votersIndexes mapping whenever the voters array is modified. Here's a suggested fix for the removeVoters function:
function removeVoters(uint256 limit) external onlyVotersRegistrar {
require(voters.length >= limit, "Limit exceeds the num of voters");
for (uint256 i; i < limit; ) {
uint256 lastIndex = voters.length - 1 - i;
address voterToRemove = voters[lastIndex].addr;
}
}
This fix ensures that when a voter is removed, the last voter in the array is moved to the removed voter's position, and the votersIndexes mapping is updated accordingly. This maintains consistency between the array and the mapping, preventing stale or incorrect index references.