Análise de vulnerabilidades do compilador Solidity e estratégias de resposta
O compilador é uma das partes fundamentais dos sistemas de computação modernos. É um programa de computador cuja função principal é converter o código-fonte em linguagem de programação de alto nível, que é fácil para os humanos entenderem e escreverem, em código de instruções que pode ser executado pela CPU de baixo nível ou por uma máquina virtual de bytecode.
A maioria dos desenvolvedores e especialistas em segurança costuma se preocupar mais com a segurança do código das aplicações, mas pode negligenciar os problemas de segurança do próprio compilador. Na verdade, o compilador, como um programa de computador, também possui vulnerabilidades de segurança, e essas vulnerabilidades podem representar riscos de segurança sérios em determinadas circunstâncias. Por exemplo, durante o processo de compilação e interpretação do código frontend JavaScript pelo navegador, uma vulnerabilidade no mecanismo de interpretação do JavaScript pode fazer com que, ao acessar uma página da web maliciosa, o usuário seja atacado por um agente malicioso que explora a vulnerabilidade para executar código remotamente, controlando assim o navegador da vítima ou até mesmo o sistema operacional.
O compilador Solidity não é exceção; de acordo com os avisos de segurança da equipe de desenvolvimento do Solidity, existem vulnerabilidades de segurança em várias versões diferentes do compilador Solidity.
Vulnerabilidade do compilador Solidity
O papel do compilador Solidity é converter o código do contrato inteligente escrito pelos desenvolvedores em código de instrução da máquina virtual Ethereum (EVM). Esses códigos de instrução EVM são empacotados em transações e enviados para o Ethereum, sendo finalmente interpretados e executados pelo EVM.
É necessário distinguir as vulnerabilidades do compilador Solidity das vulnerabilidades da própria EVM. As vulnerabilidades da EVM referem-se a problemas de segurança que surgem quando a máquina virtual executa instruções. Como os atacantes podem fazer upload de qualquer código para o Ethereum, esse código acabará sendo executado em cada programa cliente P2P do Ethereum. Se houver vulnerabilidades de segurança na EVM, isso afetará toda a rede Ethereum, podendo causar um ataque de negação de serviço (DoS) ou até mesmo permitir que os atacantes tenham controle total. No entanto, devido ao design relativamente simples da EVM e ao fato de que o código central não é atualizado com frequência, a probabilidade de que esses problemas ocorram é baixa.
Vulnerabilidades do compilador Solidity referem-se a problemas que ocorrem quando o compilador converte Solidity em código EVM. Ao contrário de cenários onde navegadores, como o JavaScript, são compilados e executados no computador do usuário, o processo de compilação do Solidity ocorre apenas no computador do desenvolvedor de contratos inteligentes e não é executado na Ethereum. Portanto, as vulnerabilidades do compilador Solidity não afetam a rede Ethereum em si.
Uma das principais ameaças das vulnerabilidades do compilador Solidity é que podem levar a um código EVM gerado que não corresponde às expectativas dos desenvolvedores de contratos inteligentes. Como os contratos inteligentes na Ethereum geralmente envolvem os ativos de criptomoeda dos usuários, qualquer bug em um contrato inteligente causado pelo compilador pode resultar em perdas de ativos dos usuários, causando consequências graves.
Os desenvolvedores e auditores de contratos podem se concentrar em problemas de implementação da lógica do código do contrato, bem como em questões de segurança em nível de Solidity, como reentrância e estouro de inteiros. No entanto, os vazamentos do compilador Solidity são difíceis de detectar apenas por meio da auditoria da lógica do código-fonte do contrato. É necessário analisar em conjunto a versão específica do compilador e padrões de código específicos para determinar se o contrato inteligente é afetado por vulnerabilidades do compilador.
Exemplo de vulnerabilidade do compilador Solidity
Os seguintes casos reais de vulnerabilidades em compiladores Solidity demonstram suas formas específicas, causas e danos.
SOL-2016-9 HighOrderByteCleanStorage
A vulnerabilidade existe nas versões iniciais do compilador Solidity (>=0.1.6 <0.4.4).
Considere o seguinte código:
solidity
contrato C {
uint32 a = 0x1234;
uint32 b = 0;
função f() pública {
a += 1;
}
function run() public view returns (uint32) {
return b;
}
}
a variável de armazenamento b não foi modificada, portanto a função run() deve retornar o valor padrão 0. Mas no código gerado pelo compilador da versão vulnerável, run() retornará 1.
Se os desenvolvedores comuns não entenderem a vulnerabilidade do compilador, será difícil detectar o bug mencionado no código apenas com uma simples revisão de código. Este é apenas um exemplo simples e não causará consequências particularmente graves. Mas se a variável b for utilizada para validação de permissões, contabilidade de ativos, etc., essa inconsistência em relação ao esperado pode levar a problemas muito sérios.
A razão para esse fenômeno estranho é que o EVM usa uma máquina virtual baseada em pilha, onde cada elemento da pilha tem 32 bytes de tamanho (, ou seja, o tamanho da variável uint256 ). Por outro lado, cada slot de armazenamento subjacente também tem 32 bytes de tamanho. A linguagem Solidity suporta tipos de dados menores que 32 bytes, como uint32, e o compilador, ao processar esse tipo de variável, precisa realizar operações de limpeza adequadas nos bits mais altos (clean up) para garantir a correção dos dados. Na situação mencionada, quando a adição resulta em um estouro de inteiro, o compilador não limpou corretamente os bits mais altos do resultado, levando a que o bit de 1 nos bits mais altos após o estouro fosse gravado no armazenamento, o que eventualmente sobrepôs a variável a, alterando o valor da variável b para 1.
SOL-2022-4 InlineAssemblyMemorySideEffects
Considere o seguinte código:
solidity
contract C {
function f() public pure returns (uint) {
assembly {
mstore(0, 0x42)
}
uint x;
assembly {
x := mload(0)
}
return x;
}
}
A vulnerabilidade existe nas versões do compilador >=0.8.13 <0.8.15. O compilador Solidity, ao converter a linguagem Solidity em código EVM, não se limita a uma simples tradução. Ele também realiza uma análise profunda do fluxo de controle e dos dados, implementando vários processos de otimização de compilação para reduzir o tamanho do código gerado e otimizar o consumo de gas durante o processo de execução. Esse tipo de operação de otimização é comum em compiladores de várias linguagens de alto nível, mas devido à complexidade das situações a serem consideradas, também é fácil que ocorram bugs ou vulnerabilidades de segurança.
A vulnerabilidade do código acima decorre deste tipo de operação de otimização. Suponha que exista um código em uma função que modifica os dados no deslocamento de memória 0, mas não há nenhum outro lugar que utilize esses dados posteriormente, então na verdade é possível remover diretamente o código que modifica a memória 0, economizando gas, sem impactar a lógica do programa subsequente.
Esta estratégia de otimização em si não tem problema, mas na implementação do código do compilador Solidity, tal otimização é aplicada apenas a um único bloco de assembly. No código PoC acima, a escrita e o acesso à memória 0 estão em dois blocos de assembly diferentes, enquanto o compilador apenas realizou a análise de otimização no bloco de assembly isolado. Como não há nenhuma operação de leitura após a escrita na memória 0 no primeiro bloco de assembly, a instrução de escrita é considerada desnecessária e será removida, resultando em um bug. Na versão vulnerável, a função f() retornará o valor 0, enquanto na verdade o valor correto que o código acima deveria retornar é 0x42.
solidity
contrato C {
function f(string[1] calldata a) external returns (string memory) {
return abi.decode(abi.encode(a), (string));
}
}
A vulnerabilidade afeta compiladores de versões >= 0.5.8 < 0.8.16. Normalmente, a variável a retornada pelo código acima deve ser "aaaa". Mas na versão vulnerável, retornará uma string vazia "".
A causa da vulnerabilidade é que o Solidity, ao realizar a operação abi.encode em arrays do tipo calldata, limpa erroneamente certos dados, o que resulta na modificação de outros dados adjacentes, causando inconsistências nos dados após a codificação e decodificação.
É importante notar que o Solidity, ao realizar chamadas externas e emitir eventos, codifica implicitamente os parâmetros usando abi.encode, portanto, a probabilidade de ocorrer o código de vulnerabilidade mencionado é maior do que a intuição sugere.
Sugestões de Segurança
A equipe de segurança da Cobo analisou o modelo de ameaça de vulnerabilidades do compilador Solidity e revisou as vulnerabilidades históricas, apresentando as seguintes recomendações para desenvolvedores e profissionais de segurança.
Para os desenvolvedores:
Utilize uma versão mais recente do compilador Solidity. Embora novas versões possam também introduzir novos problemas de segurança, os problemas de segurança conhecidos geralmente são menos do que nas versões antigas.
Melhorar os casos de teste unitário. A maioria dos bugs a nível de compilador pode levar a resultados de execução de código que não correspondem às expectativas. Este tipo de problema é difícil de detectar através da revisão de código, mas pode facilmente ser exposto na fase de testes. Portanto, ao aumentar a cobertura de código, é possível evitar ao máximo este tipo de problema.
Tente evitar o uso de assembly inline, operações complexas como codificação e decodificação de abi para arrays multidimensionais e estruturas complexas. Evite usar recursos novos e experimentais da linguagem por impulso, a menos que haja uma necessidade clara. De acordo com a análise da equipe de segurança da Cobo sobre as vulnerabilidades históricas do Solidity, a maioria das vulnerabilidades está relacionada a operações como assembly inline e codificadores de abi. O compilador realmente tende a apresentar mais bugs ao lidar com características complexas da linguagem. Por outro lado, os desenvolvedores também podem facilmente cometer erros ao usar novos recursos, levando a problemas de segurança.
Para a equipe de segurança:
Ao realizar uma auditoria de segurança no código Solidity, não ignore os riscos de segurança que podem ser introduzidos pelo compilador Solidity. O item correspondente na Classificação de Fraquezas de Contratos Inteligentes(SWC) é SWC-102: Versão do Compilador Desatualizada.
Durante o processo de desenvolvimento interno do SDL, instar a equipa de desenvolvimento a atualizar a versão do compilador Solidity e considerar a introdução de verificações automáticas da versão do compilador no processo CI/CD.
Mas não há necessidade de entrar em pânico excessivo sobre vulnerabilidades do compilador; a maioria das vulnerabilidades do compilador só se manifesta em padrões de código específicos, e não significa que contratos compilados com versões vulneráveis do compilador apresentem necessariamente riscos de segurança. O impacto real na segurança deve ser avaliado com base nas circunstâncias do projeto.
Recursos úteis:
Publicações de Alertas de Segurança da equipe Solidity, lançadas regularmente:
Lista de bugs atualizada regularmente no repositório oficial do Solidity:
Lista de bugs do compilador de várias versões:
No Etherscan, o triângulo com ponto de exclamação no canto superior direito da página do contrato -> Código pode indicar vulnerabilidades de segurança na versão atual do compilador.
Resumo
Este artigo começa com os conceitos básicos de compiladores, introduz os vulnerabilidades do compilador Solidity e analisa os riscos de segurança que podem surgir no ambiente de desenvolvimento real do Ethereum. Por fim, oferece várias recomendações de segurança práticas para desenvolvedores e profissionais de segurança.
Esta página pode conter conteúdos de terceiros, que são fornecidos apenas para fins informativos (sem representações/garantias) e não devem ser considerados como uma aprovação dos seus pontos de vista pela Gate, nem como aconselhamento financeiro ou profissional. Consulte a Declaração de exoneração de responsabilidade para obter mais informações.
11 gostos
Recompensa
11
5
Partilhar
Comentar
0/400
staking_gramps
· 16h atrás
As portas das vulnerabilidades são muitas, quem ainda se atreve a desenvolver?
Ver originalResponder0
JustHereForMemes
· 07-23 05:15
O compilador também tem grandes problemas, estou a sair!
Ver originalResponder0
BakedCatFanboy
· 07-22 21:56
A falha depende de ser apanhada, certo?
Ver originalResponder0
OldLeekNewSickle
· 07-22 21:56
idiotas de alto nível veem falhas, idiotas de baixo nível observam a Carteira de encomendas
Ver originalResponder0
RektCoaster
· 07-22 21:52
O compilador também tem bugs? Isso realmente me faz aparecer e explodir na rua.
Análise de vulnerabilidades do compilador Solidity e estratégias de segurança para desenvolvedores
Análise de vulnerabilidades do compilador Solidity e estratégias de resposta
O compilador é uma das partes fundamentais dos sistemas de computação modernos. É um programa de computador cuja função principal é converter o código-fonte em linguagem de programação de alto nível, que é fácil para os humanos entenderem e escreverem, em código de instruções que pode ser executado pela CPU de baixo nível ou por uma máquina virtual de bytecode.
A maioria dos desenvolvedores e especialistas em segurança costuma se preocupar mais com a segurança do código das aplicações, mas pode negligenciar os problemas de segurança do próprio compilador. Na verdade, o compilador, como um programa de computador, também possui vulnerabilidades de segurança, e essas vulnerabilidades podem representar riscos de segurança sérios em determinadas circunstâncias. Por exemplo, durante o processo de compilação e interpretação do código frontend JavaScript pelo navegador, uma vulnerabilidade no mecanismo de interpretação do JavaScript pode fazer com que, ao acessar uma página da web maliciosa, o usuário seja atacado por um agente malicioso que explora a vulnerabilidade para executar código remotamente, controlando assim o navegador da vítima ou até mesmo o sistema operacional.
O compilador Solidity não é exceção; de acordo com os avisos de segurança da equipe de desenvolvimento do Solidity, existem vulnerabilidades de segurança em várias versões diferentes do compilador Solidity.
Vulnerabilidade do compilador Solidity
O papel do compilador Solidity é converter o código do contrato inteligente escrito pelos desenvolvedores em código de instrução da máquina virtual Ethereum (EVM). Esses códigos de instrução EVM são empacotados em transações e enviados para o Ethereum, sendo finalmente interpretados e executados pelo EVM.
É necessário distinguir as vulnerabilidades do compilador Solidity das vulnerabilidades da própria EVM. As vulnerabilidades da EVM referem-se a problemas de segurança que surgem quando a máquina virtual executa instruções. Como os atacantes podem fazer upload de qualquer código para o Ethereum, esse código acabará sendo executado em cada programa cliente P2P do Ethereum. Se houver vulnerabilidades de segurança na EVM, isso afetará toda a rede Ethereum, podendo causar um ataque de negação de serviço (DoS) ou até mesmo permitir que os atacantes tenham controle total. No entanto, devido ao design relativamente simples da EVM e ao fato de que o código central não é atualizado com frequência, a probabilidade de que esses problemas ocorram é baixa.
Vulnerabilidades do compilador Solidity referem-se a problemas que ocorrem quando o compilador converte Solidity em código EVM. Ao contrário de cenários onde navegadores, como o JavaScript, são compilados e executados no computador do usuário, o processo de compilação do Solidity ocorre apenas no computador do desenvolvedor de contratos inteligentes e não é executado na Ethereum. Portanto, as vulnerabilidades do compilador Solidity não afetam a rede Ethereum em si.
Uma das principais ameaças das vulnerabilidades do compilador Solidity é que podem levar a um código EVM gerado que não corresponde às expectativas dos desenvolvedores de contratos inteligentes. Como os contratos inteligentes na Ethereum geralmente envolvem os ativos de criptomoeda dos usuários, qualquer bug em um contrato inteligente causado pelo compilador pode resultar em perdas de ativos dos usuários, causando consequências graves.
Os desenvolvedores e auditores de contratos podem se concentrar em problemas de implementação da lógica do código do contrato, bem como em questões de segurança em nível de Solidity, como reentrância e estouro de inteiros. No entanto, os vazamentos do compilador Solidity são difíceis de detectar apenas por meio da auditoria da lógica do código-fonte do contrato. É necessário analisar em conjunto a versão específica do compilador e padrões de código específicos para determinar se o contrato inteligente é afetado por vulnerabilidades do compilador.
Exemplo de vulnerabilidade do compilador Solidity
Os seguintes casos reais de vulnerabilidades em compiladores Solidity demonstram suas formas específicas, causas e danos.
SOL-2016-9 HighOrderByteCleanStorage
A vulnerabilidade existe nas versões iniciais do compilador Solidity (>=0.1.6 <0.4.4).
Considere o seguinte código:
solidity contrato C { uint32 a = 0x1234; uint32 b = 0; função f() pública { a += 1; } function run() public view returns (uint32) { return b; } }
a variável de armazenamento b não foi modificada, portanto a função run() deve retornar o valor padrão 0. Mas no código gerado pelo compilador da versão vulnerável, run() retornará 1.
Se os desenvolvedores comuns não entenderem a vulnerabilidade do compilador, será difícil detectar o bug mencionado no código apenas com uma simples revisão de código. Este é apenas um exemplo simples e não causará consequências particularmente graves. Mas se a variável b for utilizada para validação de permissões, contabilidade de ativos, etc., essa inconsistência em relação ao esperado pode levar a problemas muito sérios.
A razão para esse fenômeno estranho é que o EVM usa uma máquina virtual baseada em pilha, onde cada elemento da pilha tem 32 bytes de tamanho (, ou seja, o tamanho da variável uint256 ). Por outro lado, cada slot de armazenamento subjacente também tem 32 bytes de tamanho. A linguagem Solidity suporta tipos de dados menores que 32 bytes, como uint32, e o compilador, ao processar esse tipo de variável, precisa realizar operações de limpeza adequadas nos bits mais altos (clean up) para garantir a correção dos dados. Na situação mencionada, quando a adição resulta em um estouro de inteiro, o compilador não limpou corretamente os bits mais altos do resultado, levando a que o bit de 1 nos bits mais altos após o estouro fosse gravado no armazenamento, o que eventualmente sobrepôs a variável a, alterando o valor da variável b para 1.
SOL-2022-4 InlineAssemblyMemorySideEffects
Considere o seguinte código:
solidity contract C { function f() public pure returns (uint) { assembly { mstore(0, 0x42) } uint x; assembly { x := mload(0) } return x; } }
A vulnerabilidade existe nas versões do compilador >=0.8.13 <0.8.15. O compilador Solidity, ao converter a linguagem Solidity em código EVM, não se limita a uma simples tradução. Ele também realiza uma análise profunda do fluxo de controle e dos dados, implementando vários processos de otimização de compilação para reduzir o tamanho do código gerado e otimizar o consumo de gas durante o processo de execução. Esse tipo de operação de otimização é comum em compiladores de várias linguagens de alto nível, mas devido à complexidade das situações a serem consideradas, também é fácil que ocorram bugs ou vulnerabilidades de segurança.
A vulnerabilidade do código acima decorre deste tipo de operação de otimização. Suponha que exista um código em uma função que modifica os dados no deslocamento de memória 0, mas não há nenhum outro lugar que utilize esses dados posteriormente, então na verdade é possível remover diretamente o código que modifica a memória 0, economizando gas, sem impactar a lógica do programa subsequente.
Esta estratégia de otimização em si não tem problema, mas na implementação do código do compilador Solidity, tal otimização é aplicada apenas a um único bloco de assembly. No código PoC acima, a escrita e o acesso à memória 0 estão em dois blocos de assembly diferentes, enquanto o compilador apenas realizou a análise de otimização no bloco de assembly isolado. Como não há nenhuma operação de leitura após a escrita na memória 0 no primeiro bloco de assembly, a instrução de escrita é considerada desnecessária e será removida, resultando em um bug. Na versão vulnerável, a função f() retornará o valor 0, enquanto na verdade o valor correto que o código acima deveria retornar é 0x42.
SOL-2022-6 AbiReencodingHeadOverflowWithStaticArrayCleanup
Considere o seguinte código:
solidity contrato C { function f(string[1] calldata a) external returns (string memory) { return abi.decode(abi.encode(a), (string)); } }
A vulnerabilidade afeta compiladores de versões >= 0.5.8 < 0.8.16. Normalmente, a variável a retornada pelo código acima deve ser "aaaa". Mas na versão vulnerável, retornará uma string vazia "".
A causa da vulnerabilidade é que o Solidity, ao realizar a operação abi.encode em arrays do tipo calldata, limpa erroneamente certos dados, o que resulta na modificação de outros dados adjacentes, causando inconsistências nos dados após a codificação e decodificação.
É importante notar que o Solidity, ao realizar chamadas externas e emitir eventos, codifica implicitamente os parâmetros usando abi.encode, portanto, a probabilidade de ocorrer o código de vulnerabilidade mencionado é maior do que a intuição sugere.
Sugestões de Segurança
A equipe de segurança da Cobo analisou o modelo de ameaça de vulnerabilidades do compilador Solidity e revisou as vulnerabilidades históricas, apresentando as seguintes recomendações para desenvolvedores e profissionais de segurança.
Para os desenvolvedores:
Utilize uma versão mais recente do compilador Solidity. Embora novas versões possam também introduzir novos problemas de segurança, os problemas de segurança conhecidos geralmente são menos do que nas versões antigas.
Melhorar os casos de teste unitário. A maioria dos bugs a nível de compilador pode levar a resultados de execução de código que não correspondem às expectativas. Este tipo de problema é difícil de detectar através da revisão de código, mas pode facilmente ser exposto na fase de testes. Portanto, ao aumentar a cobertura de código, é possível evitar ao máximo este tipo de problema.
Tente evitar o uso de assembly inline, operações complexas como codificação e decodificação de abi para arrays multidimensionais e estruturas complexas. Evite usar recursos novos e experimentais da linguagem por impulso, a menos que haja uma necessidade clara. De acordo com a análise da equipe de segurança da Cobo sobre as vulnerabilidades históricas do Solidity, a maioria das vulnerabilidades está relacionada a operações como assembly inline e codificadores de abi. O compilador realmente tende a apresentar mais bugs ao lidar com características complexas da linguagem. Por outro lado, os desenvolvedores também podem facilmente cometer erros ao usar novos recursos, levando a problemas de segurança.
Para a equipe de segurança:
Ao realizar uma auditoria de segurança no código Solidity, não ignore os riscos de segurança que podem ser introduzidos pelo compilador Solidity. O item correspondente na Classificação de Fraquezas de Contratos Inteligentes(SWC) é SWC-102: Versão do Compilador Desatualizada.
Durante o processo de desenvolvimento interno do SDL, instar a equipa de desenvolvimento a atualizar a versão do compilador Solidity e considerar a introdução de verificações automáticas da versão do compilador no processo CI/CD.
Mas não há necessidade de entrar em pânico excessivo sobre vulnerabilidades do compilador; a maioria das vulnerabilidades do compilador só se manifesta em padrões de código específicos, e não significa que contratos compilados com versões vulneráveis do compilador apresentem necessariamente riscos de segurança. O impacto real na segurança deve ser avaliado com base nas circunstâncias do projeto.
Recursos úteis:
Publicações de Alertas de Segurança da equipe Solidity, lançadas regularmente:
Lista de bugs atualizada regularmente no repositório oficial do Solidity:
Lista de bugs do compilador de várias versões:
No Etherscan, o triângulo com ponto de exclamação no canto superior direito da página do contrato -> Código pode indicar vulnerabilidades de segurança na versão atual do compilador.
Resumo
Este artigo começa com os conceitos básicos de compiladores, introduz os vulnerabilidades do compilador Solidity e analisa os riscos de segurança que podem surgir no ambiente de desenvolvimento real do Ethereum. Por fim, oferece várias recomendações de segurança práticas para desenvolvedores e profissionais de segurança.