DeFiFoundry
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

Missing access control in ```FjordPoints::distributePoints``` leading to Dos attack

Summary

The FjordPoints::distributePoints is marked as public and there are no access control modifiers restricting who can call this function, allowing anyone to call it. While the function includes checks to return early if certain conditions are not met (the points are only distributed if the appropriate amount of time has passed EPOCH_DURATION and if there are tokens stakedtotalStaked > 0), it does not prevent repeated calls. An attacker can exploit this by repeatedly calling the function, consuming gas, leading to a Denial of Service (DoS) attack.

Link: https://github.com/Cyfrin/2024-08-fjord/blob/0312fa9dca29fa7ed9fc432fdcd05545b736575d/src/FjordPoints.sol#L232C5-L248C6

Vulnerability Details

The lack of access control in the distributePoints function allows any external account or contract to call it repeatedly. This can result in unnecessary gas consumption, as each call, even if it returns early. Over time, this exhausts the gas limit for the block, making it difficult or expensive for legitimate users to interact with the contract.

@> function distributePoints() public {
if (block.timestamp < lastDistribution + EPOCH_DURATION) {
return;
}
if (totalStaked == 0) {
return;
}
uint256 weeksPending = (block.timestamp - lastDistribution) / EPOCH_DURATION;
pointsPerToken =
pointsPerToken.add(weeksPending * (pointsPerEpoch.mul(PRECISION_18).div(totalStaked)));
totalPoints = totalPoints.add(pointsPerEpoch * weeksPending);
lastDistribution = lastDistribution + (weeksPending * 1 weeks);
emit PointsDistributed(pointsPerEpoch, pointsPerToken);
}

Impact

Add this test in the points.t.sol file and

run: forge test --match-test test_NoAccessControl_DistributePoints -vv

function test_NoAccessControl_DistributePoints() public {
address alice = makeAddr("alice");
vm.startPrank(address(alice));
skip(1 weeks);
uint256 initialGasLeft = gasleft();
//Repeated call of distributePoints
for (uint256 i = 0; i < 25_000; i++) {
fjordPoints.distributePoints();
}
uint256 finalGasLeft = gasleft();
uint256 gasUsed = initialGasLeft - finalGasLeft;
console2.log("Gas used: ", gasUsed);
//Ethereum Block gas Limit
uint256 GAS_LIMIT = 30_000_000;
assert(gasUsed > GAS_LIMIT);
assertEq(fjordPoints.pointsPerToken(), 0);
vm.stopPrank();
}
Ran 1 test for test/unit/points.t.sol:TestFjordPoints
[PASS] test_NoAccessControl_DistributePoints() (gas: 30370671)
Logs:
Gas used: 30358542
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 76.57ms (71.44ms CPU time)

The test shows that after 25.000 calls, the gas limit block is passed.

Although the function includes checks to return early if the conditions are not met, an attacker could still repeatedly call the function to consume gas and disrupt operations.

Likelihood: medium

Impact: medium/high

Tools Used

Manual review

Recommendations

Implement a modifier that restricts the function to be callable only by certain authorized address/addresses.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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