Les ingénieurs d'Apple ont annoncé être prêts à tester le mode « -fbounds-safety » pour le compilateur Clang, qui garantit un fonctionnement sécurisé avec les tampons dans le code C. Ce mode fait partie d'un fork LLVM soutenu par Apple pour le projet Swift. À l'avenir, un passage progressif des fonctionnalités de « -fbounds-safety » à la base de code principale de LLVM/Clang est prévu.
Il est à noter que le mécanisme de protection proposé est déjà activement utilisé dans des produits Apple, tels que le noyau XNU, les firmwares, les bibliothèques audio et les décodeurs d'images. L'activation du mode « -fbounds-safety » réduit les performances des applications d'environ 5 % (variation de -1 % à 29 %), augmente la taille du code de 9,1 % (variation de -1,4 % à 38 %) et ralentit la compilation de 11 %.
L'utilisation du mode « -fbounds-safety » pour la détection automatique des dépassements de mémoire liés aux pointeurs nécessite l'ajout d'annotations spéciales dans le code et l'inclusion du fichier d'en-tête « ptrcheck.h ». La méthode de protection proposée repose sur l'attachement automatique de vérifications des limites autorisées, ajoutées sur la base des annotations manuelles ou des tailles connues par le compilateur.
Contrairement à l'utilisation de pointeurs étendus dans le code, qui incluent des informations sur la limite supérieure et inférieure du tampon, le mode « -fbounds-safety » ne porte pas atteinte à l'ABI (Application Binary Interface), ne modifie pas le format des pointeurs exportés et ne nécessite pas une refonte immédiate de l'ensemble du projet. Dans le mode « -fbounds-safety », les pointeurs étendus sont appliqués uniquement dans des zones ne chevauchant pas l'ABI, tandis que pour les pointeurs influençant l'ABI, des pointeurs ordinaires avec des vérifications dérivées d'annotations sont utilisés.
Les annotations doivent être attachées aux pointeurs dans les champs de structures et les paramètres de fonctions pointant vers des tableaux d'objets, ainsi qu'aux variables globales avec des pointeurs. Pour les pointeurs dans les variables locales, il n'est pas nécessaire d'ajouter d'annotations, car ils sont automatiquement traités comme des pointeurs étendus, incluant déjà des informations sur les limites autorisées. Le compilateur affichera des suggestions concernant les constructions dans le code nécessitant des annotations lors de l'exécution avec le flag « -fbounds-safety ».
Le modèle de protection basé sur «-fbounds-safety» peut être implémenté progressivement, fichier par fichier, sans interrompre le développement de l'ensemble du projet. L'ajout de protections au projet se résume à indiquer des annotations dans un fichier de code spécifique, à corriger les avertissements du compilateur et à effectuer des tests de fonctionnement du programme, après quoi ces étapes sont répétées pour le fichier suivant. Le code avec les annotations ajoutées reste compatible avec le code C standard et les compilateurs qui ne supportent pas «-fbounds-safety» (lors de la compilation avec d'autres compilateurs ou sans le drapeau «-fbounds-safety», aucune vérification de limite supplémentaire ne sera ajoutée).
Au cours de l'exécution du programme, en cas de tentative d'accès en dehors des limites autorisées, une exception est générée et le programme se termine. Une terminaison anormale peut également se produire en cas d'annotations incorrectes, c'est pourquoi lors de l'utilisation de «-fbounds-safety», des tests supplémentaires du fonctionnement du programme sont nécessaires.
Dans l'exemple ci-dessous, l'annotation «__counted_by(n)» est insérée dans le paramètre «int *p», ajoutant une vérification supplémentaire sur les limites autorisées qui s'applique à l'exécution. Si l'on tente de compiler le code en mode «-fbounds-safety» sans spécifier cette annotation, le compilateur émettra un avertissement indiquant l'absence d'informations sur les limites du tableau lors du traitement de l'expression «p[i] = 0». #include void init_buf(int *__counted_by(n) p, int n) { for (int i = 0; i < n; ++i) p[i] = 0; // en mode «-fbounds-safety», le compilateur ajoutera automatiquement une vérification, semblable au code «if (i = n) trap();» }
Pour les variables locales avec des pointeurs, les vérifications sont ajoutées automatiquement, par exemple : void foo(int i){ char *buf = (char *)malloc(10); // des informations sur les limites seront conservées pour le pointeur buf buf[i] = 0xff; // une vérification automatique sera ajoutée «if (buf + i = buf + 10) trap();» }
Dans la mesure du possible, le compilateur optimise et évite d'ajouter du code superflu si les vérifications nécessaires sont déjà présentes. Par exemple : for (size_t i = 0; i < count; ++i) { buf[i] = i; // la vérification "if (i = count) trap()" ne sera pas ajoutée, car il existe déjà une condition «i < count» et i ne peut pas être inférieur à 0. }
Annotations principales :
- «__counted_by(N)» — définit la taille du buffer en éléments du type cible.
- «__sized_by(N)» — définit la taille du buffer en bytes.
- » __ended_by(P)» — établit la limite supérieure du tampon.
- «__null_terminated» — considère le caractère nul comme fin du tampon.
- «__single» — lie le pointeur à un seul objet. Utilisé par défaut pour les pointeurs affectant l'ABI, sauf si une annotation explicite est spécifiée.
- «__bidi_indexable» — pointeur avancé contenant des informations sur les limites supérieures et inférieures. Utilisé par défaut pour les pointeurs n'affectant pas l'ABI.
- «__indexable» — pointeur avancé avec des informations sur la limite supérieure.
- «__unsafe_indexable» — pointeur sans vérification des limites (pour la portabilité avec du code non sécurisé, par exemple, pour obtenir des pointeurs à partir de code externe).
Source : opennet.ru
