Open code423n4 opened 1 year ago
At first read, it looks like a great finding considering the project will be on zkSync
.
Picodes marked the issue as primary issue
mattt21 marked the issue as sponsor confirmed
Picodes marked the issue as satisfactory
Picodes marked the issue as selected for report
Picodes changed the severity to 2 (Med Risk)
Picodes changed the severity to 3 (High Risk)
Lines of code
https://github.com/code-423n4/2023-03-mute/blob/4d8b13add2907b17ac14627cfa04e0c3cc9a2bed/contracts/dao/dMute.sol#L90-L129 https://github.com/code-423n4/2023-03-mute/blob/4d8b13add2907b17ac14627cfa04e0c3cc9a2bed/contracts/dao/dMute.sol#L135-L139
Vulnerability details
Impact
This report deals with how an attacker can abuse the fact that he can lock
MUTE
tokens for any other user and thereby push items to the array ofUserLockInfo
structs of the user.There are two functions in the
dMute
contract that iterate over all items in this array (RedeemTo
andGetUnderlyingTokens
).Thereby if the attacker pushes sufficient items to the array of a user, he can make the above two functions revert since they require more Gas than the Block Gas Limit.
According to the
zkSync
documentation the block gas limit is currently 12.5 million (Link).The attack is of "High" impact for the
RedeemTo
function since this function needs to succeed in order for the user to redeem hisMUTE
tokens.The user might have a lot of
MUTE
tokens locked and the attacker can make it such that they can never be redeemed. The attacker cannot gain a profit from this attack, i.e. he cannot steal anything, but due to the possibility of this attack users will not lock their tokens, especially not a lot of them.This is all the more severe because the
MuteBond
andMuteAmplifier
contracts also rely on the locking functionality so those upstream features can also not be used securely.In the Mitigation section I will show how the
GetUnderlyingTokens
function can be made to run in $O(1)$ time instead of $O(lock\:array\:length)$.The
RedeemTo
function can be made to run in $O(indexes\:array\:length)$ instead of $O(lock\:array\:length)$. The length of the indexes array is determined by the user and simply tells how many locked items to redeem. So there is no possibility of DOS.Proof of Concept
Note: a redemption costs
~7 million Gas
when 1000 items are locked. So when running on thezkSync
network even 2000 items should be enough. The hardhat tests use a local Ethereum network instead of a fork ofzkSync
so in order to hit30 million Gas
(which is the Ethereum block gas limit) we need to add more items to the queue.You can add the following test to the
dao.ts
test file:It adds
5000
lock items to the array of theowner
address. When theowner
then tries to redeem even a single lock the transaction fails due to an out of gas error.(Sometimes it reverts with
TransactionExecutionError: Transaction ran out of gas
error sometimes it reverts due to timeout. If you try a few times it should revert with the out of gas error.)The amount of
MUTE
tokens that the attacker loses to execute this attack is negligible. As you can see in the test100 Wei * 5000 = 500,000 Wei
is sufficient (There needs to be some amount ofMUTE
such that theLockTo
function does not revert). The only real cost comes down to Gas costs which are cheap onzkSync
.Tools Used
VSCode
Recommended Mitigation Steps
First for the
GetUnderlyingTokens
function: The contract should keep track of underlying token amounts for each user in a mapping that is updated with every lock / redeem call. TheGetUnderlyingTokens
function then simply needs to return the value from this mapping.Secondly, fixing the issue with the
RedeemTo
function is a bit harder. I discussed this with the sponsor and I have been told they don't want this function to require an already sortedlock_index
array as parameter. So thelock_index
array can contain indexes in random order.This means it must be sorted internally. Depending on the expected length of the
lock_index
array different sorting algorithms may be used. I recommend to use an algorithm like quick sort to allow for many indexes to be specified at once.I will use a placeholder for the sorting algorithm for now so the sponsor may decide which one to use.
The proposed fixes for both functions are then like this: