Vulnerabilidades nos contratos inteligentes Etherium. Exemplos de código

Com este post, inicio uma série de artigos sobre a segurança dos contratos inteligentes da Ethereum. Eu acho que esse tópico é muito relevante, já que o número de desenvolvedores está crescendo como uma avalanche, e não há ninguém para salvar do "rake". Tchau - traduções ...

1. Digitalização de contratos do Ethereum ao vivo quanto a erro de envio não verificado


Original - Digitalização de contratos ao vivo do Ethereum para o "Envio não verificado ..."


Autores: Zikai Alex Wen e Andrew Miller

Sabe-se que a programação de contratos inteligentes do Ethereum é propensa a erros [1] . Recentemente, vimos que vários
contratos inteligentes de última geração, como King of the Ether e DAO-1.0, continham vulnerabilidades causadas por erros de programação.

Desde março de 2015, programadores inteligentes de contratos são avisados ​​sobre os perigos específicos da programação que podem surgir quando os contratos enviam mensagens um para o outro [6] .

Vários guias de programação recomendam como evitar erros comuns (nos documentos oficiais do Ethereum [3] e em um guia independente da UMD [2] ). Embora esses perigos sejam compreensíveis o suficiente para evitá-los, as consequências de tal erro são terríveis: o dinheiro pode ser bloqueado, perdido ou roubado.

Quão comuns são os erros resultantes desses perigos? Existem contratos de blockchain Ethereum mais vulneráveis, mas vivos? Neste artigo, respondemos a essa pergunta analisando contratos no blockchain ao vivo da Ethereum usando a nova ferramenta de análise que desenvolvemos.


Qual é o erro de envio não verificado?


Para enviar um contrato de tempo de antena para outro endereço, a maneira mais fácil é usar a palavra - chave send . Isso atua como um método definido para cada objeto. Por exemplo, o seguinte snippet de código pode ser encontrado em um contrato inteligente que implementa um jogo de tabuleiro.


/*** Listing 1 ***/ if (gameHasEnded && !( prizePaidOut ) ) { winner.send(1000); //    prizePaidOut = True; } 

O problema aqui é que o método de envio pode falhar. Se não funcionar, o vencedor não receberá o dinheiro, no entanto, a variável awardPaidOut será definida como True.

Existem dois casos diferentes em que a função winner.send () pode falhar. Analisaremos a diferença entre eles mais tarde. O primeiro caso é que o endereço do vencedor é um contrato (não uma conta de usuário) e o código desse contrato gera uma exceção (por exemplo, se ele usa muito "gás"). Nesse caso, talvez neste caso seja um "erro do vencedor". O segundo caso é menos óbvio. A máquina virtual Ethereum possui um recurso limitado chamado " pilha de chamadas " (profundidade da pilha de chamadas) e esse recurso pode ser usado por outro código de contrato que foi executado anteriormente em uma transação. Se a pilha de chamadas já tiver sido usada no momento em que o comando send for executado, o comando falhará, independentemente de como o vencedor seja determinado. O prêmio do vencedor será destruído sem culpa alguma!



Como esse erro pode ser evitado?

A documentação do Ethereum contém um breve aviso sobre esse perigo potencial [3] : "Existe algum perigo ao usar o envio - a transmissão falha se a profundidade da pilha de chamadas for 1024 (isso sempre pode ser causado pelo chamador) e também falhará se o destinatário "gas" termina. Portanto, para garantir uma transmissão segura, sempre verifique o valor de retorno do envio, ou melhor ainda: use um modelo no qual o destinatário retire dinheiro. "

Duas frases. O primeiro é verificar o valor de retorno do envio para ver se ele foi concluído com êxito. Se não for esse o caso, lance uma exceção para reverter o estado.


  /*** Listing 2 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (winner.send(1000)) prizePaidOut = True; else throw; } 

Essa é uma correção adequada para o exemplo atual, mas nem sempre a decisão correta. Suponha que modifiquemos nosso exemplo para que, quando o jogo terminar, o vencedor e o perdedor revertam suas fortunas. Uma aplicação óbvia de uma solução "formal" seria a seguinte:


 /*** Listing 3 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (winner.send(1000) && loser.send(10)) prizePaidOut = True; else throw; } 

No entanto, isso é um erro, pois apresenta uma vulnerabilidade adicional. Embora esse código proteja o vencedor de um ataque de pilha de chamada , ele também torna o vencedor e o perdedor vulneráveis ​​um ao outro. Nesse caso, queremos impedir um ataque de pilha de chamada , mas continuemos a execução se o comando send falhar por algum motivo.

Portanto, mesmo a melhor prática (recomendada no Guia do programador do Ethereum e Serpent, embora se aplique igualmente ao Solidity), é verificar a presença de um recurso de pilha de chamada . Podemos definir uma macro callStackIsEmpty () , que retornará um erro se e somente se o pilha de chamadas estiver vazia.


 /*** Listing 4 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (callStackIsEmpty()) throw; winner.send(1000) loser.send(10) prizePaidOut = True; } 

Melhor ainda, a recomendação da documentação do Ethereum - "Use um modelo no qual o destinatário recebe o dinheiro" é um pouco enigmática, mas tem uma explicação. A sugestão é reorganizar seu código para que o efeito da falha no envio seja isolado e afete apenas um destinatário por vez. A seguir, é apresentado um exemplo dessa abordagem. No entanto, essa dica também é um anti-padrão. Ele aceita a responsabilidade de verificar a pilha de chamadas para os próprios destinatários, o que torna possível cair na mesma armadilha.


 /*** Listing 5 ***/ if (gameHasEnded && !( prizePaidOut ) ) { accounts[winner] += 1000 accounts[loser] += 10 prizePaidOut = True; } ... function withdraw(amount) { if (accounts[msg.sender] >= amount) { msg.sender.send(amount); accounts[msg.sender] -= amount; } } 

Muitos contratos inteligentes altamente desenvolvidos são vulneráveis. A loteria Rei do Ar do Trono é o caso mais famoso desse erro [4] . Esse erro não foi percebido até que a quantia de 200 éteres (no valor de mais de 2000 dólares americanos ao preço de hoje) não conseguiu o vencedor legítimo da loteria. O código correspondente em King of the Ether é semelhante ao código da Listagem 2. Felizmente, nesse caso, o desenvolvedor do contrato conseguiu usar a função não relacionada no contrato como uma "substituição manual" para liberar os fundos bloqueados. Um administrador menos escrupuloso poderia usar a mesma função para roubar a transmissão!


Continuação da digitalização de contratos ao vivo no Ethereum para erro de envio não verificado. Parte 2

Source: https://habr.com/ru/post/pt430602/


All Articles