You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Oct 1, 2023. It is now read-only.
sherlock-admin opened this issue
Mar 27, 2023
· 0 comments
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
Attacker can rollover more shares than a user's intention or cause the mintRollovers function to be unusable.
Summary
Due to a bug in enlistInRollover function, an attacker can rollover more shares than a user's intention or cause a permanent DoS in the mintRollovers function, making it unusable.
Vulnerability Detail
In the enlistInRollover function of carousel contract,
function enlistInRollover(
uint256_epochId,
uint256_assets,
address_receiver
) publicepochIdExists(_epochId) minRequiredDeposit(_assets) {
// check if sender is approved by ownerif (
msg.sender!= _receiver &&isApprovedForAll(_receiver, msg.sender) ==false
) revertOwnerDidNotAuthorize(msg.sender, _receiver);
// check if user has enough balanceif (balanceOf(_receiver, _epochId) < _assets)
revertInsufficientBalance();
// check if user has already queued up a rolloverif (ownerToRollOverQueueIndex[_receiver] !=0) {
// if so, update the queueuint256 index =getRolloverIndex(_receiver); //@audit index in rolloverQueue for a user is gotten by subtracting 1 from `ownerToRolloverQueueIndex[user]`
rolloverQueue[index].assets = _assets;
rolloverQueue[index].epochId = _epochId;
} else {
// if not, add to queue
rolloverQueue.push(
QueueItem({
assets: _assets,
receiver: _receiver,
epochId: _epochId
})
);
}
ownerToRollOverQueueIndex[_receiver] = rolloverQueue.length; //@audit always updated for every _receiver because it is outside the else blockemitRolloverQueued(_receiver, _assets, _epochId);
}
The public state mapping, ownerToRolloverQueueIndex for a _receiver is always updated to the last index in the rolloverQueue array whenever the function is called. This can allow an attacker to override the epochId and _assets amount that other users originally set.
Let's say an attacker has 500 _assets from an _epochId 1500, and innocent Bob has 200 _assets from an _epochId 1200,
Attacker enlists some _assets for rollover, calling enlistInRollover(1500,500,attackerAddress) which will push QueueItem({assets:500,receiver:attackerAddress,epochId:1500}) to the state array rolloverQueue. Now, ownerToRolloverQueueIndex[attackerAddress] will be set to 1 (i.e. rolloverQueue.length), and the attacker index in the rolloverQueue array is 0
Bob then enlists his 200 _assets, calling enlistInRollover(1200,200,bobAddress) which will push QueueItem({assets:200,receiver:bobAddress,epochId:1200}) to the state array rolloverQueue. ownerToRolloverQueueIndex[bobddress] will be set to 2 (i.e. rolloverQueue.length), and bob's index in the rolloverQueue array is 1
Attacker sees that Bob has been added to rolloverQueue, and then calls enlistInRollover(1500,500,attackerAddress) again. This would check if attacker has already been added to the rolloverQueue, if (ownerToRollOverQueueIndex[_receiver] != 0) , then updates the QueueItem the attacker previously enlisted at index 0, BUT AFTER THAT, ownerToRolloverQueueIndex[attackerAddress] GETS UPDATED AGAIN to the length of the array(in this case, 2).
Attacker will then call the function again with the same parameters, but this time, instead of updating the index position in the rolloverQueue of the attacker(which was originally 0), the index position 1 will be updated because index is calculated as ownerToRolloverQueueIndex[attackerAddress]-1. Therefore, bob's QueueItem is updated to QueueItem({assets:500,receiver:bobAddress,epochId:1500}).
This bug can cause two things:
Remember that Bob initially listed 200 _assets for rollover
If Bob has up to 500 _assets, it gets rolled over against his will.
If Bob does not have up to 500 _assets, there will PERMANENTLY be a DoS in the mintRollover function because _burn(queue[index].receiver,queue[index].epochId,queue[index].assets) (LINE 409) will always revert. (This is more disastrous).
Since Bob can help solve the DoS issue by getting more _assets, Attacker could use an EOA or Contract account instead of Bob to make the attack insoluble
Impact
Attacker can make mintRollovers function unusable by causing a permanent DoS.
Move ownerToRollOverQueueIndex[_receiver] = rolloverQueue.length; in LINE 268 to the else block to prevent updating ownerToRollOverQueueIndex[_receiver] for those that have previously called the function.
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
Emmanuel
high
Attacker can rollover more shares than a user's intention or cause the mintRollovers function to be unusable.
Summary
Due to a bug in enlistInRollover function, an attacker can rollover more shares than a user's intention or cause a permanent DoS in the mintRollovers function, making it unusable.
Vulnerability Detail
In the enlistInRollover function of carousel contract,
The public state mapping,
ownerToRolloverQueueIndex
for a _receiver is always updated to the last index in the rolloverQueue array whenever the function is called. This can allow an attacker to override the epochId and _assets amount that other users originally set.Let's say an attacker has 500 _assets from an _epochId 1500, and innocent Bob has 200 _assets from an _epochId 1200,
Attacker enlists some _assets for rollover, calling
enlistInRollover(1500,500,attackerAddress)
which will pushQueueItem({assets:500,receiver:attackerAddress,epochId:1500})
to the state arrayrolloverQueue
. Now,ownerToRolloverQueueIndex[attackerAddress]
will be set to 1 (i.e. rolloverQueue.length), and the attacker index in the rolloverQueue array is 0Bob then enlists his 200 _assets, calling
enlistInRollover(1200,200,bobAddress)
which will pushQueueItem({assets:200,receiver:bobAddress,epochId:1200})
to the state arrayrolloverQueue
.ownerToRolloverQueueIndex[bobddress]
will be set to 2 (i.e. rolloverQueue.length), and bob's index in the rolloverQueue array is 1Attacker sees that Bob has been added to rolloverQueue, and then calls
enlistInRollover(1500,500,attackerAddress)
again. This would check if attacker has already been added to the rolloverQueue,if (ownerToRollOverQueueIndex[_receiver] != 0)
, then updates the QueueItem the attacker previously enlisted at index 0, BUT AFTER THAT,ownerToRolloverQueueIndex[attackerAddress]
GETS UPDATED AGAIN to the length of the array(in this case, 2).Attacker will then call the function again with the same parameters, but this time, instead of updating the index position in the rolloverQueue of the attacker(which was originally 0), the index position 1 will be updated because index is calculated as
ownerToRolloverQueueIndex[attackerAddress]
-1. Therefore, bob's QueueItem is updated toQueueItem({assets:500,receiver:bobAddress,epochId:1500})
.This bug can cause two things:
Remember that Bob initially listed 200 _assets for rollover
_burn(queue[index].receiver,queue[index].epochId,queue[index].assets)
(LINE 409) will always revert. (This is more disastrous).Since Bob can help solve the DoS issue by getting more _assets, Attacker could use an EOA or Contract account instead of Bob to make the attack insoluble
Impact
Attacker can make mintRollovers function unusable by causing a permanent DoS.
Code Snippet
https://github.com/Y2K-Finance/Earthquake/blob/736b2e1e51bef6daa6a5ecd1decb7d156316d795/src/v2/Carousel/Carousel.sol#L238-L271
Tool used
Manual Review
Recommendation
Move
ownerToRollOverQueueIndex[_receiver] = rolloverQueue.length;
in LINE 268 to the else block to prevent updatingownerToRollOverQueueIndex[_receiver]
for those that have previously called the function.Duplicate of #2
The text was updated successfully, but these errors were encountered: