Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: low
Valid

Missing Session End Check in graduateAndUpgrade

Summary

The vulnerability is that the graduateAndUpgrade function can be called by the principal at any time, without checking whether the session has actually ended. This allows the contract to be upgraded and funds to be distributed before the intended session duration is complete, potentially bypassing important business logic or time-based restrictions.

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
@> // Need to verify if the session has ended
if (_levelTwo == address(0)) {
revert HH__ZeroAddress();
}
uint256 totalTeachers = listOfTeachers.length;
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
}

Vulnerability Details

Description:
The graduateAndUpgrade function does not verify whether the current session has ended before allowing the principal to execute the upgrade and distribute funds. As a result, the principal can trigger graduation and contract upgrade at any time, even immediately after starting a session.

Exploit Scenario:
A malicious or careless principal could call graduateAndUpgrade right after starting a session, upgrading the contract and distributing funds before any meaningful activity or review process occurs.

PoC:

function test_confirm_principale_can_update_befor_end_session() public schoolInSession {
levelTwoImplementation = new LevelTwo();
levelTwoImplementationAddress = address(levelTwoImplementation);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
uint256 initialPrincipalBalance = usdc.balanceOf(principal);
uint256 initialAliceBalance = usdc.balanceOf(alice);
uint256 initialBobBalance = usdc.balanceOf(bob);
vm.startPrank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
vm.stopPrank();
uint256 finalPrincipalBalance = usdc.balanceOf(principal);
uint256 finalAliceBalance = usdc.balanceOf(alice);
uint256 finalBobBalance = usdc.balanceOf(bob);
assert(finalPrincipalBalance > initialPrincipalBalance);
assert(finalAliceBalance > initialAliceBalance);
assert(finalBobBalance > initialBobBalance);
}

Impact

  1. The session duration and its intended restrictions can be bypassed.

  2. Students and teachers may not have sufficient time to participate in the session, receive reviews, or meet graduation criteria.

  3. Funds (bursary) can be distributed prematurely, potentially disadvantaging participants who expect the session to last its full duration.

Tools Used

Foundry

Recommendations

Add a check in graduateAndUpgrade to ensure that the current block timestamp is greater than or equal to sessionEnd, enforcing that the session must be completed before graduation and upgrade are allowed.

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
+ require(block.timestamp >= sessionEnd, "Session is still ongoing")
.
.
.
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

can graduate without session end

`graduateAndUpgrade()` can be called successfully even when the school session has not ended

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

can graduate without session end

`graduateAndUpgrade()` can be called successfully even when the school session has not ended

Support

FAQs

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