Alors que je cherchais un outil d'analyse statique pour PHP, j'étais tombé sur la page Tool Survey du projet Software Assurance Metrics And Tool Evaluation. Ce projet, mené par le NIST et financé par le DHS, m'intéresse car il vise à tester et classer divers outils servant pour un audit de sécurité. Plus particulièrement, la page Source Code Security Analyzers dresse une longue liste d'outils d'analyse statique de code.

Rien ne vaut une relecture manuelle et attentive

Je garde un souvenir amer de l'analyse statique. J'avais testé SPlint, FlawFinder et RATS pour m'aider à relire le code source du pare-feu NuFW écrit en C. Ces outils sont peu efficaces car on est rapidement noyés sous une tonne de faux positifs. On perd finalement plus de temps à affiner leur configuration qu'à trouver des bugs. Je préfère encore une relecture manuelle et attentive du code.

Quand les ressources humaines sont insuffisantes pour relire toute la base de code, il faut choisir efficacement les portions de code à auditer. Il vaut mieux commencer par celles qui traitent les données venant de l'utilisateur. Une autre approche est de rechercher des erreurs classiques comme les dépassements de tampon, erreur de formatage printf, injection SQL, etc. Je vous conseille d'ailleurs d'aller lire les recommandations du CERT : Secure Coding Standards.

Trouver une erreur memset avec Google

On peut s'amuser à exploiter les moteurs de recherche de code pour trouver des failles connues : koders.com, krugle.com et Google codesearch. Seul Google codesearch supporte les expressions régulières, sans référence arrière malheureusement. On peut également trouver des mots de passe codés en dur et autres indiscrétions. C'est le moment de redécouvrir la Google Hacking Database pour vous donner des idées. Recherchons par exemple une utilisation incorrecte de la fonction memset() par l'inversion du 2e et 3e argument. Utilisez le motif suivant avec Google codesearch :

memset\([^,]+,[^,]+,\ *0\)

On trouve cette erreur dans divers projets dont certains très connus : OpenSSL, GnuPG, Prelude, Linux, Mozilla, Python, Parrot, Kaffe, aMule, µClibc, libgphoto2, ATI gatos, WINE, Blender, etc. Après vérification dans quelques projets (en particulier OpenSSL, GnuPG et Prelude !), l'erreur est corrigée dans la dernière version. Le fait qu'elle ait existé un temps prouve que de meilleurs outils d'analyse statique seraient utiles !

Autre exemple : erreur memcpy

L'instruction « memcpy(dest, source, sizeof(dest)); » est incorrecte quand dest est un pointeur. La taille copiée est celle du pointeur et non pas celles des données pointées ! Voici donc deux commandes pour rechercher des utilisations incorrectes des fonctions memcpy(), memmove(), strncpy(), g_memdup(), memset() et wmemset() :

find DOSSIER -name "*.c"|xargs egrep -H '(memcpy|memmove|strncpy|memset|g_memdup)\(([^,]+), .*sizeof\(\2\)\)'
find DOSSIER -name "*.c"|xargs egrep -H 'w?memset([^,]+,[^,]+, *0)'

Sauf que strncpy() (et ses voisines) peuvent fonctionner pour « strncpy(dest, source, sizeof(dest)); » quand dest n'est pas pas un pointeur mais un tableau de taille fixe comme « char buffer[256]; »...

Complexité McCabe d'une fonction

Pour finir, un ami (misc) m'a fait découvrir aujourd'hui l'outil pmccabe par le billet The Cyclomatic Horror From Outer Space. Ce programme sert à estimer la complexité d'une fonction sachant qu'une note supérieure à 50 indique une fonction « non testable (risque très élevé) ». Je me suis amusé à lancer pmccabe sur la GNU libc avec la commande :

rm /tmp/out; find . -name "*.c"|xargs pmccabe >>/tmp/out; sort -nr /tmp/out|head

Voici les pires fonctions :

  • 494 : scanf()
  • 230 : fnmatch()
  • 222 : strtod()
  • 200 : collate_read()
  • 197 : dl_main()
  • 175 : wordexp()

Dans les commentaires du blog, on apprend que gcc explose le record avec une fonction à plus de 1000. Une scéance de refactoring cuisse-abdo-fessier ne ferait pas de mal à la libc et à gcc...