Skip to content
This repository was archived by the owner on Jan 7, 2024. It is now read-only.
This repository was archived by the owner on Jan 7, 2024. It is now read-only.

0xbepresent - A malicious vUSD withdrawal receiver can cause a DOS in the vUSD.processWithdrawals() function #95

Description

@sherlock-admin

0xbepresent

high

A malicious vUSD withdrawal receiver can cause a DOS in the vUSD.processWithdrawals() function

Summary

A malicious vUSD withdrawal receiver can cause a DOS in the vUSD.processWithdrawals() function causing all withdrawals to get stuck.

Vulnerability Detail

The VUSD._withdrawTo() function helps to queue the withdrawals in the withdrawals list, then the processWithdrawals() can be called in order to send all the withdrawals to their corresponding receiver.

The problem is low-level external calls can exhaust all available gas. The withdrawal receiver can be a contract which executes arbitrary logic and receives/spend all the available gas, then the processWithdrawals() may not have enough gas to complete the execution, causing a denial of service. Consider the next scenario:

  1. Malicious actor mints some vUSD via mintWithReserve() function.
  2. Malicious actor creates a malicious receiver smart contract that consumes all the gas forwarded to it.
  3. Malicious actor calls the withdrawTo() function using his Malicious smart contract as a receiver (parammeter to).
  4. Others users create more withdrawals
  5. When the processWithdrawals() is called, the Malicious smart contract will be called exhausting all the available gas causing processWithdrawals() may not have enough gas to complete the execution.
  6. All the next withdrawals will be get stuck in the vUSD contract.

Impact

The vUSD withdrawals will be get stuck causing a complete damage for the legitimate users who wants their usdc. Since the malicious actor can create withdrawals of 5e6 each one, the attack can be executed many times causing excessive resource consumption and a degraded quality of service.

Code Snippet

The processWithdrawals() and VUSD._withdrawTo() functions:

File: VUSD.sol
065:     function processWithdrawals() external override whenNotPaused nonReentrant {
066:         uint reserve = address(this).balance;
067:         require(reserve >= withdrawals[start].amount, 'Cannot process withdrawals at this time: Not enough balance');
068:         uint i = start;
069:         while (i < withdrawals.length && (i - start) < maxWithdrawalProcesses) {
070:             Withdrawal memory withdrawal = withdrawals[i];
071:             if (reserve < withdrawal.amount) {
072:                 break;
073:             }
074: 
075:             (bool success, bytes memory data) = withdrawal.usr.call{value: withdrawal.amount}("");
076:             if (success) {
077:                 reserve -= withdrawal.amount;
078:             } else {
079:                 emit WithdrawalFailed(withdrawal.usr, withdrawal.amount, data);
080:             }
081:             i += 1;
082:         }
083:         // re-entracy not possible, hence can update `start` at the end
084:         start = i;
085:     }
...
...
109:     function _withdrawTo(address to, uint amount) internal {
110:         require(amount >= 5 * (10 ** PRECISION), "min withdraw is 5 vusd");
111:         burn(amount); // burn vusd from msg.sender
112:         withdrawals.push(Withdrawal(to, amount * SCALING_FACTOR));
113:     }

Tool used

Manual review

Recommendation

Validates that the withdrawal receiver is not a contract.

Duplicate of #116

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions