Coinbase Logo

Language and region

How to Evaluate Forked EVMs for Security Risks

Tl;dr:  

To strengthen security and custody guarantees for our customers who trade ERC-20s and other smart contract based assets, our Blockchain Security Team investigates the programmatic layers which define the behavior of these assets: the Ethereum Virtual Machine (EVM). When evaluating projects who modify the EVM for their own network, our Blockchain Security Team reviews critical EVM changes to determine whether the modified EVM can make the same security and custody guarantees as the original EVM implementation.

By Ethen Pociask, Eric Meng, Nadir Akhtar, Gabriela Melendez Quan, Tom Ryan

Engineering

, May 19, 2023

, 7min read time

Screenshot 2023-05-19 at 10.58.00 AM

Introduction

As of May 2023, it is without a doubt that the Ethereum Virtual Machine (EVM) is the most popular smart contract execution platform. According to DefiLlama, 9 of the top 10 chains by total-value-locked (TVL) have EVM smart contract support. Consequently, a deep understanding of the EVM is essential to support smart contracts throughout the blockchain ecosystem.

The EVM is a virtual machine designed for decentralized execution of smart contracts on the Ethereum network. Many EVM compatible blockchains leverage standard implementations from popular Ethereum execution clients in different languages like go-ethereum (Golang) and besu (Java) directly in their protocol software:

Screenshot 2023-05-19 at 11.03.02 AM

That said, forking and modifying the EVM is actually very common in the blockchain ecosystem, even across major protocols. For example, the Optimism Bedrock Stack that powers Coinbase’s Base L2 Blockchain uses a forked version of the go-ethereum execution client called op-geth which runs an EVM compatible to the ones on popular Ethereum execution clients.  However, this doesn't mean that the EVM on Ethereum behaves identically to the EVM on Optimism: the op-geth EVM behaves slightly different under certain circumstances (ie. DIFFICULTY returning a random value determined by the sequencer)

While this could sound scary, it is often healthy for EVM adoption. While the standard EVM implementations are highly optimized for the Ethereum base protocol, forked ones are typically augmented for their own novel protocols. In consequence, the way in which a contract executes on some EVM compatible chain can vary from how it executes on Ethereum.

In result, the security assumptions in how EVM smart contracts behave can vary greatly between protocols.

To that end, Coinbase has developed a Web3 security framework for assessing the security implications of changes in some forked EVM implementation from a standard one. We call this our Forked EVM Framework which is described and explained in technical detail below. 

With this Forked EVM Security Framework, Coinbase is able to efficiently:

  • Understand invalidations to the security assumptions of our Ethereum Token Framework, allowing us to securely enable new EVM compatible blockchains for ERC-20/ERC-721 token support on our centralized exchange 

  • Provide smart contract auditors insights on the smart contract vulnerability landscape for a forked EVM, particularly subtleties across networks

  • Ensure safe use and execution of EVM smart contracts on Coinbase’s Base L2 Blockchain 

Key security requirements of an EVM compatible blockchain

To understand how security risks present themselves within the Ethereum Virtual Machine, it is important to first understand what guarantees a standard EVM implementation provides us. We define a standard EVM as one that’s used unanimously by Ethereum validator execution clients as described within the Ethereum execution specifications. As of now, the most used client is go-ethereum (i.e. geth).

We summarize safety into two security pillars that represent minimum requirements for any forked EVM implementation to be eligible for Coinbase support. While minimal, these pillars provide the foundation and guidance for the framework’s overarching taxonomy.

Screenshot 2023-05-19 at 11.03.56 AM

How we Audit EVM Implementations for Security Risks

Our Forked EVM Framework primarily focuses on the following Key Review Components when assessing for adherence with the overarching security pillars (i.e. contract immutability, safe execution environment). This framework allows us to understand how a deviated EVM can be inequivalent to the one run on Ethereum.

It is important to note that the following risk components are not the entire scope of a forked EVM audit. These are some important snippets we’d like to share with the rest of the web3 security industry, but there is always more to explore.

Screenshot 2023-05-19 at 11.04.37 AM

Modifying EVM opcode definitions and encodings can lead to critical differences in how contracts are executed. For example, say some forked EVM implementation (EVM’) defines the arithmetic ADD opcode definition logic (x1 + x2) to instead subtract two values (x1 - x2). (Whether a mistake or intentionally malicious, the effect is the same: ADD no longer adds.) Let’s also assume that our modified ADD is still mapped to 0x1 within the interpreter jump table and that no changes have been made to other logical subsections of the opcode definition (ie. gas accounting, min/max stack size enforcements).

Screenshot 2023-05-19 at 11.05.08 AM

In result, the deviated EVM’ is executionally inequivalent and incompatible to a standard EVM. The consequences of modifying opcodes can range from helpful behavior, like protecting against integer overflows and underflows within arithmetic opcodes, or to more dangerous behavior, like a self-destruct causing infinite minting of a native asset.

Let’s use a tangible example: Opcode DIFFICULTY (0x44) reads directly from the block in which some transaction is executing and returns the difficulty of that block, where difficulty is a Proof-of-Work consensus concept of how much power is required to produce a block. Oftentimes, smart contract developers rely on DIFFICULTY for randomness (which is not recommended for security reasons, as described in SWC-120). However, this opcode may behave differently to accommodate a non-Proof-of-Work chain to return a more useful value: Post-merge Ethereum maps DIFFICULTY to “RANDAO,” a sufficiently random and tamper-resistant value for DIFFICULTY per every new block, reducing the likelihood of counterparty and gamification risks. Even though the EVM was modified to behave differently here, the context means that executional inequivalence is not only tolerable but welcomed to provide safe randomness to smart contract developers.

Further examples of critical risk vectors presented by modifications to Frontier opcodes:

  • Assuming different key locations in jump table

  • Allowing a party to unilaterally modify any deployed contract’s state (i.e. storage modifications and state reversions)

  • Modifying stack, memory, or storage in an unsafe manner

  • Modifying gas accounting rules where cost is not proportionate to actual node resources consumed (e.g. Making SSTORE a low cost static-gas opcode)

Lastly, examples of critical risk vectors presented by introducing new opcodes (keeping all current ones the same):

  • Allowing a hardcoded address (e.g. the chain developer) to unilaterally modify existing chain state (e.g. account balances, contract storage)

    • This can lead to stolen funds and unexpected mutations to the chain’s history, contradicting the premise of immutable execution

  • Modifying stack, memory, or storage in an unsafe manner

  • Shadowing standard opcode jump table key hex locations leading to undesirable overrides

Screenshot 2023-05-19 at 11.05.46 AM

The EVM uses precompile contracts to define complex functionality (like cryptographic functions) in more convenient and performant languages like Golang rather than in the less accessible EVM bytecode. For example, writing a SHA-256 hashing function from scratch using EVM bytecode is akin to writing a video game in x86: possible, but not for the faint of heart.

Fundamentally, these are programmed functions accessible via predetermined chain addresses expressed within the node software. Defined within the Ethereum Yellow Paper (as of May 2023) are nine precompiles, and any changes made to these nine precompiles—or introduction of new precompiles—will need to be audited given the sensitivity of this set of powers.

Let’s again use a tangible example: The BNB Smart Chain exploit. The BNB Smart Chain runs nodes using a deviated implementation of go-ethereum. To that end, two new precompile contracts were introduced (tmHeaderValidate, iavlMerkleProofValidate) that leveraged legacy libraries from third-party software (i.e. Cosmos SDK) to perform light-client block validation and Merkle proof validation. The issue was that the Cosmos SDK software had an implementation bug in their IAVL Tree representation that allowed for cryptographically invalid proofs to pass validation—in other words, anyone could generate funds out of thin air. An attacker was able to leverage this implementation bug nested inside the iavlMerkleProofValidate precompile and drain hundreds of millions of USD from the Binance bridge.

Screenshot 2023-05-19 at 11.06.22 AM

This exploit example is to showcase the necessity of precompile security and the potential risks associated with introducing new precompile contracts to a deviated EVM implementation.

Some examples of critical risks presented by additional precompiles:

  • Allowing a party to unilaterally modify any deployed contract’s state

    • This includes all storage modifications (insertions, updates, deletions)

  • Use of untrusted, unverified, or unaudited third-party dependencies

  • Providing access to non-deterministic intra-node values

As elegant as abstractions can be, rarely are they perfect. Though it’s nice to think of the compiler and EVM as completely separate entities, it’s worth noting that the Solidity compiler does make strict assumptions about the behavior of the first three precompile contracts (ecrecover, sha256, & ripemd) which are represented via native language keyword functions in the Solidity language. Under the hood, the Solidity compiler actually processes these keywords into bytecode that performs inter-contract static call operations to invoke hardcoded protocol logic defined in the precompile. This inter-contract communication is further illustrated in the below diagram.

Screenshot 2023-05-19 at 11.06.55 AM

Some examples of security risks presented by modifying standard precompiles include:

  • Allowing a centralized counterparty to unilaterally modify any deployed contract’s state.

    • This includes all storage modifications (insertions, updates, deletions)

  • Misalignment of Solidity compiler precompile location assumptions

  • Providing access to non-deterministic intra-node values

  • Use of untrusted, unverified, or unaudited third-party dependencies

Screenshot 2023-05-19 at 11.07.27 AM

All EVM-specific machine constructs/abstractions used to interpret bytecode to opcodes should be assessed for implementation deviations. This includes but not limited to: 

  • Stack & memory representation

  • Interpreter execution logic 

  • Bytecode analysis functionality

Let’s use an example: say some deviated EVM (EVM’) decided to reduce the maximum call-stack frame depth from 1024 to 1. In other words, this limits the number of allowed nested inter-contract calls to only 1. Perhaps the motivation was to prevent reentrancy attacks (SWC-107) by removing the ability to generate deeply nested contract call graphs. The modification means that only one nested CALL edge could exist for any transaction contract call graph. The below diagram demonstrates standard operating behavior on the left, and the modified version on the right:

Screenshot 2023-05-19 at 11.08.07 AM

As shown on the right, it’s only possible for some Contract0 to have up to a single CALL operation to some callee Contract1 hitting the max call stack depth of 1. Contract1 therefore is incapable of calling another contract (i.e. Contract2) since this would mean exceeding the maximum call depth. It’s also worth noting that this would pose a major Denial-of-Service risk where interoperable multi-contract architectures with greater than 1 nested call-edge would be incapable of successful execution. 

Effectively, multi-contract architectures would be generally impossible given a depth minimized EVM execution model. To mitigate potential Denial-of-Service risks, smart contract developers would be forced to rework their existing applications for this executionally inequivalent EVM by either:

  • Removing the use of external calls to callee contracts from a smart contract (i.e. no use of call type opcodes)

  • Ensuring that the maximum call depth never exceeds 1 (i.e. all contract callee targets are immutable contracts that don’t make depth incrementing calls to other contracts)

In this case, the benefits of restraining what can happen doesn’t merit breaking multi-contract architectures across the network.

Further examples of critical risk vectors presented by modifications to essential EVM components include:

  • Unconstraining the interpreter stack to be infinitely large

  • Making size modifications or changes to the memory model that could result in non-deterministic execution

  • Bypassed access controls that allow an arbitrary counter-party unilateral access to all chain-state

  • The use of untrusted, unverified, or unaudited third-party dependencies

Why care about the EVM

Our goal is to foster an open financial system built on blockchain technology, and to that end, we encourage the development of various EVM implementations. However, for an EVM compatible blockchain to be fully supported by Coinbase, it must meet the basic requirements of a standard EVM implementation. This blog post is intended to raise awareness of the risks associated with deviated EVMs and to encourage asset issuers to prioritize the development of secure components when forking from the EVM. Additionally, it is intended to provide meaningful insights to the web3 security ecosystem as a whole. 

Coinbase logo