Hawk High

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

Blacklisted Teacher or Principal Address Prevents Contract Upgrade

Description:
The graduateAndUpgrade() function in the LevelOne contract is vulnerable to complete failure if any teacher or the principal has been blacklisted by the USDC token contract. The function uses SafeERC20's safeTransfer to distribute payments to all teachers in a loop and then to the principal. If any recipient address is blacklisted by the USDC token, the transfer will revert, causing the entire transaction to fail. This creates a critical vulnerability where a single blacklisted address can prevent the entire contract upgrade process from completing.

Code Snippet:

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); // Reverts if listOfTeachers[n] is blacklisted
}
usdc.safeTransfer(principal, principalPay); // Reverts if principal is blacklisted
}

Impact: This vulnerability creates a significant risk to the contract's operation and longevity:

  1. The contract upgrade mechanism becomes completely blocked if any single teacher is blacklisted

  2. USDC funds remain locked in the contract indefinitely

  3. The school system cannot progress to a new implementation

  4. The principal cannot remove the problematic teacher after the upgrade attempt has started

  5. A malicious actor could intentionally get themselves added as a teacher and then get blacklisted to sabotage the system

Proof of Concept:

  1. A teacher's address gets blacklisted by the USDC token (could happen for various compliance reasons)

  2. The principal attempts to call graduateAndUpgrade() with a valid new implementation address

  3. When the function attempts to transfer USDC to the blacklisted teacher, the transaction reverts

  4. The contract remains in its current implementation, and all funds stay locked

Recommended Mitigation: Implement a pull payment pattern instead of push payment, separating the upgrade logic from the payment distribution:

// Add state variables
mapping(address => uint256) public pendingPayments;
bool public upgraded;
address public newImplementation;
// Split the upgrade and payment functions
function initiateGraduationAndUpgrade(address _levelTwo) public onlyPrincipal {
if (_levelTwo == address(0)) {
revert HH__ZeroAddress();
}
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
// Calculate payments but don't transfer
for (uint256 n = 0; n < listOfTeachers.length; n++) {
pendingPayments[listOfTeachers[n]] = payPerTeacher;
}
pendingPayments[principal] = principalPay;
// Set the upgrade state
upgraded = true;
newImplementation = _levelTwo;
// Execute the upgrade
_authorizeUpgrade(_levelTwo);
}
// Allow individuals to withdraw their payments
function withdrawPayment() public {
uint256 amount = pendingPayments[msg.sender];
require(amount > 0, "No payment available");
pendingPayments[msg.sender] = 0;
// If this transfer fails, it only affects the caller
usdc.safeTransfer(msg.sender, amount);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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