In the withdrawWinnings()
function, a user is able to wihdraw their winnings stored in pendingWinnings
. In a normal working process, the winnings are securely transferred and the internal state is updated.
In this case however, the contract updates the user’s pendingWinnings
after performing the external call to msg.sender
. This creates a reentrancy vulnerability, where a malicious contract can reenter the same function. As a result, the attacker could repeatedly withdraw more than their actual winnings. Although the withdrawWinnings()
function is protected by the nonReentrant
modifier, this only prevents reentering the same function, it does not protect against cross-function reentrancy.
If another function accesses or modifies the same shared state (pendingWinnings
) and is not protected by nonReentrant
modifier, an attacker can exploit this gap by reentering through that unprotected function during the external call.
Likelihood:
A user can deploy a contract with a fallback or receive function that reenters the withdrawWinnings()
logic.
Another function can also accesses and modify pendingWinnings
, thus increasing the risk for reentrancy.
Impact:
An attacker can repeatedly withdraw winnings before their balance is zeroed, draining the contract’s ETH.
Below is a simplified malicious contract that exploits the reentrancy vulnerability:
}
Use the checks-effects-interactions pattern. Update the internal state before making any external calls. This prevents reentrancy attacks even if the external call triggers a fallback function.
We only audit the current code in scope. We cannot make speculation with respect to how this codebase will evolve in the future. For now there is a nonReentrant modifier which mitigates any reentrancy. CEI is a good practice, but it's not mandatory. Informational
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.