Hawk High

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

Empty `graduate()` Reinitializer Function Fails to Enforce Student Graduation Criteria

Summary

This report details a functional vulnerability in the LevelTwo.sol contract. The graduate() function, intended as a reinitializer(2) to be called upon upgrading from LevelOne.sol, is currently empty. This omission means that critical student graduation logic, specifically the requirement that "Any student who doesn't meet the cutOffScore should not be upgraded," is not enforced during the transition to Level Two. Consequently, all students from Level One, regardless of their academic performance, are carried over to Level Two, violating a core project invariant.

Vulnerability Details

In UUPS upgradeable contracts, reinitializer functions are invoked once when a proxy is upgraded to a new implementation version. Their purpose is to perform necessary state migrations, initialize new variables, or execute setup logic specific to the new version.

The LevelTwo.sol contract defines a graduate() function:

// From /src/LevelTwo.sol
function graduate() public reinitializer(2) {}

This function is currently empty.

The project documentation specifies a key invariant:

  • Any student who doesn't meet the cutOffScore should not be upgraded

The graduateAndUpgrade() function in LevelOne.sol handles wage payments and authorizes the upgrade but does not perform any filtering of students based on their scores. Therefore, the graduate() reinitializer in LevelTwo.sol is the logical place to implement this student filtering logic. Its current empty state means this crucial step is missed.

The necessary state variables (listOfStudents, studentScore mapping, and cutOffScore) are part of the shared storage between LevelOne.sol and LevelTwo.sol and are accessible to the graduate() function (assuming storage layout compatibility is otherwise addressed).

Proof Of Concept

  1. Setup:

  • LevelOne.sol is deployed via a proxy and initialized.

  • A cutOffScore (e.g., 70) is set when the session starts.

  • Two students are enrolled:

  • Student A, with studentScore[StudentA] = 60 (below cutOffScore).

  • Student B, with studentScore[StudentB] = 80 (at or above cutOffScore).

  • Both Student A and Student B are present in the listOfStudents array.

  1. Upgrade Process:

  • The Principal calls graduateAndUpgrade() in LevelOne.sol, targeting LevelTwo.sol. This function pays wages and authorizes the upgrade but does not modify listOfStudents.

  • The proxy is upgraded to the LevelTwo.sol implementation.

  • As part of the upgradeToAndCall mechanism, the graduate() function in LevelTwo.sol is executed.

  1. Outcome:

  • Since graduate() is empty, no filtering logic is applied.

  • Both Student A and Student B remain in the listOfStudents array within the state now managed by LevelTwo.sol.`.

  • This violates the invariant that Student A (who did not meet the cutOffScore) should not have been "upgraded."

Impact

  • Violation of Core Business Logic: The system fails to adhere to a clearly documented rule regarding student progression.

  • Incorrect System State:LevelTwo.sol will operate with an incorrect list of students, including those who should not have graduated.

  • Potential Unfairness/Exploitation: If Level Two introduces new features or benefits for "graduated" students, those who did not meet the criteria would unfairly gain access.

  • Misleading Functionality: The name graduate() implies specific actions that are not performed, potentially confusing developers or auditors about the system's behavior.

Tools Used

Manual Code Review

Recommendations

The graduate() reinitializer function in LevelTwo.sol must be implemented to filter the listOfStudents based on the cutOffScore. Students who do not meet the cutOffScore should be removed from the active student list for Level Two.

A common approach is to build a new list containing only the students who meet the graduation criteria and then replace the existing listOfStudents array.

Proposed Conceptual Code Change for LevelTwo.sol:

function graduate() public reinitializer(2) {
// This function is called upon upgrading to LevelTwo.
// It should filter students based on the cutOffScore.
uint256 currentCutOffScore = cutOffScore; // Read from storage (set in LevelOne)
address[] memory graduatedStudents = new address[]();
uint256 graduatedCount = 0;
for (uint256 i = 0; i < listOfStudents.length; i++) {
address student = listOfStudents[i];
if (studentScore[student] >= currentCutOffScore) {
graduatedStudents[graduatedCount] = student;
graduatedCount++;
} else {
// Optionally, mark the student as not graduated or remove from isStudent mapping
// For this example, we just don't add them to the new list.
// isStudent[student] = false; // If they should no longer be considered a student at all
}
}
assembly { mstore(listOfStudents, graduatedCount) } // Update length of listOfStudents
for (uint256 i = 0; i < graduatedCount; i++) { listOfStudents[i] = graduatedStudents[i]; }
}
Updates

Lead Judging Commences

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

cut-off criteria not applied

All students are graduated when the graduation function is called as the cut-off criteria is not applied.

Support

FAQs

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