Skip to content

Latest commit

 

History

History
69 lines (47 loc) · 2.89 KB

081.md

File metadata and controls

69 lines (47 loc) · 2.89 KB

Shambolic Banana Barbel

Medium

Owner can arbitrarily finalize batches by manipulating importGenesisBatch()

Summary

An incorrect check in importGenesisBatch() allows an owner to maintain the ability to finalize any batch at any time with no challenge process, breaking the security guarantees of the chain.

Root Cause

In Rollup.sol, the importGenesisBatch() function is used to allow the owner to import the first batch without a ZK proof. This gives a starting point for future ZK proofs to be performed against.

The following check is intended to ensure that the owner can only import the first batch, and not skip the proving process for future batches:

require(finalizedStateRoots[0] == bytes32(0), "genesis batch imported");

However, there is no guarantee that the genesis batch imported is batch 0. As we can see, the batchIndex is determined by reading the batch header argument, and we simply finalize whatever batch is passed.

This allows an owner to pass a valid, non-zero index batch to start the chain, arousing no suspicion. However, this leaves the door open that importGenesisBatch() can be called at any time in the future to immediately finalize any batch, breaking the security promises of the chain.

Internal Preconditions

  1. Owner needs to call importGenesisBatch() with a non-zero batch index.

External Preconditions

None

Attack Path

  1. Owner calls importGenesisBatch() with a non-zero batch index.
  2. Later, at any point, they can call importGenesisBatch() again with a different value, immediately finalizing any batch they want with no challenge process.

Impact

The owner can arbitrarily finalize any batch, breaking the security guarantees of the chain.

PoC

The following test can be added to Rollup.t.sol to demonstrate the attack:

function testPapa_doubleImportGenesis() external {
    bytes memory batchHeader = new bytes(249);

    // import genesis batch first
    assembly {
        mstore8(add(batchHeader, add(0x20, 8)), 1)
        mstore(add(batchHeader, add(0x20, 25)), 1)
        mstore(add(batchHeader, add(0x20, 57)), 0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014)
        mstore(add(batchHeader, add(0x20, 121)), 1) // postStateRoot
        mstore(add(batchHeader, add(0x20, 217)), 0) // parentBatchHash
    }
    hevm.startPrank(multisig);
    rollup.importGenesisBatch(batchHeader);
    assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(1)));

    assembly {
        mstore(add(batchHeader, add(0x20, 121)), 420) // postStateRoot
    }
    rollup.importGenesisBatch(batchHeader);
    assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(420)));
}

Mitigation

Enforce a check that batchIndex == 0 in importGenesisBatch(), which will ensure it can only be called once.