sherlock-audit / 2024-02-optimism-2024-judging

5 stars 4 forks source link

haxatron - Anchor state registry can be corrupted which will prevent game creation of the same type. #64

Closed sherlock-admin4 closed 5 months ago

sherlock-admin4 commented 6 months ago

haxatron

medium

Anchor state registry can be corrupted which will prevent game creation of the same type.

Summary

Anchor state registry can be corrupted which will prevent game creation of the same type.

Vulnerability Detail

When the dispute game is initialized, the anchor root and the root block number will be pulled out of the anchor state registry and a few checks will be done.

FaultDisputeGame.sol#L513-L575

    function initialize() public payable virtual {
        ...
        // Grab the latest anchor root.
        (Hash root, uint256 rootBlockNumber) = ANCHOR_STATE_REGISTRY.anchors(GAME_TYPE);

        // Should only happen if this is a new game type that hasn't been set up yet.
=>      if (root.raw() == bytes32(0)) revert AnchorRootNotFound();

        // Set the starting output root.
        startingOutputRoot = OutputRoot({ l2BlockNumber: rootBlockNumber, root: root });

        // Do not allow the game to be initialized if the root claim corresponds to a block at or before the
        // configured starting block number.
=>      if (l2BlockNumber() <= rootBlockNumber) revert UnexpectedRootClaim(rootClaim());
        ...
    }

Therefore, if these values become corrupted, it is possible to prevent the creation of new, valid dispute games as the checks will revert. For instance, if attacker is able to corrupt the anchor root to bytes32(0), revert occurs. Similarly, if they can corrupt anchor root block number to a high number then revert occurs and it will be impossible to create new dispute games of the same type.

Because we know that an invalid dispute game might get wrongly accepted as valid, and there is no air-gap between the resolution of a game and the updating of the anchor state registry. The anchor state registry can become corrupted with these values, which will prevent the creation of any new dispute game types.

FaultDisputeGame.sol#L386-L402

    function resolve() external returns (GameStatus status_) {
        // INVARIANT: Resolution cannot occur unless the game is currently in progress.
        if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();

        // INVARIANT: Resolution cannot occur unless the absolute root subgame has been resolved.
        if (!subgameAtRootResolved) revert OutOfOrderResolution();

        // Update the global game status; The dispute has concluded.
        status_ = claimData[0].counteredBy == address(0) ? GameStatus.DEFENDER_WINS : GameStatus.CHALLENGER_WINS;
        resolvedAt = Timestamp.wrap(uint64(block.timestamp));

        // Update the status and emit the resolved event, note that we're performing an assignment here.
        emit Resolved(status = status_);

        // Try to update the anchor state, this should not revert.
        ANCHOR_STATE_REGISTRY.tryUpdateAnchorState();
    }

AnchorStateRegistry.sol#L59-L87

    function tryUpdateAnchorState() external {
        // Grab the game and game data.
        IFaultDisputeGame game = IFaultDisputeGame(msg.sender);
        (GameType gameType, Claim rootClaim, bytes memory extraData) = game.gameData();

        // Grab the verified address of the game based on the game data.
        // slither-disable-next-line unused-return
        (IDisputeGame factoryRegisteredGame,) =
            DISPUTE_GAME_FACTORY.games({ _gameType: gameType, _rootClaim: rootClaim, _extraData: extraData });

        // Must be a valid game.
        require(
            address(factoryRegisteredGame) == address(game),
            "AnchorStateRegistry: fault dispute game not registered with factory"
        );

        // No need to update anything if the anchor state is already newer.
        if (game.l2BlockNumber() <= anchors[gameType].l2BlockNumber) {
            return;
        }

        // Must be a game that resolved in favor of the state.
        if (game.status() != GameStatus.DEFENDER_WINS) {
            return;
        }

        // Actually update the anchor state.
        anchors[gameType] = OutputRoot({ l2BlockNumber: game.l2BlockNumber(), root: Hash.wrap(game.rootClaim().raw()) });
    }

Clarification from sponsor for this issue was:

Yes this a known issue, if the game resolves incorrectly (rather than not resolving at all) then it would likely be necessary to change the respected game type in the OptimismPortal as a result of this corruption

However, I am still submitting this anyway in case somehow a duplicate of this gets accepted.

Impact

Anchor state registry can be corrupted which will prevent game creation of the same type.

Code Snippet

https://github.com/sherlock-audit/2024-02-optimism-2024/blob/main/optimism/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L386-L402

Tool used

Manual Review

Recommendation

The protocol's way of remediating is updating the game type in both the optimism portal 2 and the anchor state registry

Duplicate of #90

Haxatron commented 5 months ago

Escalate

Please take a look. This is a dupe of #90.

sherlock-admin2 commented 5 months ago

Escalate

Please take a look. This is a dupe of #90.

You've created a valid escalation!

To remove the escalation from consideration: Delete your comment.

You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final.

nevillehuang commented 5 months ago

Agree with the escalation, this is a dupe of #90, although the validity/severity of the issue is pending.

Evert0x commented 5 months ago

Will accept escalation but severity is dependent on outcome of 90

Evert0x commented 4 months ago

Result: Medium Duplicate of #90

sherlock-admin2 commented 4 months ago

Escalations have been resolved successfully!

Escalation status: