_getWinnerShares function is called from the setWinner function and performs iteration through an unbound array. A setWinner transaction cost increases significantly when a lot of participants joined an event and the usersAddress array becomes too big for iteration.
Participants may joinEvent several times with the same countryId for each countryId without an additional deposit, and the usersAddress array grows.
Gas required for the transaction may reach the block gas limit, which will make it impossible to setWinner and lock funds in the contract.
Likelihood: HIGH
The exploit will occur when a lot of participants joined an event, or the same participants join with all possible country ids, or even the same participant joins with the same country id several times, so usersAddress becomes too big to iterate without consequences.
Impact: HIGH
It will be too expensive in terms of gas or even impossible to set a winner of the event, causing the denial of service (DoS) and the impossibility of finishing the event and withdrawing funds.
There are 2 tournaments in the POC:
the first with 4800 members in usersAddress array;
the second with 9600 members in that array;
in both cases, only 100 participants deposited, but they joined several times;
Steps of POC:
Use briVault contract deployed in setUp() in the test suit briVault.t.sol:
Deposit from 100 participants and join these 100 participants in 48 countries 1 time in each country: the usersAddress array will contain 4800 members.
Check participant's balance - the only one deposit was performed, but 48 times joinEvent.
Check participant's shares - the same amount of shares (= minted amount on deposit) is stored for every countryId.
Deploy another instance of BriVault.sol:
Deposit from 100 participants and join these 100 participants in 48 countries 2 times in each country: the usersAddress array will contain 9600 members.
Check participant's balance - the only one deposit was performed but 96 times joinEvent.
Check participant's shares - the same amount of shares (= minted amount on deposit) is stored for every countryId.
Compare gas used for setWinners for 4800 and 9600 members in usersAddress array:
gas used for setWinners for 4800 members of usersAddress array = 6_165_087;
gas used for setWinners for 9600 members of usersAddress array = 12_204_384;
Add the following test to BriVault.t.sol
Do not allow participants to join with the same repeated countryId;
Do not allow duplicates in usersAddress array - push to array on first join only;
The _getWinnerShares() function is intended to iterate through all users and sum their shares for the winning country, returning the total.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.