Analyse statique des sources C# (invisibles)

Votre outil d’analyse statique voit-il la source C# sous-jacente à votre source C# ? Je suis ingénieur compilateur chez ShiftLeft, le concepteur et (principal) implémenteur de la couche de langage de programmation de notre outil d’analyse statique pour C# et Python. Dans cet article, je discute un peu de l’analyse statique des programmes C#.
Lorsque vous compilez votre programme C# dans Visual Studio, un certain nombre de fichiers DLL et/ou EXE sont produits. Ces fichiers se composent, entre autres, du bytecode CIL (Common Intermediate Language), qui est traduit à partir de la source C# par le compilateur Roslyn. Au lancement d’une application, ce bytecode est compilé en JIT et exécuté sous l’environnement du CLR (Common Language Runtime). À ce stade, il serait bon que vous fassiez une analyse statique de votre programme.

Les outils d’analyse statique n’exécutent pas de programmes ; ils raisonnent sur les propriétés d’un programme en inspectant sa syntaxe – dans ce cas, celle de sa source C# ou de son bytecode CIL. D’une part, un outil d’analyse statique bénéficie de l’inspection d’une syntaxe proche de celle qui est exécutée, de sorte que les problèmes qu’il diagnostique sont des problèmes réels lors de l’exécution. D’autre part, un outil d’analyse statique bénéficie d’une syntaxe d’inspection proche de celle dans laquelle un programme est écrit, de sorte que les problèmes qu’il diagnostique captent mieux l’intention d’un programmeur et sont identifiables pendant le développement, c’est-à-dire avant l’exécution. Pour les outils axés sur la sécurité, en particulier, un avantage supplémentaire de l’analyse statique de la syntaxe source est que certaines vulnérabilités sont pilotées par l’API et, pour être diagnostiquées, nécessitent que les noms de programme soient reconnaissables (ce qui n’est pas toujours dans la syntaxe du bytecode).
En raison du compromis dans l’analyse statique de la correspondance source C# et du bytecode CIL, il est naturel qu’un ingénieur compilateur s’intéresse à la relation entre les deux syntaxes. Peut-être que vous, en tant que programmeur ou expert en sécurité, êtes intéressé à en savoir plus…
Un aperçu de la syntaxe de la syntaxe du bytecode CIL
Considérez la source C # ci-dessous (en haut à gauche). Sa syntaxe est assez similaire à la syntaxe – sélectionnée – de sa traduction de bytecode CIL (à droite). Par exemple, dans les deux syntaxes, nous voyons la déclaration d’un classer dont le nom est T. Pourtant, il y a peu de différences entre la source et le bytecode :
- Le mot clé de C# pour une déclaration de classe est class ; Le mot clé de CIL est .class.
- En C#, une classe étend implicitement object (au milieu à gauche) ; en CIL, l’héritage est explicite.
- La syntaxe de C# autorise certaines « abréviations » (en bas à gauche), par exemple, object abrège System.Object et int abrège System.Int32 .
- Au CIL, un champ de T , comme V, est déclaré avec le mot clé .field .

Malgré les correspondances évidentes dans le programme ci-dessus, en général, il y a beaucoup plus de dissemblances – plutôt que de similitudes – entre la syntaxe de la source C# et le bytecode CIL. (Sinon, quel serait le but de ce dernier ?) Par exemple, il n’y a pas d’expressions dans la source C# et j’ai omis toutes les instructions de son bytecode CIL. Fait intéressant, la correspondance entre les deux syntaxes est toujours intéressante, car il existe de nombreuses constructions dans lesquelles elles se chevauchent, par exemple en ce qui concerne les déclarations, comme nous venons de le voir.
La source C# et la dualité de la syntaxe du bytecode CIL
Considérez la source C# ci-dessous (en haut à gauche), et la déclaration de la classe U : elle déclare une auto propriété P. Maintenant, observez la traduction du bytecode CIL (à droite) de U : elle déclare, en tant que membres de U, un champ ‘
K__BackingField’ et deux les fonctions get_P et set_P . Un outil d’analyse statique qui inspecte la syntaxe du source C# doit comprendre (i) la déclaration de propriété P comme celui d’un champ(ii) toute expression qui lit P, comme V = P, comme un appel à get_P , et (iii) chaque expression qui écrit dans P, comme P = 1, comme un appel à set_P ; même si (ii) et (iii) ressemblent mission expressions. Inversement, un outil d’analyse statique qui inspecte la syntaxe du bytecode CIL doit comprendre ces des champs et les fonctions comme appartenant à une automobile propriété. En fait, il existe un source C# (en bas à gauche) qui est sémantiquement équivalent à l’original mais dont la correspondance avec le bytecode CIL est assez exacte.

Il existe un certain nombre d’approches pour concevoir un outil d’analyse statique qui est activé avec une double compréhension de la source C # / du bytecode CIL. Par exemple, nous pourrions inspecter la syntaxe du bytecode CIL et, en tant que post–action d’analyse, mapper – via un mécanisme séparé – les résultats vers la source C#. Mais l’élaboration d’un tel mécanisme d'”intelligence inverse” n’est pas anodine. De plus, cette approche serait insuffisante pour les noms d’API dans le source C# qui ne sont pas reconnaissables dans le bytecode CIL.
Une autre approche de la double compréhension de la source C # / du bytecode CIL consiste à inspecter la syntaxe des sources C # et, en tant qu’action dans l’analyse, désucrer (c’est-à-dire éliminer le sucre syntaxique) toute syntaxe “de haut niveau” en une syntaxe “de bas niveau” équivalente syntaxe plus proche du bytecode CIL. Un tel désucrage peut être logique ou physique. L’idée derrière un désucrage logique est de modifier les algorithmes et/ou les structures de données de votre analyse statique raisonner sur le source C# d’origine comme s’il s’agissait du source désucré. L’idée derrière un désucrage physique est de modifier la syntaxe d’une source C# et de soumettre à une analyse statique la source C# désucrée, au lieu de celle d’origine.
Dans une comparaison succincte entre le désucrage logique et physique, un avantage de ce dernier est que nous pouvons vérifier si notre modification de la syntaxe d’un programme est valide ou non (et, dans une certaine mesure, qu’elle est sémantiquement équivalente) en recompilant la source C# ; dans le premier cas, un bogue dans le peaufinage d’un algorithme et/ou d’une structure de données peut ne pas être remarqué – jusqu’à ce qu’une erreur faussement négative/positive le “détecte”.
Réécriture de la syntaxe source C#
L’analyse statique de ShiftLeft adopte l’approche d’inspection de la syntaxe des sources C#, avec le désucrage physique comme action dans l’analyse. Le désucrage physique est réalisé au moyen d’une technique connue sous le nom de réécriture de syntaxe. Notre outil de réécriture de syntaxe est construit avec le compilateur Roslyn ; Plus précisément, en tirant parti de son CSharpSyntaxRewriter. Nous avons rendu cet outil open source sous la licence Apache 2.0 ; vous pouvez le trouver sur https://github.com/ShiftLeftSecurity/SharpSyntaxRewriter.

Dans l’outil de réécriture de syntaxe (C) Sharp, vous trouverez une variété de réécritures. L’un d’eux est le réécrivain DecomposeNullConditional : il réécrit une instruction contenant une expression conditionnelle nulle comme obj?.f() dans l’instruction if ((object)obj != null) obj.f(). Un autre réécrivain est UninterpolateString : il réécrit une expression comme $”hi {name}” dans l’expression string.Format(“hi {0}”, name) . Il y en a d’autres… Vous trouverez ci-dessous un exemple dans lequel un programme est affiché avec sa source C# d’origine et sa traduction de bytecode CIL (en haut), et avec sa source C# désurée et sa traduction de bytecode CIL (en bas). Observez les appels à Add items to a list, GetEnumerator , get_Current et MoveNext . Tous les réécrivains de l’outil © Sharp Syntax Rewriter désucrent la syntaxe d’une source C # selon (une interprétation au mieux de) la spécification du langage.


Applications de l’outil de réécriture de syntaxe (C) Sharp
Chez ShiftLeft, l’objectif final de la réécriture de la syntaxe source C# est l’analyse statique. Mais il existe d’autres applications de notre outil. Supposons qu’un de vos anciens collègues ait écrit un analyseur C# personnalisé il y a quelque temps et qu’en raison de l’évolution du langage, il ne soit plus utilisable, car les constructions C# récentes ne sont pas reconnues : vous pouvez utiliser notre outil pour réécrire des constructions non reconnues dans (héritage) des reconnus. Ou supposons que votre organisation souhaite assurer une convention de codage pour une base de code : vous pouvez utiliser notre outil comme base d’un plugin de refactoring de code. Bien sûr, ces deux exemples supposent que l’outil (C) Sharp Syntax Rewriter Tool offre le réécrivain pour la tâche dont vous avez besoin – sinon, vous pouvez toujours l’étendre (faites-nous savoir si vous avez besoin d’aide).
Le code C # invisible a été initialement publié dans ShiftLeft Blog sur Medium, où les gens poursuivent la conversation en mettant en évidence et en répondant à cette histoire.
*** Ceci est un blog syndiqué du Security Bloggers Network de ShiftLeft Blog – Medium rédigé par Leandro TC Melo. Lisez le message original sur : https://blog.shiftleft.io/the-invisible-c-code-51f008d8930?source=rss—86a4f941c7da—4