The Distribution.sol contract allows users to stake stETH in order to receive MOR tokens as rewards. Users could withdraw their staked stETH anytime as long as the withdrawLockPeriod and withdrawLockPeriodAfterStake have passed. stETH is a rebasing token and a rebase occurs every day as long as the Oracle reaches quorum on the beacon chain. The rebase automatically adjusts the balances of each wallet / smart contract holding stETH.
There is also a minimalStake amount for each pool which is used to check if the user _stake covers that criteria.
Within the _withdraw() function we can see that there are a few checks:
If the _amount passed for withdrawal is greater than the _deposited amount of the given user, the value of _amount is overwritten with _deposited
Then for public pools we have a check if the contract balance is greater than that amount and if not, _amount is overwritten again with the _balance of the contract
The vulnerability lies within the second overwrite mentioned above in the case of a negative rebase for stETH and would impact the last user who might be trying to withdraw their stETH.
Based on Lido official documentation: " In case Lido's validators get slashed or penalized, the stETH balances can decrease according to penalty sizes." (https://docs.lido.fi/guides/lido-tokens-integration-guide/#steth-internals-share-mechanics)
Although there hasn't been a negative rebalance it is not impossible to see one in the future.
Now with all the above information imagine the following scenario:
minimalStake is set to 1 stETH
userA has staked 1 stETH a while back
there is a negative rebase for stETH which lowers the contract balance from 1 stETH to 0.8 stETH
userA is the last user in the system and tries to perform a _withdraw by passing 1 stETH as amount
_amount is overridden with contractBalance which is 0.8 stETH due to the fact that passed _amount is higher than the actual balance of the contract
newDeposited for the given userA becomes 1 stETH - 0.8 stETH --> 0.2 stETH
this causes the following require to fail: require(newDeposited_ >= pool.minimalStake || newDeposited_ == 0, "DS: invalid withdraw amount");, because the newDeposited is not >= to minimalStake, but also it is not 0 and the transaction fails
The last user in the system might not be able to withdraw his/hers stETH in the case of a negative rebase for stETH and there is no other way to recover the funds.
Manual Review
Add a emergencyWithdraw function for user whose funds are stuck to be able to withdraw whatever is left in the system.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.