Open sherlock-admin3 opened 2 months ago
Escalate
Permanent DoS of drawWinner
for a subset of drawable raffles (which is arguably a time-sensitive function).
Breaks core contract functionality (drawing a winner) for a subset of drawable raffles, (arguably) rendering the contract useless.
Loss of funds in the form of lost EV for all participants if total value of the tickets was smaller than the prize.
Escalate
Permanent DoS of
drawWinner
for a subset of drawable raffles (which is arguably a time-sensitive function).Breaks core contract functionality (drawing a winner) for a subset of drawable raffles, (arguably) rendering the contract useless.
Loss of funds in the form of lost EV for all participants if total value of the tickets was smaller than the prize.
You've created a valid escalation!
To remove the escalation from consideration: Delete your comment.
You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final.
The root cause is indeed valid, but what's the impact here? Only if someone calls cancelRaffle
before drawWinner
the raffle will be canceled?
refundPlayers
can be called to refund the biddersThe impact is LOW, and the likelihood is between Low/medium as it requires both:
minTicketsThreshold
cancelRaffle
before drawWinner
According to Sherlock rules:
DoS has two separate scores on which it can become an issue:
- The issue causes locking of funds for users for more than a week.
- The issue impacts the availability of time-sensitive functions (cutoff functions are not considered time-sensitive). If at least one of these are describing the case, the issue can be a Medium. If both apply, the issue can be considered of High severity. Additional constraints related to the issue may decrease its severity accordingly.
Both of the above apply:
drawWinner
can't be called anymore, i.e. it becomes unavailable. As this function can be called only before the raffle is canceled, it is time-sensitive.Thus, according to the Sherlock rules this may be even a valid High; but it is clearly at least a valid Medium.
A fixed is desired to perfect the conditional check but it's deemed low given the trivial change needed.
@mystery0x I would kindly ask to point to the Sherlock docs which allow to qualify the finding as Low if the fix is trivial.
To the best of my knowledge no such docs exist. Moreover, they can't exist. There are literally billions of funds lost due to bugs with trivial fixes. As an example, take a look at the Wormhole Bridge Exploit Incident Analysis. The exploit was for over $320M, but the fix is trivial: replace the deprecated function load_current_index with load_current_index_checked
.
It's not about whether the fix is trivial or not, the impact is low and the likelihood is low/medium. This doesn't quality for Medium severity
It's not about whether the fix is trivial or not, the impact is low and the likelihood is low/medium. This doesn't quality for Medium severity
To that I have already elaborated above; have nothing to add. The severity is at least Medium; arguably can be a High.
@kuprumxyz about your points in the previous comment:
The user funds are locked for more than a week: From the link I supplied in my finding, Long Running Raffle (scroll under the "Activity" tab): user funds have been locked for a month under this raffle. Anyone can cancel the raffle, thus the funds have been locked for so long in vein
I don't think this "lock" can be considered a DOS here, because it's how the protocol should work, i.e. you enter the raffle and your funds are locked until the raffle has finished or gets cancelled. Moreover, I don't think the finding is about this, the finding is about cancelling the raffle before the winner was drawn and I don't think any funds are locked here, since as noted above anyone can call refundPlayers
.
The issue impacts the availability of time-sensitive functions: cancelling a raffle means that drawWinner can't be called anymore, i.e. it becomes unavailable. As this function can be called only before the raffle is canceled, it is time-sensitive
But, in that sense, just cancelling the raffle is DOSing the drawing of the winner. Could you elaborate a bit more on why you consider this a time-sensitive function, I don't think the ability to cancel a raffle, makes the function of picking a winner time-sensitive.
@WangSecurity the scenario is as follows:
minTicketsThreshold
.We see that "the user funds are locked for more than a week". Yes, this is how the protocol works, but the point is that the users lock their funds not just because of some weird desire to lock them for a month; they lock them because each of them wishes to participate in a raffle, and may be win. Depriving them of the right to draw a winner, and of winning a raffle, is the same as DoS of their funds for a month.
But, in that sense, just cancelling the raffle is DOSing the drawing of the winner. Could you elaborate a bit more on why you consider this a time-sensitive function, I don't think the ability to cancel a raffle, makes the function of picking a winner time-sensitive.
Point 4. above is the bifurcation point, at which only one of the two alternatives is possible: either the winner will be drawn xor the raffle will be canceled. From the time point when one happens, another can't happen anymore; thus drawing a winner is time-sensitive for this specific combination of parameters.
@WangSecurity, here is another point to consider:
If the above scenario happens, i.e. users were waiting for a month, and then the raffle got canceled though it should have been drawn, the trust of users into the protocol will be severely undermined, and most likely they won't like to participate anymore. This may easily lead to the protocol collapse.
I would like to add that in some raffles, at the end of the buying period, someone will buy just enough tickets so the raffle reaches minTicketsThreshold, reasonably expecting that it would guarantee that the raffle will be drawn.
@WangSecurity, here is another point to consider:
If the above scenario happens, i.e. users were waiting for a month, and then the raffle got canceled though it should have been drawn, the trust of users into the protocol will be severely undermined, and most likely they won't like to participate anymore. This may easily lead to the protocol collapse.
This is not a valid impact.
refundPlayers
can be called to refund the biddersThe likelihood is between Low/medium as it requires both:
minTicketsThreshold
cancelRaffle
before drawWinner
@aslanbekaibimov
I would like to add that in some raffles, at the end of the buying period, someone will buy just enough tickets so the raffle reaches minTicketsThreshold, reasonably expecting that it would guarantee that the raffle will be drawn.
Even in such a case, if someone calls drawWinner
, the draw will happen.
@Brivan-26
Even in such a case, if someone calls drawWinner, the draw will happen.
It will happen only if cancelRaffle
was not called. For raffles that reached minTicketsThreshold
, drawWinner
should always be available.
After additionally considering this, indeed the raffle shouldn't be cancellable when the minTickets threshold is reached. Therefore, this is a break of the contract functionality, since you can cancel the raffle when you shouldn't be able to. Hence, it qualifies for the following:
Breaks core contract functionality, rendering the contract useless or leading to loss of funds.
Planning to accept the escalation and validate with medium severity. This will be the main issue of the new family, the duplicates are:
Note: if there are other missing duplicates, let me know. Also, big thanks to Watsons who didn't escalate their issues and just flagged under the escalation.
I see there are 2 issues, but I believe this report focuses slightly more on this issue. FYI, in the future, put these as two issues, since the first scenario breaks the README statement, while the second is a different issue. Hence, in this contest, it would have been 2 valid issues, but we can't separate one report and reward it twice
@WangSecurity, thank you; the advice is highly appreciated.
hey @WangSecurity
@WangSecurity the mentioned comment says
Minimum number of tickets required to be sold for this raffle
Which means the raffle can draw a winner if this amount is reached, which is true. The only thing enforced by this statement is that if not enough tickets are bought, the round can not be drawn, which holds. Just because the round may also be cancelled at the exact same number of tickets, it does not mean the comment is broken.
There is no vulnerability in allowing the round to be both drawable and cancelled when the tickets are exactly minTicketsThreshold
, unless there was a comment specifically saying that rounds should be cancelled only when minTicketsThreshold
is exceeded, which is not the case.
We should assume people act in their best interest and if they want to ensure the round is drawn, they can just buy the number of tickets left to minTicketsThreshold + 1
to guarantee it is not cancelled. This is because it is only possible to cancel after the raffle ends, so they have plenty of time to do so.
I didn't say the comment is broken and that's why the issue is validated. The problem here is that the intended design of the protocol is that the raffle shouldn't be cancellable when the min tickets threshold requirement is passed (i.e. tickets == minTicketsThreshold). Therefore, this issue qualifies for medium severity since it breaks the functionality (the raffle can be cancelled when it shouldn't be) and thus the raffle that should've been finalised, wouldn't be finalised:
Breaks core contract functionality, rendering the contract useless or leading to loss of funds.
The decision remains the same, accept the escalation and validate with medium severity. The duplication list has been updated.
@WangSecurity could you elaborate on this part?
We should assume people act in their best interest and if they want to ensure the round is drawn, they can just buy the number of tickets left to minTicketsThreshold + 1 to guarantee it is not cancelled. This is because it is only possible to cancel after the raffle ends, so they have plenty of time to do so.
Due to this, this will never happen unless users want to, which is fair.
Also, this boils down to just 1 difference in the number of tickets needed to cancel, I don't see how this is breaking core functionality as it is a very small error.
hey @WangSecurity i don't see my report (#114) in the duplicate list
We should assume people act in their best interest and if they want to ensure the round is drawn, they can just buy the number of tickets left to minTicketsThreshold + 1 to guarantee it is not cancelled. This is because it is only possible to cancel after the raffle ends, so they have plenty of time to do so
It's fair argument, but still, the scenario where the bought tickets equal exactly the minimum tickets threshold and no one wants to buy more is completely viable. I understand that it's only one ticket difference, but this can fairly happen. About the broken functionality, the raffle shouldn't be cancellable when the tickets reach that min threshold, but this report clearly shows how it can be reached. Thus, the raffle that should've been drawn, got cancelled. That's why it qualifies for medium severity as a broken core contract functionality, rendering the contract useless.
The decision remains, accept the escalation and validate with medium severity. The duplication list has been updated.
Result: Medium Has duplicates
@WangSecurity https://github.com/sherlock-audit/2024-08-winnables-raffles-judging/issues/270 The above is also a dup of this.
The protocol team fixed this issue in the following PRs/commits: https://github.com/Winnables/public-contracts/pull/23
aslanbek
Medium
Anyone can cancel a raffle with tickets == minTicketsThreshold, griefing all participants
Summary
As we can see, once the ticket sale is over, if exactly
minTicketsThreshold
were sold, bothcancelRaffle
anddrawWinner
are available. If anyone (e.g. a participant who does not want to participate anymore, or a griefer) manages to callcancelRaffle
before anyone callsdrawWinner
for thatraffleId
, it would cancel a raffle that should have been drawn, according to the code comment.Root Cause
_checkShouldCancel
does not revert whensupply == raffle.minTicketsThreshold
.Internal pre-conditions
currentTicketSold == raffle.minTicketsThreshold
block.timestamp >= raffle.endsAt
raffleStatus == RaffleStatus.IDLE
Attack Path
Anyone calls
cancelRaffle
whenblock.timestamp >= raffle.endsAt
, and beforedrawWinner
is called.Impact
Raffle that should be drawn is cancelled.
Mitigation