Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Unbounded Loop through the teachers array becomes a potential DoS vector.

Summary:

The Levelone::graduateAndUpgrade function loops through the listOfTeachers array in order to initiate payPerTeacher invariant. However, if the listOfTeacher array becomes large, the more expensive the gas cost is and this might cause a gas block limit and might cause the entire transaction to revert.

Vulnerability Details:

This vulnerability is most likely to happen if the principal address is compromised in a number of ways, the method i am focused on is when a malicious actor deploys or upgrades a contract and assigns themselves as principal and inflate the listOfTeachers intentionally to brick the contract.

Impact

The vulnerability will cause the upgrade to be blocked and the contract become stuck in Levelone. Furthermore, funds will be locked because bursary needed to be distributed in the same transaction.

Proof of Concept:

If we have 2 sets of teachers; their gas cost becomes

Initial teachers(2) = 124,202

Inflated teachers (100) = 5,112,844

the gas cost for inflated teachers will be a lot higher than that of the initial teachers, because of the for loop.

```javascript
@> for (uint256 n = 0; n < totalTeachers; n++) {

usdc.safeTransfer(listOfTeachers[n], payPerTeacher);

}
```

Here the attacker or rouge principal can cause a DoS when Inflate the listOfTeacher array with a 100 addresses, and then call the safeTransfer function which will cost the transaction a lot of gas cost. Hereby, breaking the functionality of the contract.

Proof of Code:


<details>

<summary>PoC</summary>

place the following test into LevelOneAndGraduateTest

function test_DenialOfService() public {
vm.txGasPrice(1);
address realPrincipal = levelOneProxy.principal();
uint256 teachersNumFirst = 2;
address[] memory listOfTeachers = new address[](teachersNumFirst);
for (uint256 i = 0; i < teachersNumFirst; i++) {
listOfTeachers[i] = address(uint160(i + 1));
}
uint256 gasStart = gasleft();
for (uint256 i = 0; i < teachersNumFirst; i++) {
vm.prank(realPrincipal);
levelOneProxy.addTeacher(listOfTeachers[i]);
}
uint256 gasEnd = gasleft();
uint256 gasUsedFirst = (gasStart - gasEnd) * tx.gasprice;
console2.log("gas cost of first 2 teachers: ", gasUsedFirst);
uint256 teachersNumSecond = 100;
address[] memory listOfTeacherstwo = new address[](teachersNumSecond);
for (uint256 i = 0; i < teachersNumSecond; i++) {
listOfTeacherstwo[i] = address(uint160(i + teachersNumFirst + 1));
}
uint256 gasStartSecond = gasleft();
for (uint256 i = 0; i < teachersNumSecond; i++) {
vm.prank(realPrincipal);
levelOneProxy.addTeacher(listOfTeacherstwo[i]);
}
uint256 gasEndSecond = gasleft();
uint256 gasUsedSecond = (gasStartSecond - gasEndSecond) * tx.gasprice;
console2.log("gas cost of second 100 teachers: ", gasUsedSecond);
assert(gasUsedFirst < gasUsedSecond);
}

</details>


Tools Used

Recommendations:

  1. Implementing a hard limit of teachers in the listOfTeachers array

    javascript````require(listOfTeachers.length <= 50, "Too many teachers");

  2. Use a pull over push method of transfer, Instead of sending USDC in a loop, record the amount owed, and let teachers withdraw individually by creating withdraw function to spread gas costs and removes the block-limit risk.

    mapping(address => uint256) public pendingPayments;
    function graduateAndUpgrade(...) public onlyPrincipal {
    for (uint256 n = 0; n < totalTeachers; n++) {
    pendingPayments[listOfTeachers[n]] += payPerTeacher;
    }
    pendingPayments[principal] += principalPay;
    _authorizeUpgrade(_levelTwo)
    function withdraw() external {
    uint256 amount = pendingPayments[msg.sender];
    require(amount > 0, "Nothing to withdraw");
    pendingPayments[msg.sender] = 0;
    usdc.safeTransfer(msg.sender, amount);
    }

    \

Updates

Lead Judging Commences

yeahchibyke Lead Judge
3 months ago
yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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