Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: high
Invalid

Denial of Service Vulnerability in Queued Withdrawals

Summary

The PriorityPool and WithdrawalPool smart contracts feature a Denial of Service (DoS) vulnerability involving the interaction between queued token withdrawals in the PriorityPool and the queuing mechanism in the WithdrawalPool. An attacker can manipulate the amount of queued tokens in the PriorityPool to ensure that the amount passed to the WithdrawalPool during a withdrawal attempt falls below the minimum threshold, causing a revert and preventing legitimate users from successfully withdrawing their tokens.

Vulnerability Details

During a withdrawal process, the PriorityPool attempts to use queued tokens to fulfill the withdrawal request. If there are insufficient tokens, it calculates the difference toWithdraw and attempts to queue this in the WithdrawalPool.

https://github.com/Cyfrin/2024-09-stakelink/blob/f5824f9ad67058b24a2c08494e51ddd7efdbb90b/contracts/core/priorityPool/PriorityPool.sol#L659C1-L684C6

function _withdraw(
address _account,
uint256 _amount,
bool _shouldQueueWithdrawal
) internal returns (uint256) {
if (poolStatus == PoolStatus.CLOSED) revert WithdrawalsDisabled();
uint256 toWithdraw = _amount;
if (totalQueued != 0) {
uint256 toWithdrawFromQueue = toWithdraw <= totalQueued ? toWithdraw : totalQueued;
totalQueued -= toWithdrawFromQueue;
depositsSinceLastUpdate += toWithdrawFromQueue;
sharesSinceLastUpdate += stakingPool.getSharesByStake(toWithdrawFromQueue);
@>> toWithdraw -= toWithdrawFromQueue;
}
if (toWithdraw != 0) {
if (!_shouldQueueWithdrawal) revert InsufficientLiquidity();
@>> withdrawalPool.queueWithdrawal(_account, toWithdraw);
}
emit Withdraw(_account, _amount - toWithdraw);
return toWithdraw;
}

However, the WithdrawalPool enforces a minimum withdrawal amount (minWithdrawalAmount).

https://github.com/Cyfrin/2024-09-stakelink/blob/f5824f9ad67058b24a2c08494e51ddd7efdbb90b/contracts/core/priorityPool/WithdrawalPool.sol#L302C1-L316C6

function queueWithdrawal(address _account, uint256 _amount) external onlyPriorityPool {
@>> if (_amount < minWithdrawalAmount) revert AmountTooSmall();
lst.safeTransferFrom(msg.sender, address(this), _amount);
uint256 sharesAmount = _getSharesByStake(_amount);
queuedWithdrawals.push(Withdrawal(uint128(sharesAmount), 0));
totalQueuedShareWithdrawals += sharesAmount;
uint256 withdrawalId = queuedWithdrawals.length - 1;
queuedWithdrawalsByAccount[_account].push(withdrawalId);
withdrawalOwners[withdrawalId] = _account;
emit QueueWithdrawal(_account, _amount);
}

If the calculated _amount is less than this minimum, the operation fails, reverting the transaction.

An attacker can exploit this by strategically manipulating the totalQueued amount in the PriorityPool. By making deposits or withdrawals, they can alter this balance to ensure that the remaining withdrawal request (after using available queue tokens) results in an amount that causes a revert in the WithdrawalPool. This effectively blocks the user from fulfilling their withdrawal request.

Impact

  • Denial of Service Users can be obstructed from withdrawing their tokens due to strategically induced reverts during WithdrawalPool queuing.

POC

The attack is orchestrated by manipulating the state of the PriorityPool smart contract, specifically targeting the amount of tokens queued for withdrawal. Here's how the attack unfolds:

  1. Setup: The attacker observes a user initiating a withdrawal request from the PriorityPool. The total available queued tokens in the PriorityPool are checked against the user's requested withdrawal amount.

  2. Calculation of Vulnerable Outcome: The attacker computes the difference between the user's withdrawal request and the total queued tokens. If this difference is less than the minWithdrawalAmount set in the WithdrawalPool, the user's withdrawal request will fail at the queueWithdrawal step due to a revert.

  3. Exploiting Timing: At the precise moment just before the user's withdrawal request is processed, the attacker strategically deposits or withdraws tokens from the PriorityPool. This action alters the totalQueued balance, ensuring that the resulting difference calculated for the queueWithdrawal falls below the minWithdrawalAmount.

  4. Inducing Revert: Once the user's request is processed, the leftover amount to be queued in WithdrawalPool is indeed less than the minimum threshold. The withdrawal attempt is therefore reverted by the WithdrawalPool, causing a Denial of Service for the user.

  5. Repeatability: The attacker can repeatedly execute this strategy, continually adjusting the totalQueued balance to block or frustrate further attempts by the user or potentially other users.

Tools Used

Foundry

Recommendations

Adjust the conditions for initiating a withdrawal queue in the WithdrawalPool to be based on the total withdrawal request amount, rather than the difference between the requested amount and the queued tokens in PriorityPool.

Updates

Lead Judging Commences

inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

galturok Submitter
9 months ago
inallhonesty Lead Judge
9 months ago
inallhonesty Lead Judge 8 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.