Shambolic Banana Barbel
Medium
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.
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.
- Owner needs to call
importGenesisBatch()
with a non-zero batch index.
None
- Owner calls
importGenesisBatch()
with a non-zero batch index. - Later, at any point, they can call
importGenesisBatch()
again with a different value, immediately finalizing any batch they want with no challenge process.
The owner can arbitrarily finalize any batch, breaking the security guarantees of the chain.
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)));
}
Enforce a check that batchIndex == 0
in importGenesisBatch()
, which will ensure it can only be called once.