Une feuille de route de sécurité mémoire est un plan avec des responsables et des échéances, et vous pouvez en rédiger une ce trimestre sans toucher à la majeure partie de votre code C et C++. La réécriture viendra plus tard et ne portera que sur les parties qui la justifient. Ce qu'il vous faut dès maintenant : une vision claire de l'endroit où se trouve le code non sûr, un moyen de le contenir et une trace de ce que vous avez décidé.
Le document de la CISA, The Case for Memory Safe Roadmaps, décrit comment les éditeurs de logiciels peuvent migrer vers des langages à sécurité mémoire (MSL) afin d'éliminer les vulnérabilités liées à la mémoire. Il demande aux éditeurs de publier des feuilles de route pour que leurs clients voient comment ils assument la responsabilité des résultats en matière de sécurité, pratiquent la transparence radicale et adoptent une approche descendante. Ce sont des principes du Secure by Design, et le document s'adresse autant aux dirigeants qu'aux ingénieurs. Ce second public compte : une feuille de route sans sponsor finit oubliée dans un wiki.
Ce que couvrent les sources, et ce qu'elles ne couvrent pas
La page de synthèse de la CISA ne mentionne que la transition vers les MSL ainsi que la création et la publication d'une feuille de route. Elle ne montre pas le contenu complet du document ; nous n'affirmons donc rien sur ce qu'il dit du fuzzing ou de l'isolation de processus. Ces deux étapes ci-dessous relèvent de notre pratique, pour les années qui séparent « nous avons du C++ aujourd'hui » de « nous avons un langage plus sûr dans les parties qui comptent ». Considérez-les comme des mesures provisoires, pas comme un substitut à la feuille de route.
Les sources ne donnent pas non plus de chiffres sur le risque que chaque étape permet d'éliminer. Nous n'en inventerons pas. Mesurez-le vous-même, à partir de l'historique de vos plantages et de vos vulnérabilités.
Étape 1 : inventorier le code non sûr
On ne peut pas prioriser ce que l'on n'a pas listé. Construisez un tableau par composant, et non par dépôt, car un même dépôt mélange souvent un utilitaire sans risque et un parseur réseau.
| Champ | Pourquoi c'est important |
|---|---|
| Langage et standard | Indique ce qui peut ou non être migré |
| Accessible de l'extérieur ? | Réseau, fichier, USB, liaison série, canal de mise à jour |
| Traite des entrées non fiables ? | Détermine la priorité du fuzzing et de la réécriture |
| S'exécute avec quels privilèges ? | Détermine l'utilité de l'isolation |
| Tiers ou interne ? | Détermine qui peut corriger |
| Responsable et dernière modification substantielle | Le code mort peut souvent être supprimé |
Incluez les bibliothèques embarquées (vendored) et les composants de firmware. Les équipes matérielles, en particulier, ont souvent du vieux code C dans les bootloaders, les drivers et les gestionnaires de mise à jour que personne n'a ouverts depuis des années. Ces composants méritent une ligne.
Étape 2 : isoler ce que vous ne pouvez pas encore remplacer
Pour chaque composant marqué comme accessible et traitant des entrées non fiables, demandez-vous ce qui se passe s'il est entièrement compromis. Si la réponse est « tout », c'est votre premier changement d'architecture. Déplacez le parseur dans son propre processus, retirez-lui ses privilèges et supprimez son accès aux fichiers, aux sockets et aux clés dont il n'a pas besoin.
Cela ne corrige pas le bug. Cela limite ce qu'il vaut pour un attaquant. C'est cohérent avec l'objectif du Secure Software Development Framework (SP 800-218) du NIST : atténuer l'impact potentiel de l'exploitation de vulnérabilités que vous n'avez pas encore trouvées.
Étape 3 : soumettre les points d'entrée au fuzzing
Prenez dans votre inventaire les composants qui traitent des entrées non fiables et écrivez un harnais de fuzzing pour chacun. Exécutez ces harnais dans la CI avec un budget de temps, et conservez le corpus de plantages. Commencez par les formats de fichiers, les messages de protocole et les paquets de mise à jour, puisque ce sont les chemins qu'un tiers peut atteindre.
Compilez les binaires de fuzzing avec la détection d'erreurs mémoire du compilateur activée, afin qu'une lecture hors limites silencieuse devienne un échec visible. Chaque plantage que vous analysez vous apporte ce dont la feuille de route a besoin : des preuves indiquant quels composants méritent une réécriture. Si un harnais ne trouve rien après des exécutions prolongées, c'est utile aussi : vous pouvez classer ce composant plus bas.
Étape 4 : choisir une courte liste de réécriture
Servez-vous des trois premières étapes pour choisir les candidats. Une bonne première réécriture présente ces propriétés :
- Elle traite des entrées non fiables.
- Elle a une interface étroite, ce qui permet de la remplacer derrière la même API.
- Elle a produit des plantages lors du fuzzing ou possède un historique de bugs.
- Elle est assez petite pour qu'une seule équipe la termine en une ou deux itérations de livraison.
Laissez en C ou C++ le code stable, interne et non exposé, et dites-le dans la feuille de route. Écrire « non planifié, isolé, fuzzé » à côté d'un composant est honnête, et vos clients savent en tirer parti. C'est notre interprétation de la transparence radicale : vous exposez votre position, et toutes les lignes n'ont pas à se terminer par une réécriture. La synthèse de la CISA nomme ce principe mais ne dit pas ce qu'il implique pour les composants qui restent en C ou C++.
Les nouveaux composants sont l'endroit le moins coûteux pour commencer. Décidez dès maintenant quel langage sera utilisé pour tout nouveau code exposé au réseau, et inscrivez-le dans vos standards d'ingénierie.
Étape 5 : formaliser par écrit et l'intégrer à votre processus
Le NIST décrit le SSDF comme un socle de pratiques de haut niveau à intégrer dans le cycle de développement que vous utilisez déjà, dans le but de traiter les causes racines pour que les vulnérabilités ne réapparaissent pas. Intégrez vos règles de sécurité mémoire à ce processus existant : choix du langage pour le nouveau code, fuzzing dans la CI, point de contrôle en revue pour les constructions non sûres. Le SSDF offre aussi aux acheteurs et aux fournisseurs un vocabulaire commun, ce qui aide lorsque le questionnaire de sécurité d'un client aborde la sécurité mémoire.
Pour les listes de contrôle de codage, le projet OWASP Secure Coding Practices Quick Reference Guide est archivé. Ses listes ont été reprises dans l'OWASP Developer Guide ; utilisez cette version plutôt qu'un PDF périmé.
Par où commencer
- Exportez la liste de tous les composants C et C++, y compris le code embarqué (vendored) et le firmware, puis remplissez le tableau d'inventaire ci-dessus. Limitez-vous à deux semaines.
- Repérez les composants qui lisent des entrées non fiables et listez ce à quoi chacun peut accéder s'il est compromis.
- Placez les trois composants exposés les plus critiques dans des processus séparés à faibles privilèges, ou planifiez la modification avec un responsable nommé.
- Écrivez des harnais de fuzzing pour ces mêmes composants et exécutez-les dans la CI. Passez les plantages en revue chaque semaine.
- Choisissez un composant à réécrire dans un langage à sécurité mémoire, et fixez une règle de langage pour tout nouveau code exposé au réseau.
- Publiez en interne une feuille de route d'une page avec responsables et échéances, puis décidez de ce que vous partagez avec vos clients. Si vous souhaitez un regard extérieur, notre service de développement logiciel sécurisé et nos tests d'intrusion peuvent examiner l'inventaire et tester les composants isolés.
