Platform: Code4rena
Start Date: 03/02/2022
Pot Size: $75,000 USDC
Total HM: 42
Participants: 52
Period: 7 days
Judge: GalloDaSballo
Total Solo HM: 21
Id: 83
League: ETH
Rank: 40/52
Findings: 3
Award: $198.34
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: ShadowyNoobDev
Also found by: 0xw4rd3n, CertoraInc, Czar102, GalloDaSballo, Heartless, IllIllI, Jujic, Randyyy, Rhynorater, Sleepy, SolidityScan, ckksec, kirk-baird, leastwood, pauliax, peritoflores, reassor
7.97 USDC - $7.97
This function claimRewards#ConcurRewardPool.sol
is vulnerable to reentrancy though no token compromised this can be very dangerous because it is a critical function.
function claimRewards(address[] calldata _tokens) external override { for (uint256 i = 0; i < _tokens.length; i++) { uint256 getting = reward[msg.sender][_tokens[i]]; IERC20(_tokens[i]).safeTransfer(msg.sender, getting); reward[msg.sender][_tokens[i]] = 0; } }
The trick is the following: As _tokens is controlled by an attacker it is possible to deploy a IERC20 contract with the function transfer()
overriden and from there call back claimReward
or (other function).
Even your are using safeTransfer from OpenZeppelin SafeERC library this function will call transfer at low level.
#PoC
As a proof of concept I share this code that you can easily test in remix
pragma solidity ^0.8.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol"; contract EvilERC20 is IERC20{ function totalSupply() external view override returns (uint256){ return 0; } event EvilMessage(string message); function balanceOf(address account) external view override returns (uint256){ return 0; } function transfer(address recipient, uint256 amount) external override returns (bool){ emit EvilMessage("I am evil and I can perform reentrancy attack"); return true; } function allowance(address owner, address spender) external override view returns (uint256){ return 0; } function approve(address spender, uint256 amount) external override returns (bool){ return true; } function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool){ return true; } } contract TestReentracyinsafeTransfer{ using SafeERC20 for IERC20; IERC20 myToken; constructor(){ myToken= new EvilERC20(); } function test() public { myToken.safeTransfer(msg.sender, 0); } }
`
If you call test#TestReentracyinsafeTransfer
even your are using safeERC20 the event "I am evil and I can perform reentrancy attack" will be triggered
I believe that you should at least put a comment here because if at some point you increment a variable in that function the exploit would work. I will not harm to add nonReentrant modifier.
Finally also I recommend you switch the order
IERC20(_tokens[i]).safeTransfer(msg.sender, getting); #LOC 37 reward[msg.sender][_tokens[i]] = 0; #LOC 38
It is always safer to set a variable to 0 and then call the external contract.
#0 - r2moon
2022-02-18T03:07:20Z
#1 - GalloDaSballo
2022-04-26T21:19:41Z
Dup of #86
#2 - JeeberC4
2022-04-28T20:32:19Z
Renaming issues so as not to confuse things. This was submitted as a QA Report, but judge has upgraded the entire finding to a Med Risk issue that is a duplicate of #86
🌟 Selected for report: hickuphh3
Also found by: 0x1f8b, 0xw4rd3n, BouSalman, CertoraInc, Czar102, Dravee, IllIllI, Randyyy, Rhynorater, Ruhum, ShadowyNoobDev, Sleepy, SolidityScan, WatchPug, bitbopper, cccz, cryptphi, csanuragjain, defsec, gzeon, harleythedog, hubble, hyh, kenta, kirk-baird, leastwood, mtz, pauliax, peritoflores, rfa, robee, samruna, throttle, wuwe1, ye0lde
125.4893 USDC - $125.49
You are assuming that ERC20.transfer() always returns true, however according to the EIP-20 Standard (https://eips.ethereum.org/EIPS/eip-20)
Callers MUST handle false from returns (bool success). Callers MUST NOT assume that false is never returned!
There is also a known issue that many token do not implement well this transfer function so the most recommendable solution in this situation is to use safeTransfer from safeERC20 that you already imported in your file. Similar reports at https://github.com/code-423n4/2021-08-notional-findings/issues/68
Reward will not be properly transfer to treasury or claimContract.
#0 - r2moon
2022-02-16T17:02:14Z
duplicated with https://github.com/code-423n4/2022-02-concur-findings/issues/263
#1 - GalloDaSballo
2022-04-20T16:08:28Z
#2 - GalloDaSballo
2022-04-26T21:20:06Z
Valid
#3 - JeeberC4
2022-04-28T20:30:07Z
Generating QA Report as judge downgraded issue. Preserving original title: ERC20 return values not checked
🌟 Selected for report: WatchPug
Also found by: 0x0x0x, 0x1f8b, 0x510c, 0xNot0rious, 0xngndev, BouSalman, CertoraInc, Dravee, Heartless, IllIllI, Jujic, Randyyy, Ruhum, ShadowyNoobDev, Sleepy, SolidityScan, Tomio, bitbopper, csanuragjain, defsec, gzeon, hickuphh3, kenta, mtz, pauliax, peritoflores, rfa, robee, sabtikw, throttle, wuwe1, ye0lde
64.8796 USDC - $64.88
Now you are using solidity 0.8 you can remove SafeMath .
The following is important I believe as you mention massUpdatePools()
consumes lot of gas
#0 - GalloDaSballo
2022-04-04T14:26:25Z
6 * 20 = 120 gas saved