
pour « favoriser la production de logiciels plus sécurisés, plus sûrs, d’une plus grande robustesse »
Le langage C offre une grande liberté aux développeurs. Cependant, il comporte des constructions ambiguës ou risquées qui favorisent l’introduction d’erreurs lors du développement. Le standard du langage C ne spécifie pas l’ensemble des comportements souhaités, et donc certains restent indéfinis ou non spécifiés. Libre alors aux développeurs de compilateurs, de bibliothèques ou de systèmes d’exploitation de faire leurs propres choix.
Pour l’Agence nationale de la sécurité des systèmes d'information (ANSSI), il est alors nécessaire de définir des restrictions quant à l’utilisation du langage C afin d’identifier les différentes constructions risquées ou non portables et d’en limiter voire interdire l’utilisation. Dans cette optique, l’agence a proposé un guide qui ambitionne de « favoriser la production de logiciels plus sécurisés, plus sûrs, d’une plus grande robustesse » en plus de favoriser leur portabilité d’un système à un autre, qu’il soit de type PC ou embarqué. Précisons que dans son document, l’Agence s’est limitée aux deux standards C90 et C99 qui restent « les plus utilisés ».
Convention de développement
Avant toute chose, tout projet de développement, quel qu’il soit doit suivre une convention de développement claire, précise et documentée. Cette convention de développement doit absolument être connue de tous les développeurs et appliquée de façon systématique. Chaque développeur a ses habitudes de programmation, de mise en page du code et de nommage des variables. Cependant, lors de la production d’un logiciel, ces différentes habitudes de programmation entre développeurs aboutissent à un ensemble hétérogène de fichiers sources, dont la vérification et la maintenance sont plus difficiles.
Aussi l’ANSSI propose la règle suivante : « Des conventions de codage doivent être définies et documentées pour le logiciel à produire. Ces conventions doivent définir au minimum les points suivants : l’encodage des fichiers sources, la mise en page du code et l’indentation, les types standards à utiliser, le nommage (bibliothèques, fichiers, fonctions, types, variables...), le format de la documentation. Ces conventions doivent être appliquées par chaque développeur ».
Cette règle autour d’une convention de développement est certes évidente et le but ici n’est pas d’imposer, par exemple, un choix de nommage de variable (tel que snake-case au lieu de camel-case), mais de s’assurer qu’un choix a bien été fait au début du projet de développement et que celui-ci est clairement explicité. En fait, au début de la réalisation d’un projet informatique, l’équipe de développement devrait toujours s’accorder sur les conventions de codage à appliquer. Le but est de produire un code source cohérent. Par ailleurs, le choix de conventions judicieuses permet de réduire les erreurs de programmation
L’ANSSI a donné un exemple de conventions de codage, prenant la peine de préciser que « certains choix sont arbitraires et discutables ». Et d’indiquer que « cet exemple de conventions peut être repris ou servir de base, si aucune convention de développement n’a été définie pour le projet à produire. Différents outils ou des éditeurs avancés sont en mesure de mettre en œuvre de façon automatique certaines de ces conventions de codage » :
- Encodage des fichiers :
- Les fichiers sources sont encodés au format UTF8.
- Le caractère de retour à la ligne est le caractère « line feed » \n (retour à la ligne au format Unix)
- Longueurs maximums :
- une ligne de code ou de commentaire ne doit pas dépasser 100 caractères.
- Une ligne de documentation ne doit pas dépasser 100 caractères.
- Un fichier ne doit pas dépasser 4000 lignes (documentation et commentaires compris).
- Une fonction ne doit pas dépasser 500 lignes.
- Indentation du code :
- L’indentation du code s’effectue avec des caractères espace : un niveau d’indentation correspond à 4 caractères espaces.
- L’utilisation du caractère de tabulation comme caractère d’indentation est interdite.
- La déclaration des variables et leur initialisation doivent être alignées à l’aide d’indentations.
- Un caractère espace est laissé systématiquement entre un mot clé et la parenthèse ouvrante qui le suit.
- L’accolade d’ouverture d’un bloc est placée sur une nouvelle ligne.
- L’accolade de fermeture de bloc est également placée sur une nouvelle ligne.
- Un caractère espace est laissé avant et après chaque opérateur.
- Un caractère espace est laissé après une virgule.
- Le point-virgule indiquant la fin d’une instruction est collé au dernier opérande de l’instruction.
Compilation
Les compilateurs proposent différents niveaux d’avertissement afin d’informer le développeur de l’utilisation de constructions risquées ou de la présence d’erreurs de programmation. D’un compilateur à l’autre, le comportement par défaut n’est pas identique pour une même version du standard C utilisée par exemple. Même les avertissements émis lors de la compilation sont directement liés à la version du compilateur. Il est donc primordial de connaître exactement le compilateur utilisé, sa version, mais aussi toutes les options activées avec, dans l’idéal, une justification pour chacune d’elles. De plus, les optimisations de code faites au niveau de la compilation et de l’édition de liens doivent être utilisées en pleine conscience des impacts associés au niveau de l’exécutable généré.
L’ANSSI estime que la maîtrise des actions à opérer à la compilation est nécessaire : « le développeur doit connaître et documenter les actions associées aux options activées du compilateur y compris en terme d’optimisation de code ».
Le niveau d’avertissement activé par défaut dans les compilateurs est souvent un niveau peu élevé,signalant peu de mauvaises pratiques. Pour l’Agence, ce niveau par défaut est insuffisant et doit donc être augmenté. Cela implique que les options de compilation utilisées doivent être explicitées.
Elle émet donc la règle suivante :
« Les options utilisées pour la compilation doivent être précisément définies pour l’ensemble des sources d’un logiciel. Ces options doivent notamment fixer précisément :
- la version du standard C utilisée (par exemple C99 ou encore C90);
- le nom et la version du compilateur utilisé;
- le niveau d’avertissements (par exemple-Wextra pour GCC);
- les définitions de symboles préprocesseurs (par exemple définir NDEBUG pour une compilation en release)».
Différentes options de durcissement à la compilation existent pour prévenir ou limiter l’impact, entre autres, des attaques sur le formatage de chaînes de caractères, des dépassements de tas ou de pile, de réécriture de section ELF ou de tas, l’exploitation d’une distribution non aléatoire de l’espace d’adressage. Ces options ne sont pas une garantie absolue contre ce type d’attaques, mais permettent d’ajouter des contremesures au code par rapport à une compilation sans durcissement. Ces options de durcissement peuvent avoir un lien direct avec les niveaux d’optimisation du compilateur (comme l’option-D_FORTIFY_SOURCE pour GCC qui n’est effective qu’avec un niveau d’optimisation supérieur ou égal à 1) et peuvent être actives, en partie, par défaut pour les versions les plus récentes du compilateur.
Face à cela, l’ANSSI en a fait une règle : « L’utilisation d’options de durcissement est obligatoire que ce soit pour imposer la génération d’exécutables relocalisables, une randomization d’adresses efficace ou la protection contre le dépassement de pile entre autres ». L’Agence demande cependant d’éviter les options d’optimisation du compilateur comme -fno-strict-overflow, -fwrapv,-fno-delete-null-pointer-checks, -fno-strict-aliasing qui peuvent affecter la sécurité.
La bonne pratique consiste à utiliser des générateurs de projets pour la compilation. En effet, l’utilisation d’un générateur de projets, tels que make, Cmake ou Meson, facilite la gestion des options de compilation. Celles-ci peuvent être définies de façon globale et appliquées à tous les fichiers sources à compiler.
Déclarations
Le langage C autorise la déclaration de plusieurs variables d’un même type simultanément en séparant chaque variable par une virgule. Cela permet d’associer à un groupe de variables un type donné et de regrouper ensemble des variables dont le rôle est lié. Cependant ce type de déclaration multiple ne doit être utilisé que sur des variables simples (pas de pointeur ou de variable structurée) et de même type.
L’ANSSI recommande donc que « Seules les déclarations multiples de variables simples de même type soient autorisées ».
Pour éviter également toute erreur dans l’initialisation de variables, les initialisations couplées à une déclaration multiple sont à proscrire. En effet, en cas d’initialisation unique à la fin de la déclaration multiple, seule la dernière variable déclarée est effectivement initialisée.
D’où la règle consistant à ne pas faire de déclaration multiple de variables associée à une initialisation : « les initialisations associées (i.e.consécutives et dans une même instruction) à unedéclaration multiple sont interdites ».
Déclaration libre de variables
Depuis C99, la déclaration de variables peut...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.