Hawk High

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

Missing Validation for Failed Students Before Upgrade

Summary

Does not validate whether students met graduation criteria (cutOffScore).

Vulnerability Details

The graduateAndUpgrade() function lacks validation to ensure only students meeting the cutOffScore are carried forward during protocol upgrades. This allows Failed students (score < cutOffScore) to persist post-upgrade.

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;
@ > // No student validation
@ > // All students (even failed ones) migrate
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
}

POC

function test_failed_students_get_to_graduate() public schoolInSession {
// Give reviews to all students (4 weeks worth)
for (uint week = 1; week <= 4; week++) {
vm.warp(block.timestamp + 1 weeks);
// Alice gives reviews
vm.prank(alice);
levelOneProxy.giveReview(clara, true); // Will graduate (100)
vm.prank(alice);
levelOneProxy.giveReview(dan, false); // Will fail (70 after deductions)
vm.prank(alice);
levelOneProxy.giveReview(eli, true); // Will graduate (100)
vm.prank(alice);
levelOneProxy.giveReview(fin, false); // Will fail (70 after deductions)
vm.prank(alice);
levelOneProxy.giveReview(grey, true); // Will graduate (100)
vm.prank(alice);
levelOneProxy.giveReview(harriet, false); // Will fail (70 after deductions)
}
// Fast forward to session end
vm.warp(block.timestamp + 4 weeks);
// Check initial balances
uint256 initialBursary = usdc.balanceOf(address(levelOneProxy));
uint256 initialAliceBalance = usdc.balanceOf(alice);
uint256 initialBobBalance = usdc.balanceOf(bob);
uint256 initialPrincipalBalance = usdc.balanceOf(principal);
// Graduate and upgrade
levelTwoImplementation = new LevelTwo();
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(address(levelTwoImplementation), data);
// Check that only eligible students graduated
LevelTwo levelTwoProxy = LevelTwo(proxyAddress);
// clara, eli, grey, harriet, fin, alice graduates
//instead of clara, eli, grey
assertEq(levelTwoProxy.getTotalStudents(), 6);
}

Impact

Protocol rules to be bypassed, undermining the integrity of the grading system.

Tools Used

Foundry Tests

Recommendations

Add pre-upgrade validation and reset the array

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
/// ...
_validateUpgradeConditions(_levelTwo);
uint256 studentCount = listOfStudents.length;
graduatingCount = 0;
address[] memory newStudentsList = new address[](studentCount);
uint256 newStudentsCount = 0;
for (uint256 i = 0; i < studentCount; i++) {
address student = listOfStudents[i];
if (studentScore[student] >= cutOffScore) {
// Add to graduating count and keep in list
graduatingCount++;
newStudentsList[newStudentsCount] = student;
newStudentsCount++;
}
}
// Update the main students list
delete listOfStudents;
for (uint256 i = 0; i < newStudentsCount; i++) {
listOfStudents.push(newStudentsList[i]);
}
///...
_executeUpgradeAndPayments(_levelTwo, graduatingCount);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 6 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.