Comment l’analyse statique avancée détecte des vulnérabilités de sûreté et sécurité ?

< Retour à la newsletter

Qu’est-ce que l’analyse statique avancée ?

Contrairement à l’analyse dynamique, dont le but est de tester le comportement d’un code en l’exécutant, l’analyse statique permet de détecter des erreurs logicielles ou des violations de règles par relecture, sans exécution, donc sans besoin de générer des cas de tests. Il convient cependant de distinguer deux types d’analyses statiques :

  • L’analyse statique classique : il s’agit de relire un code source linéairement. À l’instar d’un correcteur orthographique de traitement de texte, cette technique permet de détecter des violations par rapport à des règles de codage ou de bonnes pratiques, ainsi que des erreurs logicielles triviales, mais ne permet pas de détecter des failles complexes. Faite humainement à la manière d’une relecture de code, elle peut par contre permettre la détection de problèmes fonctionnels ;
  • L’analyse statique avancée, quant à elle, déduit des informations sur le comportement du logiciel basé sur une représentation statique, que l’on peut aussi qualifier de « modèle ». Il ne s’agit pas de simulation ni d’analyse dynamique, car il n’y a toujours pas d’exécution du « vrai » code ou même d’une représentation abstraite de celui-ci ; le but est d’extraire du code des informations sémantiques (chemins, valeurs possibles de variables, …), et de les utiliser pour découvrir des défauts potentiels ou d’autres propriétés intéressantes.
Le gros avantage de l’analyse statique avancée par rapport à l’analyse dynamique est sa capacité d’automatisation et à analyser un nombre bien plus important de chemins d’exécutions. Ainsi, certains problèmes logiciels pouvant mener à de sévères défaillances ou failles de sécurité, comme les erreurs « Runtime », les dépassements de mémoire, déréférencements de pointeurs nuls, divisions par zéro, fuites mémoires, injections de code, …, peuvent se détecter bien plus facilement en analyse statique avancée qu’en analyse dynamique.

Sa limitation est qu’elle ne peut en aucun cas adresser les problèmes fonctionnels, par définition réservés à une analyse « humaine », et qui est le but originel de l’analyse dynamique.
La qualité des détections de bugs en analyse dynamique dépend des cas de tests utilisés… Y compris lorsque l’on effectue une « couverture de code à 100% ». Si vous n’effectuez pas le dernier cas de test, l’analyse dynamique ne permettra jamais de détecter la division par zéro, contrairement à l’analyse statique avancée.
analyse_dynamiqueCodeSonar

Analyse de chemins, arbres syntaxiques et modélisation

Pour analyser l’ensemble des chemins d’exécutions possibles au sein d’un code, l’analyse statique avancée crée tout d’abord un graphe d’appels (callgraph), listant l’ensemble des appels entre fonctions de code au sein d’un projet logiciel. Ceci permet d’avoir une bonne vision des chemins possibles à l’intérieur d’une application.
callgraphCodeSonar
Graphe d’appels d’une application, partant de la fonction « main »

La seconde étape consiste à rentrer dans le détail de chaque fonction de code, au travers d’un graphe de flots de contrôle (flowgraphs ou CFG). Un flowgraph représente les liens entre blocs (l’ensemble des sections d’un code qui s’exécuteront d'un seul tenant, sans sauts) et arcs (sauts provoqués par des constructions comme les if, for, while, goto, jump, …). L’exemple suivant montre le lien entre un code source simple écrit en langage C et le graphe de flots de contrôle de cette application
Exemple d'un programme C, avec ses blocs CFG et son flowgraph

Les graphes d’appels et de flots de contrôle facilitent la modélisation des chemins d’exécution, permettant de remonter un chemin lorsqu’une construction connue comme dangereuse est détectée, comme par exemple une division.
bloc_rougeCodeSonar
Si une division est détectée dans le bloc rouge, seuls les chemins orange provenant des blocs jaunes sont à vérifier pour éviter toute division par zéro.

Les graphes d’appels et de flots de contrôle n’analysant pas les variables et leurs valeurs, ils sont souvent couplés avec les arbres syntaxiques abstraits (abstract syntax tree ou AST) afin de détecter des non initialisations, fuites mémoires, …

L’exemple suivant reprend le code source ci-dessus. Cet arbre affiche toutes les utilisations de la variable « c », en les liant avec les différents chemins possibles.
arbre_syntaxiqueCodeSonar
Arbre syntaxique abstrait du code source de la figure 3. Toutes les opérations effectuées sur le code source sont modélisées, et liées aux différents chemins d'exécutions possibles.

À partir de l’analyse des graphes d’appels et de flots de contrôle, ainsi que des arbres syntaxiques abstraits, il est possible de détecter des chemins menant à des erreurs critiques ainsi qu’à des vulnérabilités potentielles.

Et comment faire lorsque nous ne disposons pas du code source ?

En effet, quid des librairies tierces dont le client ne possède pas le code source ? De nombreuses parties d'applications sont en fait cachés aux yeux mêmes des fabricants des systèmes logiciels qui les intègrent, et ceux-ci peuvent être la porte ouverte à des défaillances, voire à la prise de contrôle du système…

Cependant, l’analyse d’un binaire diffère peu de l’analyse d’un code source, mis à part une première étape de désassemblage de l’exécutable, dans le but d’en générer un code « source » assembleur lisible.
desassembler_binaireCodeSonar
Désassembler un binaire revient à traduire le langage machine en un code assembleur « lisible ».

Le langage assembleur obtenu permet de retrouver une partie des chemins initialement prévus par les développeurs dans le code source du système logiciel. Bien que celle-ci soit bien plus ardue et nécessite des compétences spéciales en assembleur, l’analyse de ces chemins, couplée à celle des valeurs stockées en mémoire et des registres, peut permettre de détecter des vulnérabilités logicielles et des erreurs « Runtime » (pouvant se déclencher à l’exécution d’un logiciel) : fuites mémoires, dépassements de buffer, divisions par zéro, injections de commandes, libération de variables non stockées en pile ou déjà libérées, utilisation d’éléments après leur libération, … Les mêmes techniques (callgraphs, flowgraphs, AST) sont utilisées.
lien_chemin_c_assCodeSonar
Lien entre chemins, code C et code assembleur simplifié.

Automatiser la détection de bugs, d’erreurs « Runtime » et de vulnérabilités logicielles : la solution CodeSonar Source & Binary

Bien que possible pour des codes sources « exemples » de très petite taille, l’analyse statique avancée (que ce soit pour code source ou binaires) devient très vite irréalisable sans moyen d’automatisation.

L’outil d’analyse statique avancée CodeSonar, développé par la société américaine GrammaTech, détecte des vulnérabilités logicielles critiques, des erreurs « Runtime » ainsi que des violations de standards de codage (MISRA, CERT, CWE, …), sur des codes source C, C++, Java et C#, ainsi que sur des binaires x86, x64 et ARM (sans nécessité de disposer du code source !).
codesonar
CodeSonar est utilisé par :
  • Des PME comme des grands comptes industriels, afin d’améliorer la sûreté et sécurité de leurs logiciels, d’assurer la conformité à des standards comme l’IEC 61508, l’ISO 26262, l’IEC 62304 ;
  • Les agences gouvernementales Américaines, notamment durant des enquêtes (problèmes logiciels de la Toyota Prius, …) ;
  • La Food and Drug Administration (FDA), agence Américaine notamment chargée de l’analyse des logiciels de dispositifs médicaux (par exemple durant les rappels de dispositifs médicaux suite à des problèmes constatés);
  • Des sociétés de développement de logiciels IT, afin de détecter des vulnérabilités de sécurité et/ou des bugs critiques.
Vous souhaitez évaluer la pertinence des résultats de CodeSonar sur votre propre code source ou sur vos binaires ? N’hésitez pas à nous contacter pour démarrer une évaluation gratuite !

Micael MARTINS - Responsable Assurance Qualité Logiciel – mmartins@isit.fr