kuprum - A raffle may be guaranteed won, or canceled despite reaching maxTicketSupply, thus depriving users from winning it #550
Labels
Duplicate
A valid issue that is a duplicate of an issue with `Has Duplicates` label
Medium
A Medium severity issue.
Reward
A payout will be made for this issue
Sponsor Confirmed
The sponsor acknowledged this issue is valid
Will Fix
The sponsor confirmed this issue will be fixed
kuprum
Medium
A raffle may be guaranteed won, or canceled despite reaching maxTicketSupply, thus depriving users from winning it
Summary
Insufficient validation in createRaffle allows to set different nonsensical combinations of raffle parameters. E.g. setting
maxTickets == 1
allows the guaranteed win with a single purchased ticket; or settingminTickets >= maxTickets
allows to cancel the raffle even when the number of bought tickets reachesmaxTickets
, thus depriving the users who bought tickets from winning the raffle.Root Cause
Function createRaffle doesn't perform any validation wrt. the supplied parameters
minTickets
,maxTickets
,maxHoldings
besides checking thatmaxTickets != 0
andmaxHoldings != 0
. Different nonsensical combinations of these parameters may be set, in particular:maxTickets == 1
and bundlingcreateRaffle
andbuyTickets
for a single permitted ticket into the same transaction, will allow an admin to have a guaranteed win of the raffle. If the prize is large, there may be enough motivation to do that.minTickets >= maxTickets
: as in the current Winnable UI theminTickets
doesn't seem to be displayed, the users may participate not knowing of the nonsensical parameters. As we outline below, this combination allows to cancel a raffle even whenmaxTickets
have been bought.Wrt. to the latter, there is a secondary root cause, in functions _checkShouldDraw and _checkShouldCancel:
_checkShouldDraw
reverts ifcurrentTicketSold < raffle.minTicketsThreshold
. Thus even ifmaxTicketSupply
is reached, butminTicketsThreshold >= maxTicketSupply
a winner can't be ever drawn_checkShouldDraw
(currentTicketSold < raffle.minTicketsThreshold
) and_checkShouldCancel
(supply > raffle.minTicketsThreshold
) are non-exclusive: ifmaxTicketSupply == minTicketsThreshold
then both functions don't revert, thus offering a choice between either drawing or canceling the raffle, even whenmaxTicketSupply
has been reached.The case of
minTicketsThreshold == maxTicketSupply
seems actually reasonable, and thus can be set by an non-malicious admin. Taking into account thatcancelRaffle
is a non-admin-restricted function, and can be called by anyone, this setting can be exploited to cancel the raffle if the odds of winning don't seem favorable for to the interested party.Internal pre-conditions
createRaffle
, e.g. to setminTicketsThreshold == maxTicketSupply
External pre-conditions
none
Attack Path
The first attack path (a malicious admin):
createRaffle
withminTickets == 0
andmaxTickets == 1
buyTickets
and buys a single permissible ticketdrawWinner
(which is possible, becausemaxTicketSupply
is reached.The second attack path (a non-malicious admin):
createRaffle
setsminTickets == maxTickets
(which seems reasonable), thus resulting for the raffle to haveminTicketsThreshold == maxTicketSupply
maxTicketSupply
is reachedcancelRaffle
, thus canceling it instead of the winner being drawn.Impact
Depending on the attack path, it's either a guaranteed win (for a malicious admin), or a cancellation of the raffle that has reached the stage when it should be drawn, if the odds don't satisfy any interested party (with a non-malicious admin).
PoC
not required according to the rules.
Mitigation
We propose to perform more validation of the input parameters of
createRaffle
, e.g.maxTickets
to be greater than some reasonable minimum (e.g. 1000)minTickets < maxTickets
maxHoldings < maxTickets
(thus preventing accumulation of the odds of winning by a single address)Different other restrictions may be imposed, depending on the protocol intentions.
Additionally, we recommend to make the revert conditions in functions
_checkShouldDraw
and_checkShouldCancel
mutually exclusive:currentTicketSold <= raffle.minTicketsThreshold
andsupply > raffle.minTicketsThreshold
respectively.Duplicate of #26
The text was updated successfully, but these errors were encountered: