Hawk High

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

Premature System Upgrade Due to Missing Session End Validation

Severity: High

Likelihood: High

Summary

The LevelOne contract's graduateAndUpgrade function lacks a critical validation to ensure that the educational session has properly concluded before allowing system graduation and upgrade. This omission enables the principal to prematurely end an academic session, distribute funds, and upgrade the system before the intended 4-week duration has elapsed.

Vulnerability Details

When a session is initiated through the startSession function, a sessionEnd timestamp is set 4 weeks into the future:

function startSession(uint256 _cutOffScore) public onlyPrincipal notYetInSession {
sessionEnd = block.timestamp + 4 weeks;
inSession = true;
cutOffScore = _cutOffScore;
emit SchoolInSession(block.timestamp, sessionEnd);
}

However, the graduateAndUpgrade function, which handles the critical operations of system upgrade and fund distribution, fails to verify that this session has actually reached its intended conclusion:

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
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);
}

The function is missing a check similar to:

require(block.timestamp >= sessionEnd, "Session has not yet ended");

Impact

This vulnerability has several serious implications:

  1. Contractual Violation: Students pay school fees with the expectation of receiving a full 4-week educational term, but this can be arbitrarily shortened.

  2. Premature Fund Distribution: Teachers and principal can receive their share of the funds without fulfilling the complete term of service.

  3. Incomplete Educational Assessment: The system may graduate students before they have received sufficient reviews or before their final scores accurately reflect their performance.

  4. System State Inconsistency: After graduation, the inSession flag is not reset, leaving the system in an ambiguous state where it technically remains "in session" after graduation.

  5. Cutoff Score Bypass: Students who haven't met the established cutOffScore might be graduated prematurely, bypassing this quality control measure.

Code Reference

The graduateAndUpgrade function in LevelOne:

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
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);
}

Proof of Concept

  1. Principal calls startSession setting sessionEnd to be 4 weeks in the future

  2. Shortly after (e.g., 1 day later), principal calls graduateAndUpgrade

  3. The function executes successfully, distributing funds to teachers and principal

  4. Students have only received 1 day of education instead of the promised 4 weeks

  5. The system upgrades to _levelTwo prematurely

Recommendations

  1. Add a timestamp validation to ensure the session has properly concluded:

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
// Add this check
require(block.timestamp >= sessionEnd, "Session has not yet ended");
if (_levelTwo == address(0)) {
revert HH__ZeroAddress();
}
// Rest of function remains the same
...
}
  1. Reset the session state to maintain system consistency:

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
require(block.timestamp >= sessionEnd, "Session has not yet ended");
require(inSession == true, "School not in session");
// Reset session state
inSession = false;
// Rest of function remains the same
...
}
  1. Consider implementing a check for the cutoff score criteria:

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
require(block.timestamp >= sessionEnd, "Session has not yet ended");
// Verify all remaining students meet the cutoff score
for (uint256 i = 0; i < listOfStudents.length; i++) {
require(studentScore[listOfStudents[i]] >= cutOffScore, "Not all students meet cutoff score");
}
// Rest of function remains the same
...
}
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

Support

FAQs

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