Back to Blog

Abordagem Sistemática para Manter a Compatibilidade e Segurança da EVM

Code Auditing
March 27, 2023
8 min read

Blockchains compatíveis com EVM (Ethereum Virtual Machine) são projetadas para ser compatíveis com a funcionalidade de contratos inteligentes do blockchain Ethereum, sua linguagem de programação (Solidity) e o ecossistema de ferramentas. Durante esse processo, a implementação de uma máquina virtual compatível com EVM é uma das etapas fundamentais. No entanto, durante nossas pesquisas, descobrimos que manter a compatibilidade com EVM em diferentes implementações não é uma tarefa simples.

Para resolver o problema, a BlockSec desenvolveu um sistema interno capaz de localizar sistematicamente bugs e vulnerabilidades de segurança dentro da implementação da EVM. Esse sistema se mostrou eficaz. Ele reportou quatro bugs na Aurora Engine e quatro bugs no Moonbeam foram localizados. Todos eles foram reportados e corrigidos. Vale ressaltar que essa metodologia de testes também foi utilizada para localizar duas vulnerabilidades críticas (CVE-2021–46102, CVE-2022–23066) na implementação do Solana rbpf no ano passado.

1. Contexto

Atualmente, muitos blockchains diferentes são propostos com otimizações em taxas de gas baixas, alto throughput e grande desempenho. Nesse cenário, novas máquinas virtuais ou linguagens de desenvolvimento são propostas, o que aumenta a barreira de migração para desenvolvedores originais de Solidity. Diante disso, muitas soluções compatíveis com EVM são propostas, permitindo que usuários implantem seus DApps desenvolvidos em Solidity nessas novas redes. Aurora e Moonbeam são os representantes dessas soluções compatíveis com EVM. No entanto, a robustez, confiabilidade e precisão dessas implementações de EVM são desconhecidas e merecem nossa atenção. Para isso, utilizamos a técnica de fuzzing diferencial e verificamos se há falhas nas implementações.

2. Fuzzing Diferencial

A ideia fundamental é fornecer entradas idênticas a essas implementações de EVM (por exemplo, Aurora, Moonbeam) e ao cliente Ethereum mais avançado disponível (ou seja, geth) para verificar se eles produzem a mesma saída. Especificamente, coletamos transações históricas no Ethereum e mutamos os códigos de contratos e estados de transações com diferentes estratégias para gerar casos de teste. Nossas descobertas mostram que a Aurora Engine e o Moonbeam não cumprem a especificação em alguns casos. Felizmente, todos os problemas reportados foram resolvidos e agora compartilharemos os detalhes.

3. Bugs Encontrados

A maioria desses bugs está nos contratos pré-compilados, e a causa raiz e o impacto são variados. Por exemplo, alguns bugs podem influenciar o cálculo do valor do nonce, enquanto outros podem afetar o cálculo de gas e resultar em um ataque de DoS. Todos os bugs encontrados contrariam a lógica de execução prevista pela especificação da EVM e podem resultar em comportamentos inesperados em casos específicos. Confira abaixo a descrição detalhada dos bugs encontrados.

3.1 Validação inadequada

A lógica criptográfica é complexa. Nesse caso, implementar a lógica correspondente em bytecode EVM pode resultar em um uso de gas bastante elevado. Em vez disso, os contratos pré-compilados, desenvolvidos em código nativo, podem aumentar o desempenho. No entanto, encontramos vários bugs nos contratos pré-compilados devido à validação inadequada.

Aurora Engine: ecPairing

Este bug foi encontrado no contrato pré-compilado ecPairing.

A entrada de ecPairing consiste em múltiplos pontos em duas curvas elípticas. De acordo com a especificação, o ponto (0, 0) está em ambas as curvas e deve ser uma entrada válida:

No entanto, a Aurora Engine (versão 2.7.0) irá reverter se o ponto (0,0) estiver incluído na entrada.

O PR relacionado está aqui.

Moonbeam: ecMul

Este bug está no contrato pré-compilado ecMul. Diferente da Aurora Engine, o Moonbeam reverte a transação quando a entrada é válida. De acordo com a especificação, o contrato pré-compilado ecMul deve preencher a entrada com zeros quando o comprimento da entrada for menor que 64. No entanto, o Moonbeam irá reverter em vez de realizar a operação de preenchimento.

Também descobrimos que os contratos pré-compilados ecAdd e modexp apresentam o mesmo problema.

Moonbeam: ecRecover

Este bug está no contrato pré-compilado ecRecover, utilizado para recuperar o endereço Ethereum.

De acordo com a especificação, o input[32..63] v representa um identificador U256, e espera-se que seja 27 ou 28; caso contrário, ecRecover não deve retornar nada (mas a transação inteira não deve ser revertida).

No entanto, o Moonbeam comete dois erros aqui:

  • Ele verifica apenas o input[63] em vez de converter o input[32..63] para o tipo U256 e então verificar o valor.
  • A entrada é considerada válida quando o identificador é 0 ou 1 (deveria ser apenas 27 ou 28).

Moonbeam: ecPairing

Este bug está no contrato pré-compilado ecPairing. O Moonbeam não reverte a transação quando a entrada é inválida. De acordo com a especificação, a entrada do contrato pré-compilado ecPairing deve ser um múltiplo de 192. Caso contrário, a transação deve ser revertida.

No entanto, o Moonbeam não reverte a transação quando os requisitos mencionados acima não são atendidos.

3.2 Cálculo incorreto de gas

Cada contrato pré-compilado possui um algoritmo para determinar o uso de gas. O cálculo incorreto de gas pode resultar em um ataque de DoS.

No entanto, encontramos dois bugs tanto na Aurora Engine quanto no Moonbeam em que o cálculo de gas está incorreto.

Aurora Engine: modexp

O bug está no contrato pré-compilado modexp. O algoritmo para calcular o uso de gas é definido pelo EIP-2565. O uso de gas está relacionado ao número de iterações.

O algoritmo para calcular o número de iterações é o seguinte.

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)

De acordo com o algoritmo acima, o contador de iterações é de pelo menos um. No entanto, a Aurora Engine retorna iteration_count diretamente em vez de max(iteration_count, 1). Nesse caso, o valor retornado (ou seja, iteration_count) pode ser 0, o que significa que a Aurora cobrará uma taxa de gas bem menor do que o esperado em casos específicos.

O link da issue relacionada está aqui.

Moonbeam: modexp

O bug também está na função calculate_iteration_count do contrato pré-compilado modexp. Mas foi encontrado no Moonbeam.

Quando o exponent_length é maior que 32, o iteration_count é calculado com o algoritmo abaixo.

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

Observe que ele utiliza exponent & (2**256 - 1), que obtém os menores 32 bytes do exponent, e a implementação do Moonbeam segue esse algoritmo.

No entanto, de acordo com a especificação, a fórmula de cálculo de gas deve utilizar os maiores 32 bytes do exponent:

3.3 Bug de incremento de nonce

O nonce de uma conta de propriedade externa (EOA) indica o número de transações bem-sucedidas assinadas por esse endereço. No entanto, observamos que o nonce pode ser incrementado ao enviar transações inválidas na Aurora Engine.

De acordo com o EIP-1559, antes de executar uma transação, a EVM deve garantir que o signatário tenha saldo suficiente para cobrir o token nativo transferido (por exemplo, ETH) e o gas necessário. Caso contrário, a transação deve ser descartada e o nonce do signatário não deve ser incrementado.

Embora a Aurora Engine descarte a transação nessa situação, ela ainda incrementa o nonce do signatário.

O PR relacionado está aqui.

3.4 Implementação incorreta de opcode

Este bug diz respeito à implementação de um opcode específico (ou seja, PUSH). Quando os bytes seguintes ao opcode PUSH estão incompletos, eles devem ser alinhados à direita. Por exemplo, o bytecode 0x64ffff pode ser decodificado como:

PUSH5 0xffff

Como o operando deve ser alinhado à direita, o valor 0xffff000000 deve ser empilhado na pilha. No entanto, as implementações de EVM tanto na Aurora Engine quanto no Moonbeam empilham 0xffff em vez disso, o que está incorreto.

O PR relacionado está aqui.

4. Nosso Serviço

Na BlockSec, compreendemos a importância de manter a compatibilidade com EVM e a segurança em diferentes implementações de blockchain. Por isso, desenvolvemos uma abordagem sistemática para detecção de bugs capaz de localizar vulnerabilidades em implementações de EVM com facilidade.

Nosso sistema interno demonstrou ser altamente eficaz na localização de bugs e vulnerabilidades de segurança em implementações de EVM. Ele reportou e corrigiu com sucesso quatro bugs na Aurora Engine e quatro bugs no Moonbeam. Além disso, nossa metodologia de testes foi utilizada para localizar duas vulnerabilidades críticas (CVE-2021–46102, CVE-2022–23066) na implementação do Solana rbpf no ano passado, destacando a eficácia de nossa abordagem.

Ao implementar nossa abordagem sistemática de detecção de bugs, nossos clientes podem ter a confiança de que suas implementações de EVM são seguras e confiáveis. Estamos comprometidos em oferecer soluções de alto nível para compatibilidade e segurança de EVM, ajudando nossos clientes a construir confiança com seus usuários e partes interessadas.

Sobre a BlockSec

A equipe BlockSec é focada na segurança do ecossistema blockchain e colabora com os principais projetos DeFi para proteger seus produtos. A equipe foi fundada por pesquisadores de segurança de alto nível e especialistas experientes tanto do meio acadêmico quanto da indústria. Eles publicaram múltiplos artigos sobre segurança em blockchain em conferências prestigiadas, reportaram vários ataques de dia zero em aplicações DeFi e divulgaram relatórios de análise detalhados sobre incidentes de segurança de alto impacto.

Site Oficial | Twitter | Medium

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit