The protocol lets users lock native Eth or ERC20 tokens. When locking above a certain amount a user is minted an unopened munchables NFT.
A player can lock for himself or on behalf of someone else.
The same lock time is required to be greater than block.timestamp when calling unlock()
function unlock(
address _tokenContract,
uint256 _quantity
) external notPaused nonReentrant {
LockedToken storage lockedToken = lockedTokens[msg.sender][_tokenContract];
if (lockedToken.quantity < _quantity) revert InsufficientLockAmountError();
=> if (lockedToken.unlockTime > uint32(block.timestamp)) revert TokenStillLockedError();
...
This is not a problem if a user locks tokens by himself.
However, it is a problem that an attacker could lock 1 wei on his behalf, because it resets the user's timer. This would stop the user from unlocking his position and retrieving his funds, effectively DoS-ing him.
As per the protocol docs the min lock duration is 30 days. Having that in mind a malicious user could DoS anyone by just locking 1 wei on their behalf every 29 days or just before the lock period ends.
The attack is super cheap (1 wei per lock) for the malicious actor, but stops a user from accessing his funds.
Proof of Concept
Add the following change to tests/run.ts:
- let testGlob = "**/.test.ts";
+ let testGlob = "**/lock.test.ts";
if (process.argv[2]) {
- testGlob = process.argv[2] + ".test.ts";
+ testGlob = process.argv[2] + "lock.test.ts";
}
And add the following test to tests/managers/LockManager/lock.test.ts and run with pnpm run test:typescript
Note that that would run the whole test suit because the --test-only option does not work
...
▶ when zero-address token is configured on the contract (361.3113ms)
Alice unlock time : 1713317686
Alice unlock time after: 1713318688
Default lock duration : 1000
▶ when using lockOnBehalf to cause DoS
✔ test lock dos (63.8206ms)
...
Tools Used
Manual Review, TypeScript
Suggested Mitigation
Remove the lockOnBehalf function alltogether or implement some mechanism that lets users chose who can stake on their behalf
Lines of code
https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L275-L427
Vulnerability details
LockManager.sol::lockOnBehalf()
Impact
The protocol lets users lock native Eth or ERC20 tokens. When locking above a certain amount a user is minted an unopened munchables NFT. A player can lock for himself or on behalf of someone else.
As seen in the code snippets above both functions call
_lock
When the
_lock()
function is called, the unlock time of thelockRecipient
is resetThe same lock time is required to be greater than
block.timestamp
when callingunlock()
This is not a problem if a user locks tokens by himself. However, it is a problem that an attacker could lock 1 wei on his behalf, because it resets the user's timer. This would stop the user from unlocking his position and retrieving his funds, effectively DoS-ing him.
As per the protocol docs the min lock duration is 30 days. Having that in mind a malicious user could DoS anyone by just locking 1 wei on their behalf every 29 days or just before the lock period ends.
The attack is super cheap (1 wei per lock) for the malicious actor, but stops a user from accessing his funds.
Proof of Concept
Add the following change to
tests/run.ts
:And add the following test to
tests/managers/LockManager/lock.test.ts
and run withpnpm run test:typescript
Note that that would run the whole test suit because the
--test-only
option does not workThe output is:
Tools Used
Manual Review, TypeScript
Suggested Mitigation
Remove the
lockOnBehalf
function alltogether or implement some mechanism that lets users chose who can stake on their behalfAssessed type
DoS