The deposit() function does not have reentrancy protection. This allows reentrancy to occur through the implementation of a malicious claim’s beneficiary onDepositMinted() function that will cause all users’ deposits to be erroneously interpreted as yield instead.
Specifically, while shares for the first deposit have been minted, funds have not been transferred when reentrancy occurs. Hence, the shares calculated for the reentrant deposit use a lower totalUnderlyingMinusSponsored() than expected, resulting in more claim shares minted.
Proof of Concept
A gist of the attack script and contract is attached here.
Bob writes (rather, I wrote =p) the following malicious beneficiary contract.
Assume Alice calls deposit() with 1000 DAI (underlying token).
Bob transfers 10_000 DAI into the malicious contract, then calls deposit() with 10_000 DAI with the malicious contract as the beneficiary (depositing a total of 20_000 DAI).
We see disastrous results:
If Alice attempts to withdraw, the transaction will revert with Vault: cannot withdraw more than the available amount. Executing a forced withdrawal results in her only receiving ~173 DAI in return (about 82.7% loss).
Bob is allocated about 9090 DAI in yield (loss in deposits, but made up with claims).
We therefore see that a portion of both users’ deposits are considered as yield as a result of this attack.
Recommended Mitigation Steps
Import and use OpenZeppelin’s reentrancy guard for crucial user actions (deposits, claiming yields and withdrawals).
Handle
hickuphh3
Vulnerability details
Impact
The
deposit()
function does not have reentrancy protection. This allows reentrancy to occur through the implementation of a malicious claim’s beneficiaryonDepositMinted()
function that will cause all users’ deposits to be erroneously interpreted as yield instead.Specifically, while shares for the first deposit have been minted, funds have not been transferred when reentrancy occurs. Hence, the shares calculated for the reentrant deposit use a lower
totalUnderlyingMinusSponsored()
than expected, resulting in more claim shares minted.Proof of Concept
A gist of the attack script and contract is attached here.
Bob writes (rather, I wrote =p) the following malicious beneficiary contract.
deposit()
with1000
DAI (underlying token).10_000
DAI into the malicious contract, then callsdeposit()
with10_000
DAI with the malicious contract as the beneficiary (depositing a total of20_000
DAI).We see disastrous results:
Vault: cannot withdraw more than the available amount
. Executing a forced withdrawal results in her only receiving ~173
DAI in return (about82.7%
loss).9090
DAI in yield (loss in deposits, but made up with claims).We therefore see that a portion of both users’ deposits are considered as yield as a result of this attack.
Recommended Mitigation Steps
Import and use OpenZeppelin’s reentrancy guard for crucial user actions (deposits, claiming yields and withdrawals).