Hawk High

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

Principal Can Add Themselves as a Teacher, Leading to Role Conflict and Potential Double Compensation

Summary

This report identifies a design flaw in the LevelOne.sol contract where the principal can use the addTeacher() function to register their own address as a teacher. This action has significant implications:

  1. Financial: The principal would receive both their designated principal's wage and a teacher's wage during the graduateAndUpgrade process.

  2. Functional: The principal, by becoming a teacher, gains the ability to call teacher-specific functions like giveReview().
    This behavior blurs the lines between distinct roles, potentially leading to unfair financial advantage and a concentration of power that may contradict the system's intended design and documented role separation.

Vulnerability Details / Issue Description

The LevelOne.sol contract's addTeacher() function is intended for the principal to add new teacher addresses.

/src/LevelOne.sol
function addTeacher(address _teacher) public onlyPrincipal notYetInSession {
if (_teacher == address(0)) {
revert HH__ZeroAddress();
}
if (isTeacher[_teacher]) {
revert HH__TeacherExists();
}
if (isStudent[_teacher]) {
revert HH__NotAllowed();
}
listOfTeachers.push(_teacher);
isTeacher[_teacher] = true;
emit TeacherAdded(_teacher);
}

The function includes checks to prevent adding the zero address, duplicate teachers, or existing students as teachers. However, it lacks a specific check to prevent the principal from adding their own address as _teacher. If the principal calls addTeacher(principal), and they are not already a student, they will be successfully added to listOfTeachers, and isTeacher[principal] will be set to true.

This leads to two primary issues:

  1. Double Compensation in graduateAndUpgrade(): The graduateAndUpgrade() function distributes wages:

// From src/LevelOne.sol
function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
// ...
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); // Issue 1: Principal gets teacher pay if in listOfTeachers
}
usdc.safeTransfer(principal, principalPay); // Issue 1: Principal gets principal pay
}

If the principal's address is in listOfTeachers, they will receive payPerTeacher during the loop and then principalPay separately, effectively "double-dipping" from the bursary.
2. Ability to giveReview(): The giveReview() function is restricted by the onlyTeacher modifier:

// from src/LevelOne.sol
modifier onlyTeacher() {
if (!isTeacher[msg.sender]) {
revert HH__NotTeacher();
}
_;
}
function giveReview(address _student, bool review) public onlyTeacher {
// ...
}

If isTeacher[principal] is true, the principal (as msg.sender) can bypass the onlyTeacher restriction and execute giveReview(), a function typically reserved for the teaching staff.

Proof Of Concept

function test_princpal_add_as_teacher() public {
vm.startPrank(principal);
levelOneProxy.addTeacher(principal);
vm.stopPrank();
assert(levelOneProxy.isTeacher(principal) == true);
assert(levelOneProxy.getTotalTeachers() == 1);
}

Impact

  • Violation of Role Separation: The system's design, as implied by distinct roles and modifiers, suggests a separation of duties. This flaw breaks that separation.

  • Unfair Financial Advantage: The principal can receive a larger share of the bursary than likely intended by the wage structure (TEACHER_WAGE + PRINCIPAL_WAGE).

  • Potential Abuse of Power: Allowing the principal to give reviews could lead to conflicts of interest or undermine the intended academic review process if not carefully managed.

  • Contradiction with Implicit Design: While not explicitly forbidden by a specific line in the documentation provided, it contradicts the common understanding of role-based access control and separation of duties in such systems. The documentation outlines distinct roles, and this allows for an unintended overlap.

Tools Used

Manual Code Review

Recommendations

To enforce stricter role separation and prevent the identified issues, it is recommended to modify the addTeacher() function to explicitly prevent the principal's address from being added as a teacher.

/src/LevelOne.sol
function addTeacher(address _teacher) public onlyPrincipal notYetInSession {
if (_teacher == address(0)) {
revert HH__ZeroAddress();
}
if (isTeacher[_teacher]) {
revert HH__TeacherExists();
}
if (_teacher == principal) {
revert HH__NotAllowed(); // Or a more specific error like HH__PrincipalCannotBeTeacher
}
if (isStudent[_teacher]) {
revert HH__NotAllowed();
}
listOfTeachers.push(_teacher);
isTeacher[_teacher] = true;
emit TeacherAdded(_teacher);
}
Updates

Lead Judging Commences

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

principal can become teacher

Principal can add themselves as teacher and share in teacher pay upon graduation

Support

FAQs

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