Platform: Code4rena
Start Date: 31/01/2023
Pot Size: $90,500 USDC
Total HM: 47
Participants: 169
Period: 7 days
Judge: LSDan
Total Solo HM: 9
Id: 211
League: ETH
Rank: 30/169
Findings: 3
Award: $758.80
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: gjaldon
Also found by: Ch_301, KIntern_NA, mookimgo
704.9309 USDC - $704.93
VaultController's deployVault do not check whether staking parameter is a valid staking clone, leading to malicious attacker can get approve of ERC20 tokens from VaultController and its adminProxy.
VaultController and its adminProxy may hold ERC20s like excessive reward tokens from other vault, or the adminProxy is set as fee receiver, or just user's wrong sending. These tokens can be stole by attacker.
We're assuming permission control canCreate
is passed, as by default it's a blacklist mechanism, basically allow anyone to deploy.
deployVault function does not check whether staking contract is a valid clone, so attacker can set a malicious staking address as he want.
Then the staking variable is passed to _registerCreatedVault, which call _registerVault, then VaultRegistry.registerVault, so that the attacker can control vaultRegistry.getVault(xxx).staking
.
Then it goes to _handleVaultStakingRewards, which is calling addStakingRewardsTokens
staking variable is read by https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultController.sol#L448
This code makes adminProxy approve rewardsToken
(which is also passed by attacker, can be most ERC20 as long as not blacklisted and not a clone) to the staking contract. And approve rewardsToken to staking contract.
Now, the malicious staking contract can steal rewardsToken from VaultController and its adminProxy.
No
Add a cloneExists check for the staking parameter for deployVault function.
#0 - c4-sponsor
2023-02-17T13:03:36Z
RedVeil marked the issue as sponsor acknowledged
#1 - c4-judge
2023-02-23T20:46:05Z
dmvt marked the issue as duplicate of #275
#2 - c4-judge
2023-02-23T21:33:32Z
dmvt changed the severity to 3 (High Risk)
#3 - c4-judge
2023-02-23T22:55:16Z
dmvt marked the issue as partial-50
#4 - c4-judge
2023-03-01T00:28:11Z
dmvt marked the issue as full credit
#5 - c4-judge
2023-03-01T00:56:36Z
dmvt marked the issue as satisfactory
🌟 Selected for report: rvierdiiev
Also found by: Lirios, Ruhum, ayeslick, bin2chen, critical-or-high, hansfriese, hashminer0725, immeas, jasonxiale, mookimgo
18.3909 USDC - $18.39
Malicious attacker can set all fees of Vault (deposit, withdrawal, management, performance) to zero, as long as the owner does not set a fee propose.
https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L539-L546
Function changeFees in Vault does not limit who can call, so every one can call this function.
The constructor of Vault does not initialize proposedFeeTime and proposedFees, so they are both zero after Vault deploy.
So this if (block.timestamp < proposedFeeTime + quitPeriod) revert
is simply comparing timestamp>0, which is true.
So the attacker can set fees to zero, making he make deposit or withdraw at zero cost.
The damage is limited, once the owner set a fee propose by calling proposeFees
, this attack does not feasible.
Human code reading
#0 - c4-judge
2023-02-16T08:09:03Z
dmvt marked the issue as duplicate of #78
#1 - c4-sponsor
2023-02-18T12:16:34Z
RedVeil marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-23T00:55:25Z
dmvt marked the issue as partial-50
🌟 Selected for report: IllIllI
Also found by: 0x3b, 0xAgro, 0xBeirao, 0xMirce, 0xNineDec, 0xRobocop, 0xSmartContract, 0xTraub, 0xWeiss, 2997ms, 41i3xn, Awesome, Aymen0909, Bauer, Bnke0x0, Breeje, Cryptor, DadeKuma, Deathstore, Deekshith99, DevABDee, DevTimSch, Dewaxindo, Diana, Ermaniwe, Guild_3, H0, IceBear, Inspectah, JDeryl, Kaiziron, Kaysoft, Kenshin, Mukund, Praise, RaymondFam, Rickard, Rolezn, Ruhum, Sathish9098, SkyWalkerMan, SleepingBugs, UdarTeam, Udsen, Walter, aashar, adeolu, apvlki, arialblack14, ast3ros, btk, chaduke, chandkommanaboyina, chrisdior4, climber2002, codetilda, cryptonue, cryptostellar5, csanuragjain, ddimitrov22, descharre, dharma09, doublesharp, eccentricexit, ethernomad, fs0c, georgits, halden, hansfriese, hashminer0725, immeas, lukris02, luxartvinsec, matrix_0wl, merlin, mookimgo, mrpathfindr, nadin, olegthegoat, pavankv, rbserver, rebase, savi0ur, sayan, scokaf, seeu, shark, simon135, tnevler, tsvetanovv, ulqiorra, ustas, waldenyan20, y1cunhui, yongskiws, yosuke
35.4779 USDC - $35.48
The interface IVaultRegistry has defined getSubmitter
to return an address.
But this code is returning VaultMetadata
, which is wrong. In this implementation, this function is working exactly same as getVault, which should not be expected.
Suggestion: delete this getSubmitter function
No change can be made for this FEE_RECIPIENT, consider adding a function to change it
Even when harvestCooldown check is not passed, this harvest function still emit the Harvested
event.
lastHarvest
is not changed in harvest functionThis means lastHarvest
must be changed in the delegate call of strategy contract, which is error-prone because it requires exact variable layout.
Suggestion: change to
function harvest() public takeFees { if ( address(strategy) != address(0) && ((lastHarvest + harvestCooldown) < block.timestamp) ) { // solhint-disable address(strategy).delegatecall( abi.encodeWithSignature("harvest()") ); lastHarvest = block.timestamp; emit Harvested(); } }
There're 6 Vault functions that require onlyOwner
: proposeFees setFeeRecipient proposeAdapter setQuitPeriod pause unpause
The owner of Vault is the AdminProxy, which gives VaultController permission to call admin functions.
For example, proposeFees is called by proposeVaultFees.
However, 2 functions are missing corresponding function: setQuitPeriod and setFeeRecipient
This makes nobody can change quit period and fee recipient of Vaults.
Suggestion: add setVaultQuitPeriod and setVaultFeeRecipient
There is no removeClone function in CloneRegistry, if we found any clone is vulnerable, we cannot remove it from the CloneRegistry, so that we can not prevent it from being proposed as adapters.
depoloyStaking
requires that asset is not a clone by calling _verifyToken; but _deployStaking
function is also called at https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultController.sol#L108 for the new created vault, which is a clone. These two behaviours does not match.
So that we cannot deploy a new staking contract for existing vault, this may not be expected.
The WhitePaper states that
The protocol will not take any percentage of the fees that accrue in Vaults. Instead each Wrapper will be able to charge a small fee of a few basis points.
But the Vaults' code has fee design such as management fee and performance fee.
This difference should be handled by update the whitepaper or changing the code.
User can simply deposit into adapters to bypass vault management fee and performance fee.
Consider only allow vaults to be able to deposit into Adapters.
#0 - c4-judge
2023-02-28T14:57:09Z
dmvt marked the issue as grade-b