Platform: Code4rena
Start Date: 22/05/2024
Pot Size: $20,000 USDC
Total HM: 6
Participants: 126
Period: 5 days
Judge: 0xsomeone
Total Solo HM: 1
Id: 379
League: ETH
Rank: 30/126
Findings: 3
Award: $0.03
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Circolors
Also found by: 0rpse, 0x175, 0xAadi, 0xHash, 0xMax1mus, 0xMosh, 0xblack_bird, 0xdice91, 0xfox, 0xhacksmithh, 0xloscar01, 0xrex, 4rdiii, Audinarey, AvantGard, Bigsam, DPS, Dots, Drynooo, Dudex_2004, Evo, Kaysoft, King_, Limbooo, MrPotatoMagic, PENGUN, Sabit, SovaSlava, SpicyMeatball, TheFabled, Utsav, Varun_05, Walter, adam-idarrha, araj, aslanbek, ayden, bctester, biakia, bigtone, brgltd, carrotsmuggler, cats, crypticdefense, dd0x7e8, dhank, fandonov, fyamf, grearlake, iamandreiski, ilchovski, jasonxiale, joaovwfreire, lanrebayode77, m4ttm, merlinboii, niser93, nnez, octeezy, oxchsyston, pamprikrumplikas, rouhsamad, tedox, trachev, turvy_fuzz, twcctop, yotov721, zhaojohnson
0.0056 USDC - $0.01
By utilizing the lockOnBehalf
function any user can be prevented from ever unlocking their locked tokens. The attack can be continuously performed as the cost of execution will be extremely low.
The lockOnBehalf
function allows any user to lock tokens on behalf of another user. The issue is that when lockOnBehalf
is called, the recipient of the locked tokens has their lockedToken.unlockTime
reset to block.timestamp + _lockDuration
:
lockedToken.unlockTime = uint32(block.timestamp) + uint32(_lockDuration);
As the unlock
function reverts if lockedToken.unlockTime > block.timestamp
, the following attack opportunity opens:
1/ User locks tokens for 90 days.
2/ 90 days pass and the user calls unlock
.
3/ An attacker front-runs the call to unlock
, locking 1 wei of the locked token on behalf of the user.
4/ lockOnBehalf
resets the lockedToken.unlockTime
to 90 more days in the future and unlock
fails to execute.
5/ The victim cannot unlock their tokens for 90 more days and is likely to be front-runned again in the future.
It's important to note that the attacker may not even need to supply the 1 wei of the locked token as calling lockOnBehalf
with quantity
equal to 0 will also execute successfully.
Manual review
Users should be able to specify which addresses are allowed to lock on their behalf.
DoS
#0 - c4-judge
2024-06-05T12:59:14Z
alex-ppg marked the issue as satisfactory
🌟 Selected for report: Circolors
Also found by: 0rpse, 0x175, 0xAadi, 0xHash, 0xMax1mus, 0xMosh, 0xblack_bird, 0xdice91, 0xfox, 0xhacksmithh, 0xloscar01, 0xrex, 4rdiii, Audinarey, AvantGard, Bigsam, DPS, Dots, Drynooo, Dudex_2004, Evo, Kaysoft, King_, Limbooo, MrPotatoMagic, PENGUN, Sabit, SovaSlava, SpicyMeatball, TheFabled, Utsav, Varun_05, Walter, adam-idarrha, araj, aslanbek, ayden, bctester, biakia, bigtone, brgltd, carrotsmuggler, cats, crypticdefense, dd0x7e8, dhank, fandonov, fyamf, grearlake, iamandreiski, ilchovski, jasonxiale, joaovwfreire, lanrebayode77, m4ttm, merlinboii, niser93, nnez, octeezy, oxchsyston, pamprikrumplikas, rouhsamad, tedox, trachev, turvy_fuzz, twcctop, yotov721, zhaojohnson
0.0056 USDC - $0.01
By utilizing the lockOnBehalf
function any user can be prevented from ever updating their lock duration. The attack can be continuously performed as the cost of execution will be extremely low.
The lockOnBehalf
function allows any user to lock tokens on behalf of another user. The issue is that when lockOnBehalf
is called, the recipient of the locked tokens has their lockedToken.unlockTime
reset to block.timestamp + _lockDuration
:
lockedToken.unlockTime = uint32(block.timestamp) + uint32(_lockDuration);
As the setLockDuration
function reverts if:
if ( uint32(block.timestamp) + uint32(_duration) < lockedTokens[msg.sender][tokenContract].unlockTime ) { revert LockDurationReducedError(); }
An attacker can front-run the call to unlock
, locking 1 wei of the locked token (or even 0) on behalf of the user. Thus increasing their unlockTime
, causing the execution to revert. The victim will not be able to update their lock duration for a prolonged period of time and is likely to be front-runned again in the future.
Manual review
Users should be able to specify which addresses are allowed to lock on their behalf.
DoS
#0 - c4-judge
2024-06-05T12:59:13Z
alex-ppg marked the issue as satisfactory
#1 - c4-judge
2024-06-05T13:00:02Z
alex-ppg changed the severity to 3 (High Risk)
🌟 Selected for report: SpicyMeatball
Also found by: 0rpse, 0xMosh, 0xblack_bird, 0xdice91, 0xhacksmithh, 0xleadwizard, 0xmystery, Audinarey, AvantGard, Bigsam, Dots, EPSec, Eeyore, Janio, Limbooo, LinKenji, Mahmud, MrPotatoMagic, Myd, Oxsadeeq, Sabit, SovaSlava, Stefanov, Tychai0s, Utsav, Varun_05, Walter, adam-idarrha, ahmedaghadi, araj, aslanbek, ayden, bigtone, c0pp3rscr3w3r, carrotsmuggler, crypticdefense, dhank, fyamf, gajiknownnothing, gavfu, itsabinashb, jasonxiale, joaovwfreire, ke1caM, leegh, merlinboii, mitko1111, n4nika, pfapostol, prapandey031, rouhsamad, sandy, snakeeaterr, stakog, steadyman, swizz, tedox, th3l1ghtd3m0n, trachev, turvy_fuzz, xyz, yashgoel72, zhaojohnson
0.014 USDC - $0.01
In the setLockDuration
there is a check to make sure that the lock duration is not reduced while there are still tokens that are locked. The issue is that the validation is insufficient.
As we can see, this check makes sure that the new duration of the lock is not less than the previous one:
if ( uint32(block.timestamp) + uint32(_duration) < lockedTokens[msg.sender][tokenContract].unlockTime ) { revert LockDurationReducedError(); }
The problem is that the previous unlockTime
is compared to the current time plus the new duration when it should be compared to the time of the last lock plus the new duration. This allows for the new unlockTime
of the currently locked tokens to be less than the previous one. We understand from the validation above that this should not be permitted. Here is an example:
1/ The current lock duration of some locked tokens is 90 days
2/ We will assume, for simplicity, that the tokens were locked on block.timestamp
equal to 0 days
3/ 40 days pass and the user decides to reduce the lock duration to 60 days
4/ The call does not revert in the check above as the current timestamp is at 40 days, which added to the new duration is more than the unlock time (100 days > 90 days)
5/ The unlock time is now set to 0 days (lastLockTime
) plus 60 days, equal to 60 days
6/ Therefore, the lock duration was decreased successfully, even though from the validation shown above we can see that this should not be allowed.
Manual review
There are two ways to solve this issue depending on the protocols intentions:
a/ The check above should use: lastLockTime + uint32(_duration) < lockedTokens[msg.sender][tokenContract].unlockTime
OR
b/ The new unlockTime
should be set to block.timestamp + uint32(_duration)
Other
#0 - c4-judge
2024-06-04T12:40:49Z
alex-ppg marked the issue as duplicate of #89
#1 - c4-judge
2024-06-05T12:54:01Z
alex-ppg marked the issue as satisfactory
#2 - c4-judge
2024-06-05T12:54:34Z
alex-ppg changed the severity to 3 (High Risk)
🌟 Selected for report: robertodf99
Also found by: 0xAadi, 0xAkira, 0xdice91, 0xhacksmithh, 0xleadwizard, AgileJune, Bauchibred, Bbash, Beosin, Bigsam, Dots, EPSec, EaglesSecurity, Eeyore, Evo, John_Femi, Mahmud, MrPotatoMagic, RotiTelur, Rushkov_Boyan, Sabit, Sentryx, Stormreckson, Topmark, Tychai0s, Utsav, Walter, ZanyBonzy, ZdravkoHr, adam-idarrha, araj, aslanbek, avoloder, bigtone, brevis, brgltd, carrotsmuggler, crypticdefense, dd0x7e8, dhank, djanerch, falconhoof, iamandreiski, joaovwfreire, leegh, merlinboii, mitko1111, pamprikrumplikas, pfapostol, prapandey031, swizz, trachev, twcctop, typicalHuman, unique, xyz
0.0148 USDC - $0.01
A price feed can either approve or disapprove the USD price of a locked token. The issue is that currently, a price feed can vote for both of the options at the same time, rendering the price feed's vote pointless.
As we can see the disapproveUSDPrice
function reverts if the price feed has already approved or disapproved the current price proposal:
if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId) revert ProposalAlreadyApprovedError(); if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId) revert ProposalAlreadyDisapprovedError();
The second check is missing in the approveUSDPrice
function.
Manual review
Make sure that the price feed has not already disapproved the price proposal in approveUSDPrice
.
Other
#0 - 0xinsanity
2024-05-30T23:00:34Z
Should be low-risk.
Fix here: https://github.com/munchablesorg/munchables-common-core-latest/commit/06caf27
#1 - CloudEllie
2024-05-31T16:21:33Z
Validators marked as dupe of #104 -- leaving open as sponsor confirmed
#2 - CloudEllie
2024-06-03T10:35:30Z
Closing at judge's request
#3 - c4-judge
2024-06-05T12:42:50Z
alex-ppg marked the issue as satisfactory