Explicado · Ethereum
O que é reentrância, o ataque que drena um contrato antes de ele zerar o saldo
Contrato mal escrito envia o ETH e recebe a chamada de volta antes de atualizar o saldo; o roubo de 3,6 milhões de ETH no The DAO, em 2016, seguiu esse caminho.
Reentrância é o nome que a documentação oficial do Ethereum dá ao ataque em que um contrato chama de volta outro contrato enquanto a função original ainda está rodando. A EVM não permite execução simultânea: quando um contrato faz uma chamada externa, o controle passa para o endereço chamado e só volta quando a chamada termina. Se o endereço de destino é um contrato controlado pelo atacante, esse retorno pode disparar uma nova chamada para o contrato original, ainda com o estado antigo.
O exemplo da documentação é um contrato de depósito e saque. Ele lê o saldo do usuário, envia o ether e só depois zera o saldo. Enquanto a transferência está em curso, o contrato atacante executa o próprio código e chama o saque de novo; cada chamada enxerga o mesmo saldo, e o contrato esvazia. O padrão invertido, checar, interagir e aplicar o efeito, é justamente o que a documentação aponta como defeito de desenho.
O episódio histórico desse mecanismo é o The DAO, atacado em 2016: foram 3,6 milhões de ETH retirados, que a documentação do Ethereum calcula como mais de 1 bilhão de dólares aos preços atuais. A mesma página estima, com a ressalva de que os números variam, que o total perdido por falhas em contratos passa com facilidade de 1 bilhão de dólares, citando o roubo da multisig da Parity, com 30 milhões de dólares, e o bloqueio permanente de mais de 300 milhões de dólares em ETH na carteira da Parity. A documentação também trata a reentrância como problema presente, apontando para listagens públicas de explorações.
A defesa descrita na fonte tem duas formas. A primeira é reescrever o fluxo no padrão checks-effects-interactions: aplicar as condições, mudar o estado do contrato, zerar o saldo e só então enviar os fundos, de modo que a chamada de retorno encontre o saldo já zerado e a segunda tentativa de saque falhe. A segunda é uma trava de mútua exclusão, um mutex: uma variável booleana é ligada antes da execução e desligada no fim, e um modificador bloqueia qualquer chamada recursiva enquanto a original não termina, como no código MutexPattern da documentação. A página recomenda ainda reutilizar bibliotecas já auditadas, como a OpenZeppelin Contracts, em vez de reimplementar a lógica.
Vale separar o mecanismo dos números. O funcionamento da EVM e as duas defesas já valem hoje para qualquer contrato que envie valor antes de atualizar o próprio estado. Os montantes de prejuízo, ao contrário, são estimativas da documentação do Ethereum, com a própria fonte avisando que as cifras variam.