Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

It should never be possible to change the status of a completed task #230

Closed
code423n4 opened this issue Aug 6, 2022 · 2 comments
Closed
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate This issue or pull request already exists edited-by-warden sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") valid

Comments

@code423n4
Copy link
Contributor

code423n4 commented Aug 6, 2022

Lines of code

https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Project.sol#L330-L359
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Project.sol#L705-L713
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Project.sol#L386-L490
https://github.com/code-423n4/2022-08-rigor/blob/main/test/utils/projectTests.ts#L1110

Vulnerability details

High Risk Finding

Impact

In Project.sol, once a task is set as completed (by calling function setComplete), the contract pays the subcontractor. Once in this state, in should not be possible to change the task state back to ACTIVE/INACTIVE, because then the same task could be set as completed again and payed out multiple times. Furthermore, function projectCost would not return the real cost, because it loops through all tasks of a project adding up the cost of each task, but the real cost of a completed task payed out multiple times would be the cost of the task times the number of times it has been set as completed.

A call to function changeOrder can unapprove a completed task just by passing a _newCost greater than _taskCost and provided that (totalLent - _totalAllocated < _newCost - _taskCost) holds. It can also be unapproved by passing in a new subcontractor to changeOrder.

It could be possible for a malicious contractor and subcontractor to (almost) drain the total lent to the project by setting as complete, changing order to unnaprove task, then setting again as complete, multiple times.

Proof of Concept

I added a new test in file projectTests.ts, substituting test 'should be able to complete a task' with the following test:


it('should be able to changeOrder after complete a task and new state should be INACTIVE', async () => {
    const taskID = 1;
    const _taskCost = 2 * taskCost;
    const taskSC = signers[3];
    const data = {
      types: ['uint256', 'address'],
      values: [taskID, project.address],
    };
    const [encodedData, signature] = await multisig(data, [
      signers[0],
      signers[1],
      taskSC,
    ]);
    await mockDAIContract.mock.transfer
      .withArgs(taskSC.address, _taskCost)
      .returns(true);
    await mockDAIContract.mock.transfer
      .withArgs(await homeFiContract.treasury(), _taskCost / 1e3)
      .returns(true);
    const tx = await project.setComplete(encodedData, signature);
    tx.wait();

    const taskDetails = await project.getTask(taskID);
    expect(taskDetails.state).to.equal(3);

    const { subcontractor, cost } = await project.getTask(taskID);

    expect(subcontractor).to.equal(taskSC.address);

    const newCost = taskCost * 100;
    const newSC = subcontractor;
    const projectAddress = project.address;
    const data2 = {
      types: ['uint256', 'address', 'uint256', 'address'],
      values: [taskID, newSC, newCost, projectAddress],
    };
    const [encodedData2, signature2] = await multisig(data2, [
      signers[0],
      signers[1],
      taskSC,
    ]);

    const tx2 = await project.changeOrder(encodedData2, signature2);
    tx2.wait();

    const { state } = await project.getTask(taskID);

    expect(state).to.equal(3);
    
});

It first sets as complete a task, and then it call changeOrder with a greater cost and same subcontractor.
The test fails, because it expects the task state to be 3 (COMPLETE), but it is 1 (INACTIVE) instead.

Tools Used

Hardhat

Recommended Mitigation Steps

There are various things that can be done to mitigate this issue, but I would:

  1. Put a require at the beginning of function changeOrder to ensure the task is not already completed: require(tasks[_taskID].state != TaskStatus.Complete, "Task already completed"). A Completed task should not be changed ever.
@code423n4 code423n4 added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels Aug 6, 2022
code423n4 added a commit that referenced this issue Aug 6, 2022
@code423n4 code423n4 added 3 (High Risk) Assets can be stolen/lost/compromised directly edited-by-warden and removed 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value labels Aug 6, 2022
@0xA5DF
Copy link

0xA5DF commented Aug 11, 2022

Subset of #95 (not reusing signature, requires contractor to be an accomplice)

@parv3213 parv3213 added the sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") label Aug 18, 2022
@jack-the-pug
Copy link
Collaborator

Duplicate of #95

@jack-the-pug jack-the-pug added duplicate This issue or pull request already exists valid labels Aug 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate This issue or pull request already exists edited-by-warden sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") valid
Projects
None yet
Development

No branches or pull requests

4 participants