sherlock-audit / 2023-09-Gitcoin-judging

11 stars 7 forks source link

hals - `RFPSimpleStrategy::rejectMilestone` : pool manager can reject any milestone as there's no check if the milestone status is set to pending by the accepted recipient. #897

Closed sherlock-admin2 closed 11 months ago

sherlock-admin2 commented 12 months ago

hals

medium

RFPSimpleStrategy::rejectMilestone : pool manager can reject any milestone as there's no check if the milestone status is set to pending by the accepted recipient.

RFPSimpleStrategy::rejectMilestone : pool manager can reject any milestone as there's no check if the milestone status is set to pending by the accepted recipient.

Vulnerability Detail

Impact

This supposed to result in disabling milestone distribution for any milestone payment that were set to Rejected even if they are not submitted by the recipient (but will not due to another vulnerability in the _distribute function where the milestone status is not checked before execution)

Code Snippet

RFPSimpleStrategy::submitUpcomingMilestone function

    function submitUpcomingMilestone(Metadata calldata _metadata) external {
        // Check if the 'msg.sender' is the 'acceptedRecipientId' and is a member of the 'Profile'
        if (acceptedRecipientId != msg.sender && !_isProfileMember(acceptedRecipientId, msg.sender)) {
            revert UNAUTHORIZED();
        }

        // Check if the upcoming milestone is in fact upcoming
        if (upcomingMilestone >= milestones.length) revert INVALID_MILESTONE();

        // Get the milestone and update the metadata and status
        Milestone storage milestone = milestones[upcomingMilestone];
        milestone.metadata = _metadata;

        // Set the milestone status to 'Pending' to indicate that the milestone is submitted
        milestone.milestoneStatus = Status.Pending;

        // Emit event for the milestone
        emit MilstoneSubmitted(upcomingMilestone);
    }

RFPSimpleStrategy::rejectMilestone function

    function rejectMilestone(uint256 _milestoneId) external onlyPoolManager(msg.sender) {
        // Check if the milestone is already accepted
        if (milestones[_milestoneId].milestoneStatus == Status.Accepted) revert MILESTONE_ALREADY_ACCEPTED();

        milestones[_milestoneId].milestoneStatus = Status.Rejected;

        emit MilestoneStatusChanged(_milestoneId, milestones[_milestoneId].milestoneStatus);
    }

Tool used

Manual Review

Recommendation

In rejectMilestone function check the milestone status before setting its status to Rejected:

    function rejectMilestone(uint256 _milestoneId) external onlyPoolManager(msg.sender) {
        // Check if the milestone is already accepted
        if (milestones[_milestoneId].milestoneStatus == Status.Accepted) revert MILESTONE_ALREADY_ACCEPTED();
+       require(milestones[_milestoneId].milestoneStatus == Status.Pending,"milestone is not submitted by the accepted recipient");

        milestones[_milestoneId].milestoneStatus = Status.Rejected;

        emit MilestoneStatusChanged(_milestoneId, milestones[_milestoneId].milestoneStatus);
    }
sherlock-admin commented 11 months ago

1 comment(s) were left on this issue during the judging contest.

n33k commented:

invalid, intended behavior