Sécurité des smarts contracts des blockchains & cryptomonnaies intégrant une EVM (ex: failles d’Ethereum)

Nicolas Cantu
ON-X Blockchain (Chain-Accelerator)
10 min readOct 21, 2020
  • Introduction
  • L’EVM délègue une trop grande partie de la sécurité à la responsabilité des développeurs qui l’utilisent. Les contrats de l’EVM pas assez “lisibles” pour être efficacement vérifiés de manière équationnelle
  • L’EVM expose aux risques des langages Turing complets, sans bibliothèques standards, et avec des appels externes, le modèle d’exécution EVM est coûteux, lent et dangereux
  • Les contrats et les comptes référencés par adresses (registres d’adresses vs registres d’entrées/sorties) empêchent un support multi-signature natif et des contrats évolutifs sans contournements
  • Conclusion

Introduction

Au cours des trois dernières années de développement de contrats intelligents, la communauté des crypto-monnaies a vu des contrats intelligents écrits dans Solidity subvertis par une variété de bogues et d’exploits (l’exploit DAO, le bogue du portefeuille multi-sig Parity, etc.), et il est commun à attribuer la responsabilité de la prépondérance des contrats intelligents non sécurisés au langage des contrats intelligents Solidity et à ses nombreuses bizarreries.

Cependant, nous soutenons que certaines des pires failles de Solidity — un manque d’inspection et de traçabilité, l’opacité et l’illisibilité du code en chaîne et des appels coûteux, lents et dangereux à des contrats intelligents externes — découlent directement de décisions de conceptions fondamentales en l’architecture de la machine virtuelle Ethereum (EVM). Tout langage construit sur l’EVM, en revanche, produira un bytecode qui sera illisible et invérifiable par les humains, et produira une compréhension incomplète de la fonctionnalité des programmes écrits dans cette langue et difficiles à prouver formellement.

Bien que nous soyons conscients que le groupe de travail EWASM cherche à faire progresser l’état de la technologie des contrats intelligents sur Ethereum, nous mettons en garde contre le fait de ne pas tenir compte des failles de l’EVM, car si les défauts de conception ne sont pas identifiés, ils risquent gravement d’être répétés.

Les éléments sont en grande partie issus de l’analyse proposée dans le post : https://medium.com/kadena-io/the-evm-is-fundamentally-unsafe-ba486cb17f1f

L’EVM délègue une trop grande partie de la sécurité à la responsabilité des développeurs qui l’utilisent

Une machine virtuelle (VM) bien construite cherche à protéger les concepteurs de langage contre les erreurs dangereuses et à prendre en charge une construction efficace avec des fonctionnalités centrales telles que la prise en charge de l’envoi, la résolution de noms, etc. L’EVM ne parvient pas à fournir des garanties similaires, c’est très fortement lié à l’objectif initial d’Ethereum, pour présenter «une machine de calcul globale». Mais il est possible d’avoir une machine Turing Complète sans soumettre les développeurs aux risques que les VM modernes ont éliminés pendant des décennies. Au lieu de cela, l’EVM est conçue pour que ce soit les langages “de surface” codés par les utilisateurs qui abordent et résolvent ces risques. Les fonctionnalités telles que la répartition et l’abstraction sont entièrement gérées par le langage utilisateur, ce qui laisse aux développeurs un travail particulièrement difficile et trop d’occasions de commettre des erreurs critiques.

En effet, l’EVM est trop complexe pour être sûr dans son langage bytecode natif, mais ne contient pas suffisamment de fonctionnalités appropriées à la sécurité. Si l’EVM adoptait un modèle de calcul plus strict et Turing incomplet, il pourrait s’approcher des garanties de sécurité du bytecode de Bitcoin, Tezos, Cardano… D’un autre côté, si l’EVM offrait des fonctionnalités que l’on pourrait attendre d’une VM moderne, alors il se rapprocherait de la sécurité de bas niveau offerte par des machines telles que la machine virtuelle Java (JVM).

Les contrats de l’EVM pas assez “lisibles” pour être efficacement vérifiés de manière équationnelle

Le bytecode EVM n’est pas lisible par l’homme. Bitcoin par exemple fournit un langage bytecode sûr, simple et efficacement lisible destiné à garantir la cohérence et l’exactitude des programmes exécutés sur la blockchain. Il offre un jeu d’instructions minimal et utilise principalement le bytecode pour réduire les charges utiles. Il n’a jamais été destiné à être une cible de compilation pour un langage à usage général. Ces restrictions délibérées au niveau du langage minimisent la surcharge cognitive liée à la compréhension de ce qu’une charge utile donnée fait réellement afin que les développeurs puissent raisonner plus efficacement à la fois sur leur propre code et sur le code des autres, notamment on-chain.

Les créateurs de l’EVM ont conservé le modèle de bytecode mais ont abandonné l’idée de petits programmes faciles à comprendre. Au lieu de cela, ils ont considéré le bytecode comme un «code machine cible» dans lequel un code lisible par l’homme est compilé. C’est pourquoi Solidity est impossible à lire en chaîne et doit être décompilé pour être débogué ou validé.

Pact, MiniScript ou les jets de Simplicity… adoptent une approche différente en proposant un langage interprété qui est directement exécuté, au lieu d’être compilé comme Solidity. De plus, il est également construit avec une philosophie «plus petit est meilleur». Comme l’histoire l’a montré, si un langage ne peut pas fournir une optimisation d’inlining, de mise en cache et «juste à temps», un langage interprété peut surpasser son concurrent compilé, tout en offrant une lisibilité supérieure.

L’utilisation d’un interpréteur signifie que le code déployé place des contrats intelligents lisibles par l’homme directement sur la chaîne. En utilisant des langages de programmation fonctionnel développeur ou un lecteur peut utiliser un raisonnement équationnel (vérifications mathématiques) sur le code d’une manière que le bytecode EVM ne pourrait jamais admettre. Plusieurs projets au sein de l’écosystème Ethereum cherchent à fournir une assurance supplémentaire autour du code compilé, comme les efforts pour vérifier formellement les contrats intelligents Ethereum mais à cause notamment d’erreurs de conception de bas niveau, cela représente un effort et un risque nettement dissuasif.

L’EVM expose aux risques des langages Turing complets

L’EVM est présente dans les projets ETHEREUM, COMOS, POLKADOT..

Un bytecode construit dans le but de garantir l’exactitude, contient également une autre caractéristique importante pour des raisons de sécurité: l’incomplétude de Turing. Ce bytecode protège les programmes de vulnérabilité aux attaques récursives, et au risque d’être involontairement piégé dans une boucle infinie, ce qui est à la fois coûteux sur un ordinateur distribué et un exploit de sécurité en attente de se produire.

Au début de l’EVM, les développeurs d’Ethereum ont tenté d’améliorer le bytecode Bitcoin en offrant un jeu d’instructions qui était supposément similaire à Bitcoin mais en éliminant la contrainte Turing-incomplète en ajoutant JUMP, CALL et les instructions associées. Ceux-ci permettent une récursion sans fin et un flux de contrôle arbitraire vers les emplacements de code et d’autres contrats dans le système. Cela a des conséquences puissantes et profondes sur l’expressivité et l’exactitude des programmes exécutés sur l’EVM, ouvrant ces programmes à toute une classe de bogues et de comportements non déterministes qui sont inexprimables par un langage incomplet de Turing.

L’exploit DAO fournit un excellent exemple, dans lequel l’attaquant pourrait se retirer récursivement d’un portefeuille avant que les soldes ne soient réglés, déposer des millions sur leurs comptes avant de terminer. L’exploit DAO a également profité du modèle de distribution «méthode par défaut» Solidity, que toute machine virtuelle moderne interdit en principe, montrant le danger de laisser une telle fonctionnalité à des constructions de plus haut niveau. Pourtant, si l’EVM avait limité l’exhaustivité de Turing dans le premier cas, l’exploit DAO n’aurait pas pu se produire.

Nous voyons aujourd’hui l’émergence de meilleures pratiques dans l’écosystème de développement Solidity qui nécessitent une sorte de condition de terminaison dans le cas où le gaz s’épuise ou la récursivité se termine de manière prouvée. Il s’agit d’un cas d’utilisateurs restreignant leur propre jeu d’instructions; une situation qui aurait pu être évitée avec un modèle de calcul plus restreint de l’EVM.

La proportion de cas d’utilisation authentiques de récursivité dans l’environnement transactionnel blockchain est à la fois extrêmement petit et ne vaut pas le risque de sécurité à supporter. De plus, une blockchain est par définition un environnement limité en termes de calcul, pour limiter les efforts (coûteux) du réseau, ce qui signifie que de vrais cas d’utilisation récursifs entraîneront une incapacité à prédire l’utilisation des ressources et devraient être correctement exécutés hors chaîne.

Sans bibliothèques standards, et avec des appels externes, le modèle d’exécution EVM est coûteuse, lente et dangereuse

L’EVM laisse de nombreuses fonctionnalités et composants critiques de son modèle d’exécution non gérés, obligeant les concepteurs de langage à les implémenter manuellement. Par exemple, l’EVM laisse les fonctionnalités suivantes à l’implémentation du langage:

  • prise en charge de la bibliothèque, au lieu de seulement des cibles CALL
  • Types de données plus riches
  • Prise en charge directe et application des interfaces / API

De cette manière, l’EVM abandonne de nombreuses fonctionnalités déterminantes des vraies machines virtuelles, telles que la répartition, l’introspection de code et la fourniture d’une bibliothèque standard, ce qui rend l’environnement d’exécution coûteux, lent et dangereux.

Dans l’EVM, lorsqu’un contrat est exécuté, l’EVM utilise un modèle d’exécution opaque, «top-down», dans lequel le corps entier du contrat intelligent est chargé comme un bloc de code opaque et exécuté aveuglément à partir de la première instruction, pour s’exécuter jusqu’à sa fin. Les machines virtuelles comme la JVM, en revanche, comprennent quelles fonctions sont exprimées dans des espaces de noms ou des modules particuliers et leur permettent d’être chargées individuellement et appelées directement. Le modèle «top-down» signifie que lorsqu’un contrat externe est appelé, l’EVM doit charger l’intégralité du contrat afin de trouver et d’exécuter une seule fonction.

Les bibliothèques standard sont une autre fonctionnalité de VM courante qui n’est pas prise en charge par EVM. Dans la JVM par exemple, de nombreuses fonctions de base communes sont stockées dans une bibliothèque standard «rt.jar» distribuée avec la VM. Si les contrats intelligents avaient accès à une bibliothèque standard, ils seraient en mesure de reporter de nombreuses tâches courantes à la bibliothèque standard plutôt que de les implémenter directement dans le contrat intelligent. Le coût de l’exécution de chaque instruction dans un Le contrat intelligent utilisateur oblige à chaque fois les développeurs à payer un supplément de gaz important uniquement pour le privilège de mettre en place les fonctionnalités de base nécessaires à la rédaction du contrat, ce qui rend les contrats intelligents sur l’EVM beaucoup plus coûteux.

Les contrats intégrés représentent une solution de contournement insatisfaisante qui peut atténuer le coût du gaz mais ne fait rien pour améliorer le modèle d’exécution inefficace.

En plus d’être lents et coûteux, les appels externes sur l’EVM sont extrêmement dangereux. Étant donné qu’un contrat d’appel n’a pas la capacité de déterminer quelles références dans le contrat sont valides dans un autre contrat, il n’y a absolument aucune protection au moment de l’exécution contre une référence qui fait référence à une référence absente ou mal formée.

Ce phénomène a alimenté le problème du portefeuille Parity, où les portefeuilles appelaient un contrat central central qui a ensuite été supprimé, bloquant les fonds contenus dans ces portefeuilles. Pendant ce temps, le piratage DAO était un exemple de référence de contrat non sécurisée, conçue uniquement pour les comptes «utilisateurs» (EOA ou «comptes détenus en externe»), qui lorsqu’il est appelé par un compte contractuel malveillant, la méthode par défaut (exécutée de manière inexplicable sur la méthode de paiement «envoyer») permet de lancer l’exploit de récursivité.

Les langages plus ciblés, en revanche, proposent une bibliothèque standard comme premier principe, fournissant tous les outils nécessaires dont un auteur pourrait avoir besoin pour rédiger des contrats sûrs et efficaces et ne «manquera jamais d’opcodes» ou d’adresses de contrat intégrées, ce qui lui permettra d’incorporer de nouvelles fonctionnalités au fur et à mesure de la demande. En conséquence, le modèle de consommation reste bien défini et statique pour les fonctions définies nativement, ce qui permet à l’utilisateur de construire son code de manière modulaire tout en conservant la capacité de raisonner non seulement sur la fonctionnalité de son code, mais sur son coût dans la durée.

Sur le principe des jets ou de Pact de Kadena, lorsqu’un auteur invoque un autre contrat sur la blockchain, le code de fonction spécifique (avec toutes ses dépendances transitives) est intégré en permanence sur le site d’appel de l’utilisateur, de sorte que l’exécution n’est pas seulement rapide (le code est dans la portée immédiate) mais également impossible à subvertir ultérieurement, et donc beaucoup plus sûr.

Les contrats et les comptes référencés par adresses (registres d’adresses vs registres d’entrées/sorties) empêchent un support multi-signature natif et des contrats évolutifs sans contournements

Les contrats et les comptes dans Ethereum sont référencés par adresse, qui est une représentation hachée d’une clé publique qui jouit naturellement du droit d’accéder à l’adresse avec des privilèges élevés. La conséquence de ce choix de conception engage Ethereum et l’EVM à interdire la prise en charge native de l’authentification multi-signature et sous-tend le choix de conception erroné «code is law» pour rendre les contrats intelligents à jamais incapables de mettre à niveau le code à une adresse particulière.

C’est en effet l’une des raisons pour lesquelles l’EVM n’a pas de mécanisme de répartition moderne, car faire cela sans aucune sorte de résolution basée sur le nom serait un système encore plus opaque que ce à quoi nous sommes confrontés aujourd’hui. Cependant, le manque d’envoi et de résolution basée sur le nom garantit essentiellement que le code ne peut pas être mis à niveau, car il n’y a aucune représentation primitive de quel code est contenu à une adresse (comme un hachage de contenu, etc.) donc il n’y aurait aucun moyen de savoir si le code a été mis à jour.

Pour aggraver les choses, le format à adresse unique oblige essentiellement tous les contrats de l’EVM à être une signature unique et à utiliser des contrats intégrés coûteux et / ou plusieurs appels de transaction pour prendre en charge le multi-signature. Si l’EVM avait une répartition, un contrat pourrait être associé à un nom abstrait, ce qui découplent les signatures d’une adresse de contrat, ce qui permettrait à un contrat d’être nativement multi-signature.

En raison de ces lacunes, une industrie artisanale de portefeuilles de multi-signatures, de normes contractuelles et de techniques a été introduite afin de combler ce vide. Des contrats proxy, des fonctions de validation multi-signatures et des portefeuilles tels que Gnosis ou Parity Multisig ont été introduits pour fournir cette fonctionnalité indispensable, mais au prix de frais supplémentaires pour la multitude d’adresses régissant la transaction. En revanche, les procédés classiques (Bitcoin) n’ont jamais contraint leur conception autour d’une approche à signature unique, éliminant la motivation pour de telles solutions de contournement. Des ensembles de clés comme primitive de validation, signifient que toutes les transactions peuvent être choisies pour être multi ou mono-signature sans complexité supplémentaire pour le programmeur. En outre, Pact fournit un modèle de répartition approprié, permettant des contrats intelligents nommés (et donc évolutifs) régis par un jeu de clés à un ou plusieurs signatures, la conception par entrées/sorties n’est donc absolument pas un frein au contrats évolués.

--

--