Scroll is a zero knowledge (ZK) roll up layer 2 blockchain. The idea is to roll up loads of Ethereum transactions on a different blockchain back on to Ethereum. Then, to crank up the privacy, add in a zkEVM to ensure that nobody can see what's going on, while still be provable.
The zk rollup system has three phases:
- Transaction execution
- Batching and data committment
- Proof generation and finalization
With this structure, if a
batch is unprovable, then it would block the whole network. This could lead to hardforks or other bizarre issues. Lag on provability can cause problems as well.
The computational power required to do the proving is so expensive that it was not worth setting up. So, they decided to look into the bus mapping module instead. This is responsible for parsing transaction traces generated by L2Geth and converting them into witness data for the zkEVM. By modifying some tests from the project, they got libfuzzer setup. Eventually, they got a crash from doing this.
The bug was an out of bounds read within the function get_create_init_code, which can be triggered from the CREATE2 opcode. This is used for finding deterministic address without doing an actual deployment, consisting of a 0xFF, account address, salt provided by the user and bytecode of the contract being deployed.
The crash occurred when the CREATE2 opcode was executed twice. This is interesting because it shouldn't be possible to submit two contracts to the same address. The second creation fails due to the contract address collision. During a revert, the proper memory is not allocated to a given user. Since memory expansion occurs but there's not memory that is allocated, a crash occurs.
All excited, the team decided to test this attack against a local test node. But, nothing happened. Why? Good design by the Scroll team! They added a circuit capacity checker (CCC) to handle these sorts of cases. In particular, if a transaction is too expensive or crashes, then it is rejected. The execution cost is evaluated before execution. So, unknown attacks can be closed out before hitting the platform.
Since the bug was not possible to exploit because of the defense in depth measures, they didn't get a pay out. The Scroll team fixed the memory corruption bug though. To me, if there's a defense-in-depth measure that blocks an attack, a payout should still be made but just a lesser amount. If there's XSS that's blocked by the CSP, you pay for the XSS! The CSP bypass is a separate security finding to me. Regardless, good write up!