Systematic Approach to Maintaining EVM Compatibility and Security

EVM (Ethereum Virtual Machine) compatible blockchains are designed to be compatible with the Ethereum blockchain’s smart contract functionality, programming language (Solidity), and tooling ecosystem.

Systematic Approach to Maintaining EVM Compatibility and Security

EVM (Ethereum Virtual Machine) compatible blockchains are designed to be compatible with the Ethereum blockchain’s smart contract functionality, programming language (Solidity), and tooling ecosystem. During this process, the implementation of an EVM-compliant virtual machine is one of the key steps. However, during our research, we find that maintaining the compatibility for EVM in different implementations is not easy.

To solve the issue, BlockSec developed an internal system that can systematically locate the bugs and security vulnerabilities inside the EVM implementation. This system turned out to be effective. It reported four bugs in Aurora Engine and four bugs in Moonbeam are located. All of them are reported and fixed. Note that, this testing methodology was also used to locate two critical vulnerabilities (CVE-2021–46102, CVE-2022–23066) in Solana rbpf implementation last year.

1. Background

Nowadays, many different blockchains are proposed with the optimization on low gas fee, high throughput, and great performance. In this case, new virtual machines or developing languages are proposed, which increases the bar of transformation for original solidity developers. In this case, many EVM-compatible solutions are proposed, which allow users to deploy their DApps developed in solidity on these new chains. Aurora and Moonbeam are the representatives of these EVM-compatible solutions. However, the robustness, reliability, and precision of these EVM implementations are unknown and deserve our attention. To this end, we utilize the differential fuzzing technique and check whether there are any flaws in the implementations.

2. Differential Fuzzing

The fundamental idea is to feed identical inputs to these EVM implementations (e.g., Aurora, Moonbeam), and the state-of-the-art Ethereum client (i.e., geth) to check whether they have the same output. Specifically, we collect the historical transactions on Ethereum and mutate the contract codes and transaction states with different strategies to generate test cases. Our findings show that Aurora Engine and Moonbeam don’t comply with the specification in some cases. Fortunately, all the reported issues are addressed and now we will share the details.

3. Found Bugs

Most of these bugs are in the precompiled contracts and the root cause and impact are various. For example, some of the bugs can influence the calculation of the nonce value while others can influence the gas calculation and can further result in a DoS attack. All the found bugs are against the designated execution logic of the EVM specification and can result in unexpected behavior in specific cases. Please find the detailed description for the found bugs below.

3.1 Improper validation

Cryptographic logic is complex. In this case, implementing the corresponding logic in EVM bytecode can result in a rather large gas usage. Instead, the precompiled contracts, which are developed in native code, can increase the performance. However, we found several bugs in the precompiled contracts due to improper validation.

Aurora Engine: ecPairing

This bug is found in the precompiled contract ecPairing.

The input of ecPairing consists of multiple points on two elliptic curves. According to the specification, the point (0, 0) is on both curves and should be a valid input:

However, Aurora Engine (version 2.7.0) will revert if the point (0,0) is included in the input.

The related PR is here.

Moonbeam: ecMul

This bug is in the precompiled contract ecMul. Different from Aurora Engine, Moonbeam reverts the transaction when the input is valid. According to the specification, the precompiled contract ecMul should pad the input with zeros when the length of the input is less than 64. However, Moonbeam will revert instead of conducting the padding operation.

We also found that the precompiled contract ecAdd and modexp have the same issue.

The related PR is here.

Moonbeam: ecRecover

This bug is in the precompiled contract ecRecover, which is used to recover the Ethereum address.

According to the specification, the input[32..63] v represents a U256 identifier, and it’s expected to be either 27 or 28, otherwise ecRecover should not return anything (but the whole transaction should not revert).

However, Moonbeam makes two mistakes here:

  • It only checks input[63] instead of converting the input[32..63] to a U256 type and then checking the value.
  • The input is considered valid when the identifier is 0 or 1(it should only be 27 or 28).

The related PR is here.

Moonbeam: ecPairing

This bug is in the precompiled contract ecPairing. Moonbeam does not revert the transaction when the input is invalid. According to the specification, the input of the precompiled contract ecPairing should be a multiple of 192. Otherwise, the transaction should revert.

However, Moonbeam does not revert the transaction when the above-mentioned requirements do not meet.

The related PR is here.

3.2 Incorrect gas calculation

Each precompile contract has an algorithm to determine the gas usage. The incorrect gas calculation can result in a DoS attack.

However, We have found two bugs in both Aurora Engine and Moonbeam that the gas calculation is incorrect.

Aurora Engine: modexp

The bug is in the precompiled contract modexp. The algorithm of calculating the gas usage is defined by EIP-2565. Gas usage is related to the number of iterations.

The algorithm for calculating the number of iterations is the following.

def calculate_iteration_count(exponent_length, exponent):
   iteration_count = 0
   if exponent_length <= 32 and exponent == 0: iteration_count = 0
   elif exponent_length <= 32: iteration_count = exponent.bit_length() - 1
   elif exponent_length > 32: iteration_count = (8 * (exponent_length - 32)) + ((exponent & (2**256 - 1)).bit_length() - 1)
   return max(iteration_count, 1)

According to the algorithm above, the iteration count is at least one. However, Aurora Engine returns iteration_count directly instead of max(iteration_count, 1). In this case, the returned value (i.e.,iteration_count) can be 0, which means that Aurora will charge rather less gas fee than expected in specific cases.

The related issue link is here.

Moonbeam: modexp

The bug is also in the function calculate_iteration_count of precompiled contract modexp. But it is found in Moonbeam.

When the exponent_length is greater than 32, the iteration_count is calculated with the algorithm below.

(8 * (exponent_length - 32)) + ((exponent & (2**256 - 1)).bit_length() - 1)

Note it uses exponent & (2**256 - 1) , which takes the lowest 32 bytes of exponent, and Moonbeam's implementation follows this algorithm.

However, according to the specification, the gas calculation formula should use the highest 32 bytes of exponent:

The related PR is here.

3.3 Nonce increment bug

The nonce of an externally owned account (EOA) indicates the number of successful transactions signed by this address. However, we noticed that the nonce can be increased by sending invalid transactions on Aurora Engine.

According to EIP-1559, before executing a transaction, the EVM should ensure that the signer has enough balance to cover the transferred native token (e.g., ETH) and the required gas. Otherwise, the transaction should be dropped and the nonce of the signer should not be increased.

Though Aurora Engine drops the transaction in this situation, it still increases the nonce of the signer.

The related PR is here.

3.4 Incorrect opcode implementation

This bug is about the implementation of a specific opcode (i.e., PUSH). When the bytes following the PUSH opcode are incomplete, they should be right-aligned. For example, bytecode 0x64ffff can be decoded as:

PUSH5 0xffff

As the operand should be right-aligned, value 0xffff000000 should be pushed onto the stack. However, the EVM implementations in both Aurora Engine and Moonbeam push 0xffff instead, which is incorrect.

The related PR is here.

4. Our Service

At BlockSec, we understand the importance of maintaining EVM compatibility and security across different blockchain implementations. That is why we have developed a systematic approach to bug detection that can locate vulnerabilities in EVM implementations with ease.

Our internal system has proven to be highly effective in locating bugs and security vulnerabilities in EVM implementations. It has successfully reported and fixed four bugs in Aurora Engine and four bugs in Moonbeam. In addition, our testing methodology was used to locate two critical vulnerabilities (CVE-2021–46102, CVE-2022–23066) in Solana rbpf implementation last year, highlighting the effectiveness of our approach.

By implementing our systematic bug detection approach, our clients can be confident that their EVM implementations are secure and reliable. We are committed to providing top-notch solutions for EVM compatibility and security, helping our clients build trust with their users and stakeholders.

About BlockSec

The BlockSec Team focuses on the security of the blockchain ecosystem, and collaborates with leading DeFi projects to secure their products. The team is founded by top-notch security researchers and experienced experts from both academia and industry. They have published multiple blockchain security papers in prestigious conferences, reported several zero-day attacks of DeFi applications, and released detailed analysis reports of high-impact security incidents.

Official Website | Twitter | Medium

Sign up for the latest updates