Summary
Games can become permanently stuck if one player commits their move but the second player never does. This occurs because the revealDeadline is only set after both players commit, leaving no timeout mechanism for the commit phase itself once the game state has transitioned to Committed.
Vulnerability Details
The commitMove function handles the transition from Created to Committed state and the setting of the revealDeadline:
When the first player commits (and Player B has joined), the state is potentially changed:
However, the revealDeadline, which is crucial for the timeoutReveal function, is only set later in the same function, after checking if both players have committed:
If Player A commits (changing the state to Committed), but Player B never calls commitMove, the condition game.commitA != bytes32(0) && game.commitB != bytes32(0) will never be true. Consequently, game.revealDeadline remains 0.
timeoutReveal cannot be called because it requires block.timestamp > game.revealDeadline, which fails if revealDeadline is 0 or hasn't passed.
cancelGame cannot be called because it requires game.state == GameState.Created, but the state is now Committed.
timeoutJoin cannot be called because Player B has joined (otherwise, the state wouldn't have changed to Committed on the first commit).
The game is therefore stuck in the Committed state with no path forward to resolution or cancellation.
Impact
Permanent Game Stall: The game cannot proceed or be cancelled, becoming permanently unusable.
Locked Funds/Tokens: The ETH or WinningTokens deposited by both Player A and Player B for the game remain locked in the contract indefinitely.
Tools Used
Manual code review.
Recommendations
Introduce a timeout specifically for the commit phase after the game state becomes Committed.
Add commitDeadline to Game struct:
Set commitDeadline when state becomes Committed: Modify commitMove to set this deadline when the first player commits and the state changes. Use a reasonable duration (e.g., similar to joinTimeout or timeoutInterval).
Create timeoutCommit function: Add a new external function allowing either player (or potentially anyone) to cancel the game if the commitDeadline passes and only one player has committed.
Adjust commitMove checks: Ensure commitMove also checks against commitDeadline if applicable.
Attack allows a player to reveal their move for the next turn before the opponent commits
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.