Vulnerability in LidoVault::_claimWithdrawals function: unlockReceive and address(this).balance leads to Potential Overpayments
Summary
The LidoVault::_claimWithdrawals function allows users to claim withdrawals, but it does in a manner that exposes users to potential overpayments due to unlockReceive flag and `address(this).balance.
Vulnerability Detail
The LidoVault::_claimWithdrawals function sets unlockReceive to true at the beginning of the function and resets it to false at the end. However, this flag is not properly protected, allowing arbitrary users to send ETH to the contract during the withdrawal process. As a result, the withdrawnAmount calculation becomes inflated, which leads to unintended overpayments.
Impact
Financial Losses: Users may receive more ETH during withdrawal process, which leads to financial losses to contract owners or other users.
function _claimWithdrawals(address user, uint256[] memory requestIds) internal returns (uint256) {
// @audit-issue: contract receives ETH from any user when true, which makes address(this).balance gets inflated
@> unlockReceive = true;
@> uint256 beforeBalance = address(this).balance;
// Claim Ether for the burned stETH positions
// this will fail if the request is not finalized
for (uint i = 0; i < requestIds.length; i++) {
lidoWithdrawalQueue.claimWithdrawal(requestIds[i]); // amount gets transferred from `lido` to this contract.
}
// @audit-issue: contract balance inflated(claimed balance + arbitrary user's ETh deposit)
@> uint256 withdrawnAmount = address(this).balance - beforeBalance; // user receives more amount than intended.
require(withdrawnAmount > 0, "IWA");
emit WithdrawalClaimed(withdrawnAmount, requestIds, user);
unlockReceive = false;
return withdrawnAmount;
}
Proof of Concept-1
An user requested for withdrawal which triggered LidoVault::_claimWithdrawals function.
Now unlockReceive sets to true which allows LidoVault::receive function to receive ETH.
Assume the uint256 beforeBalance = address(this).balance; is 1000 Ether.
Arbitrary user sends 10 ether to this contract, because unlockReceive = true.
Now this line lidoWithdrawalQueue.claimWithdrawal(requestIds[i]); triggered, where lidoWithdrawalQueue transfers the amount to this contract.
Assume lidoWithdrawalQueue sends 100 ether to this contract.
So, 100 ether is intended withdrawal that should be received by the user.
But this line uint256 withdrawnAmount = address(this).balance - beforeBalance; makes withdrawn Amount = (1000 + 10 +100) - 1000 ether = 110 ether .
So, user receives more ether than intended.
Proof of Concept-2
user1 requested for withdrawal which triggered LidoVault::_claimWithdrawals function, which sets unlockReceive = true.
Now while _claimWithdrawals::for loop executing, user2 initiated his withdrawal request and his transaction is executed first which sets unlockReceive = false.
since unlockReceive = false, the contract won't receive any incoming ether.
So, user1's transaction gets reverted.
Tool used
Manual Review
Recommendation
Keep track of withdrawn amounts instead of relying on address(this).balance.
vizay9652
High
Vulnerability in
LidoVault::_claimWithdrawals
function:unlockReceive
andaddress(this).balance
leads to Potential OverpaymentsSummary
The
LidoVault::_claimWithdrawals
function allows users to claim withdrawals, but it does in a manner that exposes users to potentialoverpayments
due tounlockReceive
flag and `address(this).balance.Vulnerability Detail
The
LidoVault::_claimWithdrawals
function setsunlockReceive
totrue
at the beginning of the function and resets it tofalse
at the end. However, this flag is not properly protected, allowing arbitrary users to send ETH to the contract during the withdrawal process. As a result, thewithdrawnAmount
calculation becomes inflated, which leads to unintended overpayments.Impact
Financial Losses: Users may receive more ETH during withdrawal process, which leads to financial losses to contract owners or other users.
Code Snippet
https://github.com/sherlock-audit/2024-08-saffron-finance/blob/main/lido-fiv/contracts/LidoVault.sol#L1168-L1185
Proof of Concept-1
LidoVault::_claimWithdrawals
function.unlockReceive
sets totrue
which allowsLidoVault::receive
function to receive ETH.uint256 beforeBalance = address(this).balance;
is1000 Ether
.10 ether
to this contract, becauseunlockReceive = true
.lidoWithdrawalQueue.claimWithdrawal(requestIds[i]);
triggered, wherelidoWithdrawalQueue
transfers the amount to this contract.lidoWithdrawalQueue
sends100 ether
to this contract.100 ether
is intended withdrawal that should be received by the user.uint256 withdrawnAmount = address(this).balance - beforeBalance;
makeswithdrawn Amount = (1000 + 10 +100) - 1000 ether = 110 ether
.So, user receives more ether than intended.
Proof of Concept-2
user1
requested for withdrawal which triggeredLidoVault::_claimWithdrawals
function, which setsunlockReceive = true
._claimWithdrawals::for
loop executing,user2
initiated his withdrawal request and his transaction is executed first which setsunlockReceive = false
.unlockReceive = false
, the contract won't receive any incoming ether.user1's
transaction gets reverted.Tool used
Manual Review
Recommendation
Keep track of withdrawn amounts instead of relying on
address(this).balance
.