Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: medium
Valid

Malicious User Can DoS Withdraw

Summary

The WithdrawalPool contains an optimization for managing withdrawal batches. The function updateWithdrawalBatchIdCutoff is responsible for updating the withdrawalBatchIdCutoff value. However, if a user has an incomplete withdrawal for a specific batch, this update cannot proceed until the user completes their withdrawal. A malicious user could exploit this situation to indefinitely prevent the update, leading to a denial-of-service (DoS) condition

Vulnerability Details

Look at the following scenario:

  1. A user submits a withdrawal request, which is assigned an ID of 130 and is associated with batch 35.

  2. The withdrawalBatchIdCutoff remains at 35 until the user's withdrawal request is finalized.

  3. If the withdrawal never completes, the getBatchIds function will iteratively process all batches for every withdrawal ID, potentially causing significant delays or halting other processes.

function getBatchIds(uint256[] memory _withdrawalIds) public view returns (uint256[] memory) {
uint256[] memory batchIds = new uint256[]();
for (uint256 i = 0; i < _withdrawalIds.length; ++i) {
uint256 batchId;
uint256 withdrawalId = _withdrawalIds[i];
for (uint256 j = withdrawalBatchIdCutoff; j < withdrawalBatches.length; ++j) {
uint256 indexOfLastWithdrawal = withdrawalBatches[j].indexOfLastWithdrawal;
if (withdrawalId <= indexOfLastWithdrawal) {
batchId = j;
break;
}
}
batchIds[i] = batchId;
}
return batchIds;
}
function updateWithdrawalBatchIdCutoff() external { // audit-mid possible dos
uint256 numWithdrawals = queuedWithdrawals.length;
uint256 newWithdrawalIdCutoff = withdrawalIdCutoff;
// find the first withdrawal that has funds remaining
for (uint256 i = newWithdrawalIdCutoff; i < numWithdrawals; ++i) {
newWithdrawalIdCutoff = i;
Withdrawal memory withdrawal = queuedWithdrawals[i];
if (withdrawal.sharesRemaining != 0 || withdrawal.partiallyWithdrawableAmount != 0) {
break;
}
} // this depends does the withdraw is already excuted
uint256 numBatches = withdrawalBatches.length;
uint256 newWithdrawalBatchIdCutoff = withdrawalBatchIdCutoff;
// find the last batch where all withdrawals have no funds remaining
for (uint256 i = newWithdrawalBatchIdCutoff; i < numBatches; ++i) {
if (withdrawalBatches[i].indexOfLastWithdrawal >= newWithdrawalIdCutoff) {
break;
}
newWithdrawalBatchIdCutoff = i;
}
withdrawalIdCutoff = uint128(newWithdrawalIdCutoff);
withdrawalBatchIdCutoff = uint128(newWithdrawalBatchIdCutoff);
}

Impact

This vulnerability could allow a malicious user to block the withdrawal process for other users by preventing the system from advancing the withdrawalBatchIdCutoff. This can lead to a DoS attack where no new withdrawals can be processed until the malicious withdrawal is completed, affecting the overall availability of the system.

Tools Used

Manual review

Recommendations

To mitigate this issue, consider implementing a timeout or fallback mechanism where an admin can finalize or cancel old withdrawal requests after a certain period of inactivity.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

M-1 Cyfrin not properly fixed - if someone forgets to withdraw the withdrawalBatches array is still ever increasing

Support

FAQs

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