The RockPaperScissors
contract contains a High severity reentrancy vulnerability affecting multiple functions responsible for transferring ETH: withdrawFees
, _finishGame
, _handleTie
, and _cancelGame
. These functions utilize low-level .call{value: ...}
for ETH transfers but lack adequate protection against reentrancy attacks, specifically the nonReentrant
modifier provided by standard guards like OpenZeppelin's ReentrancyGuard
. This absence allows a malicious contract receiving ETH to potentially call back into the RockPaperScissors
contract during the transfer, enabling scenarios that could lead to state corruption or the loss/theft of protocol fees or player funds (winnings/refunds).
The vulnerability stems from the use of low-level external calls (.call{value: _amount}("")
) for transferring Ether within the RockPaperScissors.sol
contract without implementing a reentrancy lock mechanism. Specifically, the following functions are affected:
withdrawFees()
: Transfers accumulated protocol fees to the admin address. Located in src/RockPaperScissors.sol
.
_finishGame()
: An internal function that transfers the prize pool (less fees) to the game winner. Located in src/RockPaperScissors.sol
.
_handleTie()
: An internal function that transfers refunds (less fees) back to both players in case of a tie. Located in src/RockPaperScissors.sol
.
_cancelGame()
: An internal function that transfers the original bets back to players if a game is cancelled (e.g., via timeout). Located in src/RockPaperScissors.sol
.
None of these functions utilize a standard reentrancy guard, such as OpenZeppelin's ReentrancyGuard
and its nonReentrant
modifier.
When .call{value: ...}
is used to send ETH to an external address, if the recipient is a contract, its receive()
or fallback()
function is executed immediately within the same transaction context. This allows the recipient contract to make calls back into the RockPaperScissors
contract before the execution of the original function (withdrawFees
, _finishGame
, etc.) has completed. This re-entry can bypass checks or manipulate state based on the assumption that the initial function call would complete atomically, potentially leading to unexpected behavior or exploitation. While some functions like withdrawFees
may implement the Checks-Effects-Interactions (CEI) pattern internally (updating state before the call), this does not prevent the re-entry itself, which is the core vulnerability allowing potential cross-function interaction exploits.
PoC:
PoC Result:
The primary impact of this vulnerability is the potential loss of funds, affecting both protocol fees and user assets (player bets/winnings). While the simple reentrancy exploit against withdrawFees
was mitigated by the Checks-Effects-Interactions (CEI) pattern observed in that function, the lack of a nonReentrant
guard still presents significant risks:
Theft of Fees/Winnings/Refunds: A malicious contract acting as an admin or player, upon receiving ETH from withdrawFees
, _finishGame
, _handleTie
, or _cancelGame
, can re-enter the contract. If CEI is not perfectly implemented in all payout/refund logic paths, or if a more complex reentrancy pattern bypasses the checks, an attacker could potentially withdraw more funds than entitled, draining protocol fees or player winnings/refunds.
State Corruption: The ability to execute arbitrary code via re-entrancy before the initial function completes allows an attacker to potentially call other functions within the RockPaperScissors
contract. This could lead to inconsistent game states (e.g., cancelling a game during payout, interfering with score updates, manipulating commit/reveal phases), denial of service for specific games, or incorrect prize distribution. Our second PoC demonstrated that an admin function (withdrawFees
) could indeed be called during a player payout (_finishGame
).
Protocol Trust: A successful exploit resulting in fund loss would severely damage user trust and the reputation of the Rock Paper Scissors DApp.
The vulnerability affects core financial operations (fee handling, prize distribution, refunds), making its potential impact High.
Manuel code review
Forge foundry
The primary recommendation is to implement a reentrancy guard to prevent recursive calls into functions performing external ETH transfers. The widely adopted ReentrancyGuard
contract from OpenZeppelin is recommended.
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.