Améliorations de l’analyse statique dans GCC 14
(developers.redhat.com)- Avec GCC 14,
-fanalyzersuit plusieurs chemins d’exécution du code C par exécution symbolique et se concentre sur la détection, à la compilation, de défauts difficiles à repérer avant l’exécution - Les améliorations se répartissent entre détection des boucles infinies, visualisation des dépassements de tampon, suivi des chaînes C et activation par défaut de l’analyse de contamination, avec l’objectif de rendre les diagnostics plus compréhensibles
-Wanalyzer-out-of-boundsva au-delà d’un simple avertissement en montrant, via des diagrammes textuels, la relation entre le tampon et l’emplacement d’écriture, et indique aussi à quel endroit, au milieu d’un caractère, le dépassement se produit dans une chaîne UTF-8- Le nouvel attribut
null_terminated_string_arg(PARAM_IDX)signale à l’analyseur et aux lecteurs du code qu’une API attend une chaîne terminée par nul, ce qui met mieux en évidence les chemins où un tampon non terminé par nul est transmis - L’analyse de contamination est activée par défaut lorsque
-fanalyzerest sélectionné, ce qui permet d’identifier plus facilement les flux où une valeur contrôlée par un attaquant est utilisée comme taille, index, décalage, etc. sans contrôle de borne supérieure
Périmètre des problèmes traités par -fanalyzer
-fanalyzerest une passe d’analyse statique de GCC qui tente de trouver des défauts à la compilation en exécutant symboliquement plusieurs chemins d’exécution du code source C- Les améliorations de GCC 14 ont été présentées avant la sortie officielle d’avril 2024 ; au moment de la rédaction, la sortie de GCC 14.1 était attendue courant avril 2024
- La préversion de GCC 14.0 est déjà utilisée dans Fedora 40 Beta
- Les exemples peuvent être testés dans Compiler Explorer avec les nouvelles options du compilateur
Détection des boucles infinies simples
- GCC 14 ajoute un nouvel avertissement
-Wanalyzer-infinite-loop - L’exemple de code contient des boucles
forimbriquées : la condition de la seconde boucle estj < n, mais l’expression d’incrémentation est restéei++- Cela correspond à une erreur où la première boucle
fora été copiée, sans remplacer leide l’incrémentation parj - Sur ce chemin, l’analyseur suit continuellement la branche vraie de
j < net signale les itérations de boucle
- Cela correspond à une erreur où la première boucle
- La sortie de diagnostic actuelle est plus facile à comprendre en lisant les événements numérotés de
(1)à(5)dans l’ordre - Pour GCC 15, l’amélioration de la lisibilité au moyen de techniques comme de l’art ASCII mettant en évidence les chemins de flot de contrôle reste un souhait
- Ce code peut être exécuté dans cet exemple Compiler Explorer
Visualisation textuelle des dépassements de tampon
- Dans GCC 13, l’analyseur a commencé à prendre en charge les contrôles de bornes via
-Wanalyzer-out-of-bounds - Dans GCC 14, il peut produire un diagramme textuel des relations spatiales d’un dépassement de tampon prédit
- Dans un exemple qui exécute
strcpy(buf, "hello")surchar buf[10], puis appellestrcat(buf, " world!"), un dépassement de tampon sur la pile est détecté- L’ancien message indiquait que
bufavait une capacité de 10 octets et qu’une écriture hors limites se produisait de l’octet 10 à l’octet 12 - Le diagramme de GCC 14 montre à la fois le tampon de destination rempli par
strcpyet l’octetNULde fin existant qui sert de point de départ àstrcat
- L’ancien message indiquait que
- Même dans des exemples de chaînes non ASCII, il suit la représentation UTF-8 et indique l’emplacement du dépassement
- Le code utilisé en exemple copie
"サツキ"danschar buf[11], puis y concatène"メイ" - Le diagramme montre que le dépassement se produit au milieu du caractère
メ, c’est-à-dire U+30E1
- Le code utilisé en exemple copie
- Le code correspondant est disponible dans les exemples chaîne ASCII et chaîne non ASCII
Suivi renforcé des opérations sur les chaînes C
- L’analyseur de GCC 14 améliore le suivi des opérations sur les chaînes C en simulant les API qui parcourent un tampon à la recherche de l’octet de terminaison nul
- Il émet un avertissement s’il existe un chemin où le pointeur désigne un tampon non terminé par nul et est transmis à cette API
- Le nouvel attribut de fonction
null_terminated_string_arg(PARAM_IDX)indique à l’analyseur et aux lecteurs du code qu’un paramètre donné doit être une chaîne terminée par nul - Dans l’exemple,
example_fn(const char *p)porte les attributsnull_terminated_string_arg(1)etnonnullchar str[3] = "abc";ne dispose pas d’espace pour contenir l’octet de terminaison nul- Lors de l’appel
example_fn(str), l’analyseur signale une lecture excessive de tampon sur la pile d’un octet au-delà de la fin destr - Le diagnostic inclut aussi une note indiquant que le premier argument de
example_fndoit être un pointeur vers une chaîne terminée par nul
- Cet exemple peut être exécuté dans Compiler Explorer
Activation par défaut de l’analyse de contamination
- L’analyse de contamination de l’analyseur suit les entrées contrôlées par un attaquant, les points de validation et les endroits où elles sont utilisées sans validation
- Dans les versions précédentes de GCC, elle n’était pas activée par défaut en raison de bogues et de nombreux faux positifs, et restait cachée derrière un argument de ligne de commande distinct
- Dans GCC 14, après plusieurs corrections de bogues, l’analyse de contamination est activée par défaut lorsque
-fanalyzerest sélectionné - Ce changement active aussi les six avertissements fondés sur la contamination suivants
Exemple d’analyse du noyau avec CVE-2011-2210
- Un extrait lié à CVE-2011-2210 dans le noyau Linux est utilisé comme exemple d’analyse de contamination
- En ajoutant
__attribute__((tainted_args))à la macro__SYSCALL_DEFINEx, on indique à l’analyseur que les arguments deosf_getsysinfoproviennent d’une frontière de confiance et doivent être traités comme des valeurs contaminées - L’analyseur de GCC 14 avertit que, dans
copy_to_user(buffer, hwrpb, nbytes), la valeur contrôlée par l’attaquantnbytesest utilisée comme taille sans contrôle de borne supérieure- Le diagnostic indique que
nbytesn’a fait l’objet que d’un contrôle de borne inférieure dansif (nbytes < sizeof(*hwrpb)) - Le troisième paramètre de
copy_to_userest indiqué comme paramètre de taille par l’attributaccess(write_only, 1, 3)
- Le diagnostic indique que
- Le problème vient du fait que la condition de validation est écrite
if (nbytes < sizeof(*hwrpb))- La condition prévue était de la forme
if (nbytes > sizeof(*hwrpb)) - Dans la version où la condition est corrigée, l’analyseur n’émet pas d’avertissement
- La condition prévue était de la forme
- Le travail consistant à exécuter l’analyseur sur le noyau, à trouver des vulnérabilités et à corriger les faux positifs de l’analyseur se poursuit
1 commentaires
Commentaires Hacker News
Pour moi, fanalyzer est l’une des fonctionnalités décisives de GCC face à Clang. Il explique les erreurs, ce qui rend la programmation en C bien plus facile, et ses messages d’erreur commencent à me faire penser à Rust par leur côté convivial pour les développeurs
L’une des principales raisons pour lesquelles on abandonne l’apprentissage d’une technologie, ce sont souvent des erreurs frustrantes ou peu claires. Je m’éloigne un peu du sujet, mais je voulais dire que j’aime le C et que de meilleurs messages d’erreur, au niveau de Rust, le rendraient encore meilleur
error: missing semicolon, je me retrouvais avec plus de 1000 lignes de messages sur l’instanciation de templatesJ’ai essayé l’analyseur de GCC plusieurs fois, mais je n’ai pas trouvé de bon frontend qui rende sa sortie agréable à lire. Clang propose pas mal d’options, comme une sortie HTML assez correcte, CodeChecker, l’intégration Xcode, etc. Je me demande donc comment vous lisez la sortie côté GCC. En plus, GCC semble produire bien plus de faux positifs que Clang
Il y a 36 autres commentaires dans un autre fil : https://news.ycombinator.com/item?id=39918278
« GCC 14 Boasts Nice ASCII Art for Visualizing Buffer Overflows (phoronix.com) », publié il y a 2 heures
Il y a quelques mois, j’ai créé un petit utilitaire Linux. C’était un shim drop-in qui remplaçait n’importe quel exécutable ; lorsqu’il était appelé, il se faisait passer pour le programme d’origine, forkait l’original et se branchait sur stdout/stderr
La sortie d’erreur était envoyée à un assistant GPT personnalisé connaissant le contexte du programme, qui reformulait l’erreur d’origine dans une forme plus lisible par un humain avant de la renvoyer vers stderr du shim. Je l’utilisais parce que j’en avais assez de lire les dumps imbriqués des compilateurs GCC/Clang autour des concepts/templates, mais on pouvait en fait l’utiliser avec n’importe quel programme. Ça marchait bien, mais je suis tombé gravement malade et je n’ai pas pu continuer ; si quelqu’un reprenait l’idée pour en faire quelque chose de plus propre et général, ce pourrait être un beau projet
J’aimerais vraiment un meilleur format de sortie pour les résultats d’analyse. Dans sa forme actuelle, c’est l’enfer pour les lecteurs d’écran
Un exemple de sortie est visible ici : https://godbolt.org/z/aan6Kfxds
J’ai simplement ajouté l’option de ligne de commande
-fdiagnostics-format=sarif-stderrau premier exemple de l’article. J’ai aussi expérimenté une sortie des diagrammes en SVG, mais ce n’était pas assez abouti pour l’intégrer dans GCC 14Le code d’origine renvoyait
-1sinbytes < sizeof(*hwrpb), et-2sicopy_to_user(buffer, hwrpb, nbytes)échouait. La correction appliquée a consisté à vérifiernbytes > sizeof(*hwrpb), mais à mon avis la vraie correction seraitcopy_to_user(buffer, hwrpb, sizeof(*hwrpb))Copier depuis le pointeur
hwrpbavec une taille autre quesizeof(*hwrpb)n’a pas de sensnbytes = 4et quesizeof(hwrpb)vaut 16 octets, alors 12 octets de trop seront copiés depuis le tampon de l’appelant, ce qui peut lire une mémoire qui ne lui appartient pas. À éviter, selon moiUne meilleure solution serait de ne copier que le minimum d’octets pris en charge à la fois par l’appelant et l’appelé. Par exemple,
nbytes = MIN(nbytes, sizeof(hwrpb));. En supposant que les informations de version danshwrpb->sizesoient respectées, cela permettrait d’assurer la compatibilité ascendante et descendante même si une partie de la structure n’est pas initialiséenbytesC’est vraiment excellent. Le volume de travail fourni a l’air énorme. La difficulté me semble comparable à l’introduction de fat pointers / vues de tableaux dans la bibliothèque standard et dans le standard C
-Wstringop-overflowproduit tellement de faux positifs que c’est l’une des premières alertes que je désactive. Je doute qu’une variante de l’analyseur fasse mieuxTrès sympa. Je ne fais plus beaucoup de développement C ces jours-ci, donc je me demande à quelle fréquence
strcpyetstrcatsont encore utilisés. La dernière fois que j’ai regardé, ils étaient presque aussi tabous quegotoBien sûr, je sais qu’en développement kernel,
gotoest souvent privilégié. Je me demande dans quelle mesure l’analyse des chaînes C est réellement utilegotoest clairement la bonne solution, et même élégante. Essayer de l’éviter à tout prix peut produire un code laid, embrouillé et difficile à maintenir. Ce n’est pas fréquent, mais il y a des usages légitimesDes fonctions comme
strcpysont moins recommandées, mais il existe des cas où leur utilisation est garantie correcte tant que des invariants plus forts — par exemple au niveau du langage — ne sont pas violés. Si ces invariants sont déjà rompus, le problème est de toute façon bien plus grave. C’est rare, mais on peut aussi soutenir qu’une alternative nominalement plus sûre sera parfois un peu moins efficace sans réel bénéficegotorestent idiomatiques en C. Il faut être prudent, bien sûr, mais enfin, c’est du CEn revanche, je n’aime pas du tout
longjmpgoto. En revanche, la famille strxcpy est complètement catastrophique et ne devrait être utilisée sous aucun prétexte. Le fait qu’elle soit utilisée dans le kernel est effrayantCes fonctions, ainsi que toutes les tentatives ratées de les « corriger », auraient dû être éjectées en orbite
gotopeut aller si on l’utilise avec discernement.strcpyetstrcataussi, dans le sens où ils « vont » tant qu’on sait que le code est correct, et que s’il ne l’est pas les conséquences peuvent être graves. Malheureusement, cette description s’applique à la majeure partie du Cgotoest aussi tabou questrcatoustrcpy.goto, ça va ; en revanche,strcatetstrcpyutilisés sansmallocde taille exacte dans le même scope, c’est presque une mauvaise odeur de codeTrès bien. Je suis content de voir que des rapports détaillés expliquant précisément ce qui ne va pas sont fournis partout