joinEvent() repeatedly, inflating the calculation of totalWinnerShares and depleting withdrawal amounts for winning users while locking remaining assets in the contractWhen users call briVault.joinEvent(countryId), the following variables are updated:
Crucially, users are not prevented from calling joinEvent() multiple times. This allows msg.sender to do two things:
Assign their share balance to userSharesToCountry[msg.sender][countryId] for multiple country IDs without overwriting/deleting previous entries.
Push their address to the usersAddress[] array multiple times.
When the owner calls setWinner(), the internal function _getWinnerShares() is called, which loops over usersAddress[] and adds each address's winning shares to totalWinnerShares:
However, if a winning user had previously called joinEvent() multiple times, then their address will appear in usersAddress[] the same number of times, and their winning share will be added over and over to totalWinningShares, inflating the amount. Alternatively, even if a user didn't win, if they had previously called joinEvent() along with the winning countryId, their shares will be added to totalWinnerShares too, causing a similarly inflated result.
Likelihood: High
Griefers can carry out this attack easily and cheaply in normal conditions by depositing a small amount of assets and repeatedly calling joinEvent() on every countryId to guarantee exploitation of the vulnerability. Moreover, if such an attacker calls joinEvent() 1000 times, then 1000x of the attacker's deposit will be included in totalWinnerShares. This multiplier effect means large attacks can be executed at relatively low costs.
Impact: High
When winning users call withdraw() to claim their winnings, the transferred amount is calculated as follows:
If the vulnerability described above is exploited, then totalWinnerShares will be higher than expected. Consequently, assetToWithdraw will be lower than expected, resulting in reduced winnings for winning users. In extremely inflated cases, winning users may even be unable to recover the full amount of their initial deposit. Any leftover assets (potentially exceeding 90% of deposits) will be locked in the contract permanently, since there is no other way to transfer assets out of the contract without going through the calculation above.
Paste the following into briVault.t.sol:
Check whether msg.sender has joined the event before. For example:
Add a new state variable to briVault.sol:
Then at the start of the joinEvent() function, add:
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.