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: 81/126
Findings: 1
Award: $0.01
🌟 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
https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L401 https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L275 https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L382-L384
When a user wants to unlock their tokens, the function makes sure the unlockTime
has passed:
if (lockedToken.unlockTime > uint32(block.timestamp)) revert TokenStillLockedError();
The problem is that anyone can lock tokens on behalf of someone else, and when a new lock is made, the unlockTime
is refreshed with a new duration:
lockedToken.unlockTime = uint32(block.timestamp) + uint32(_lockDuration);
Not only that, but there is no require(_quantity > 0)
check either, so an attacker can virtually grief any vicitm they choose from ever unlocking their tokens and the only cost of attack is paying gas. It's a permanent loss of funds and it costs only gas to make the attack so I believe high is appropriate.
Simple issue
Manual review
Even if a minimum amount of 1 wei was set, this would still cost nothing and can be pulled off. One idea is to only allow someone to lock on behalf of someone else, if they have previously approved them through a function.
DoS
#0 - c4-judge
2024-06-05T12:59:19Z
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
https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L381 https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L256-L260 https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L265-L267
When a user locks their tokens, the timestamp of their lock is logged as lastLockTime
:
lockedToken.lastLockTime = uint32(block.timestamp);
If a user wants to update their lock duration (as long as it's not a timestamp before the already set unlockTime
), they can call setLockDuration()
in order to update their unlock time:
lockedTokens[msg.sender][tokenContract].unlockTime = lastLockTime + uint32(_duration);
So when a user wants to set a new lock duration, the unlockTime
is actually updated to lastLockTime + _duration
and not block.timestamp + _duration
. As long as the new updated duration is > the already set unlocked time, then all is good and it will be updated. The issue comes from the fact that when someone locks tokens on behalf of someone else, their lastLockTime
is updated again, so an attacker can grief their victim, let's follow a simple example to demonstrate:
unlockTime
is set to 30 days from now, her lastLockTime
is set to block.timestamp (let's call it 0th day)unlockTime = lastLockTime + _duration
, so intention is to set to 35 days from the original 0th day instead of 30amount == 0
which updates the lastLockTime
to block.timestampunlockTime = block.timestamp + _duration
, which is now a new 35 days from now, making the total lock time she has to wait 25 passed days + 35 new ones due to attack
, she is forced to wait her lock time for a total of 60 days instead of her intended 35 daysThe attacker can front run her or just spam 0 amount locks repeatedly and the grief is achieved. This is both possible with native msg.value 0, and token amount 0.
Fairly simple, can provide PoC if requested
Manual review
Even if a check for require(_quantity > 0)
is implemented, this can still be achieved with 1 wei, but at least its a good starting point. Deeper refactoring of the code might be needed to come up with a foolproof solution.
Timing
#0 - c4-judge
2024-06-05T12:59:17Z
alex-ppg marked the issue as partial-75
#1 - c4-judge
2024-06-05T13:00:01Z
alex-ppg changed the severity to 3 (High Risk)