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: 36/52
Findings: 3
Award: $220.93
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: mtz
Also found by: 0x1f8b, Czar102, GalloDaSballo, GeekyLumberjack, Randyyy, Rhynorater, Ruhum, ShadowyNoobDev, bitbopper, cccz, cmichel, csanuragjain, danb, gzeon, hickuphh3, hyh, leastwood
31.0722 USDC - $31.07
https://github.com/code-423n4/2022-02-concur/blob/main/contracts/Shelter.sol#L52-L57
tl;dr Anyone who can call withdraw
to withdraw their own funds can call it repeatedly to withdraw the funds of others. withdraw
should only succeed if the user hasn't withdrawn the token already.
The shelter can be used for users to withdraw funds in the event of an emergency. The withdraw
function allows callers to withdraw tokens based on the tokens they have deposited into the shelter client: ConvexStakingWrapper. However, withdraw
does not check if a user has already withdrawn their tokens. Thus a user that can withdraw
tokens, can call withdraw repeatedly to steal the tokens of others.
tl;dr an attacker that can successfully call withdraw
once on a shelter, can call it repeatedly to steal the funds of others. Below is a detailed scenario where this situation can be exploited.
wETH
into ConvexStakingWrapper
using deposit
. Let's also assume that other users have deposited 2 wETH
into the same contract.ConvexStakingWrapper
calls setShelter(shelter)
and enterShelter([pidOfWETHToken, ...])
. Now shelter
has 3 wETH
and is activated for wETH
.shelter.withdraw(wETHAddr, MalloryAddr)
, mallory will rightfully receive 1 wETH because her share of wETH in the shelter is 1/3.shelter.withdraw(wETHAddr, MalloryAddr)
again, receiving 1/3*2 = 2/3 wETH. withdraw
does not check that she has already withdrawn. This time, the wETH does not belong to her, she has stolen the wETH of the other users. She can continue calling withdraw
to steal the rest of the fundsManual inspection.
To mitigate this, withdraw
must first check that msg.sender
has not withdrawn this token before and withdraw
must also record that msg.sender
has withdrawn the token.
The exact steps for this are below:
withdraw
(line 53):require(!claimed[_token][msg.sender], "already claimed")
claimed[_token][msg.sender] = true;
This replacement is necessary because we want to record who is withdrawing, not where they are sending the token which isn't really useful info.
#0 - GalloDaSballo
2022-04-08T13:45:58Z
The warden has identified a logical fallacy in the Shelter
contract.
This would allow a caller to claim their tokens multiple times, as long as they send them to a new address.
Mitigation is as simple as checking claims against msg.sender
, however because all funds can be drained, this finding is of High Severity
🌟 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
124.9798 USDC - $124.98
Example Note that this issue is present in pretty much every other contract in the project.
This is a bad practice because ^0.8.11
means that the contracts can be compiled with solidity versions greater than or equal to 0.8.11 but less than 0.9.0. Because any number of solidity versions can be used, it is possible that the contracts are tested with one version of solidity and deployed to production using another version of solidity, which can lead to issues if there are bugs in the solidity compiler.
Instead, the lines should be replaced with pragma solidity 0.8.11;
so that 0.8.11 is used in production and testing.
informational: safeTransfer
to msg.sender
in StakingRewards.withdraw
should come after all effects.
location
This follows the check-effects-interactions pattern, and is good defense-in-depth (since there function uses nonReentrant
modifier).
Informational: Code layout in StakingRewards doesn't obey conventions, events are on the bottom. Example They should come before functions. Putting events at the end is inconsistent with the other contracts in this repo. It is also inconsistent with solidity's standard conventions
#0 - GalloDaSballo
2022-04-26T21:02:42Z
LOW: Solidity version pragma uses caret. Disagree, the version can be known
informational: safeTransfer to msg.sender in StakingRewards.withdraw should come after all effects.
The sponsor chose to use nonReentrant
, I can make the argument that the propose solution is still vulnerable as the function connects to more than one external contract, for that reason I must disagree
Informational: Code layout in StakingRewards doesn't obey conventions, events are on the bottom. Agree with this as a coding convention
#1 - GalloDaSballo
2022-04-27T15:03:05Z
++
🌟 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
#0 - r2moon
2022-02-15T15:27:46Z
duplicated with https://github.com/code-423n4/2022-02-concur-findings/issues/263
#1 - GalloDaSballo
2022-04-04T14:41:04Z
Valid but 0