Sécurité de la mémoire pour les polices Web

Dominik Röttsches
Dominik Röttsches
Rod Sheeter
Rod Sheeter
Chad Brokaw
Chad Brokaw

Publié le 19 mars 2025

Skrifa est écrit en Rust et a été créé pour remplacer FreeType afin de sécuriser le traitement des polices dans Chrome pour tous nos utilisateurs. Skifra exploite la sécurité de la mémoire de Rust et nous permet d'itérer plus rapidement sur les améliorations de la technologie des polices dans Chrome. Le passage de FreeType à Skrifa nous permet d'être à la fois agiles et audacieux lorsque nous apportons des modifications à notre code de police. Nous passons désormais beaucoup moins de temps à corriger les bugs de sécurité, ce qui permet de déployer des mises à jour plus rapidement et d'améliorer la qualité du code.

Cet article explique pourquoi Chrome a abandonné FreeType et fournit des informations techniques intéressantes sur les améliorations qu'il a permises.

Pourquoi remplacer FreeType ?

Le Web est unique en ce sens qu'il permet aux utilisateurs d'extraire des ressources non approuvées à partir d'une grande variété de sources non approuvées, en s'attendant à ce que tout fonctionne et qu'ils soient en sécurité. Cette hypothèse est généralement correcte, mais tenir cette promesse aux utilisateurs a un coût. Par exemple, pour utiliser une police Web de manière sécurisée (une police diffusée sur le réseau), Chrome utilise plusieurs mesures d'atténuation de la sécurité:

  • Le traitement des polices est mis en bac à sable conformément à la règle des deux : elles ne sont pas fiables et le code consommateur n'est pas sécurisé.
  • Les polices sont transmises au nettoyeur OpenType avant d'être traitées.
  • Toutes les bibliothèques impliquées dans la décompression et le traitement des polices sont soumises à des tests aléatoires.

Chrome est fourni avec FreeType et l'utilise comme bibliothèque de traitement des polices principale sur Android, ChromeOS et Linux. Cela signifie qu'un grand nombre d'utilisateurs sont exposés en cas de faille dans FreeType.

La bibliothèque FreeType est utilisée par Chrome pour calculer des métriques et charger des contours suggérés à partir de polices. Globalement, l'utilisation de FreeType a été un grand succès pour Google. Il effectue une tâche complexe et le fait bien. Nous nous appuyons beaucoup sur lui et nous y contribuons. Toutefois, il est écrit en code non sécurisé et remonte à une époque où les entrées malveillantes étaient moins probables. Le simple suivi du flux de problèmes détectés par le fuzzing coûte à Google au moins 0,25 ingénieur logiciel à temps plein. Pire encore, nous ne trouvons pas tout ou ne trouvons que des éléments après l'envoi du code aux utilisateurs.

Ce type de problème n'est pas propre à FreeType. Nous observons que d'autres bibliothèques non sécurisées présentent des problèmes, même lorsque nous faisons appel aux meilleurs ingénieurs logiciels que nous pouvons trouver, que nous examinons le code de chaque modification et que nous exigeons des tests.

Pourquoi les problèmes continuent-ils de se produire ?

Lorsque nous avons évalué la sécurité de FreeType, nous avons observé trois principales classes de problèmes (non exhaustives):

Utilisation d'un langage non sécurisé

Schéma/Problème Exemple
Gestion manuelle de la mémoire
Accès non vérifié au tableau CVE-2022-27404
Dépassements d'entiers Lors de l'exécution de machines virtuelles intégrées pour l'anticrépissage TrueType du dessin et de l'anticrépissage CFF
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
Utilisation incorrecte de l'allocation à zéro par rapport à l'allocation non à zéro Discussion sur https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, 8 problèmes de fuzzing détectés par la suite
Casts non valides Consultez la ligne suivante sur l'utilisation des macros.

Problèmes spécifiques au projet

Schéma/Problème Exemple
Les macros masquent l'absence de typage de taille explicite
  • Les macros telles que FT_READ_* et FT_PEEK_* masquent les types d'entiers utilisés, ce qui masque le fait que les types C99 avec des tailles explicites (int16_t, etc.) ne sont pas utilisés.
Le nouveau code ajoute systématiquement des bugs, même lorsqu'il est écrit de manière défensive.
  • COLRv1 et OT-SVG acceptent les deux problèmes générés
  • Le fuzzing en trouve certains, mais pas nécessairement tous, #32421, #52404
Manque de tests
  • Créer des polices de test est long et difficile

Problèmes de dépendance

Le fuzzing a identifié à plusieurs reprises des problèmes dans les bibliothèques sur lesquelles FreeType dépend, telles que bzip2, libpng et zlib. Par exemple, comparez freetype_bdf_fuzzer : utilisation d'une valeur non initialisée dans inflate.

Le fuzzing n'est pas suffisant

Le fuzzing (test automatisé avec un large éventail d'entrées, y compris des entrées aléatoires non valides) est destiné à détecter de nombreux types de problèmes qui se produisent dans la version stable de Chrome. Nous fuzzons FreeType dans le cadre du projet oss-fuzz de Google. Il détecte des problèmes, mais les polices se sont avérées quelque peu résistantes au fuzzing, pour les raisons suivantes.

Les fichiers de polices sont complexes, comparables aux fichiers vidéo, car ils contiennent plusieurs types d'informations différents. Les fichiers de polices sont un format de conteneur pour plusieurs tables, où chaque table a un objectif différent dans le traitement du texte et des polices pour produire un glyphe correctement positionné à l'écran. Dans un fichier de police, vous trouverez les éléments suivants:

  • Métadonnées statiques telles que les noms de polices et les paramètres des polices variables
  • Mappages des caractères Unicode sur des glyphes.
  • Ensemble de règles et de grammaire complexes pour la mise en page des glyphes à l'écran.
  • Informations visuelles: formes de glyphes et informations sur l'image décrivant l'apparence des glyphes placés à l'écran.
    • Les tables visuelles peuvent à leur tour inclure des programmes d'hinting TrueType, qui sont de petits programmes exécutés pour modifier la forme des glyphes.
    • Chaînes de caractères dans les tables CFF ou CFF2, qui sont des instructions de dessin de courbes et d'indications impératives exécutées dans le moteur de rendu CFF.

La complexité des fichiers de polices équivaut à disposer de son propre langage de programmation et de son propre traitement de machine à états, ce qui nécessite des machines virtuelles spécifiques pour les exécuter.

En raison de la complexité du format, le fuzzing présente des lacunes dans la détection des problèmes dans les fichiers de polices.

Il est difficile d'obtenir une bonne couverture de code ou une progression du fuzzeur pour les raisons suivantes:

  • Le fuzzing des programmes d'hinting TrueType, des chaînes de caractères CFF et de la mise en page OpenType à l'aide de modificateurs de style basculement de bits/décalage/insertion/suppression simples ne parvient pas à atteindre toutes les combinaisons d'états.
  • Le fuzzing doit produire au moins des structures partiellement valides. La mutation aléatoire ne le fait que rarement, ce qui rend difficile d'obtenir une bonne couverture, en particulier pour les niveaux de code plus profonds.
  • Les efforts actuels de fuzzing dans ClusterFuzz et oss-fuzz n'utilisent pas encore la mutation tenant compte de la structure. L'utilisation de mutators respectueux de la grammaire ou de la structure peut aider à éviter la production de variantes qui sont rejetées rapidement, mais au prix d'un développement plus long et de l'introduction de chances qui manquent des parties de l'espace de recherche.

Les données de plusieurs tables doivent être synchronisées pour que le fuzzing progresse:

  • Les modèles de mutation habituels des fuzzers ne produisent pas de données partiellement valides. Par conséquent, de nombreuses itérations sont rejetées et la progression devient lente.
  • La cartographie des glyphes, les tables de mise en page OpenType et le dessin des glyphes sont connectés et dépendent les uns des autres, formant un espace combinatoire dont les coins sont difficiles à atteindre avec le floutage.
  • Par exemple, la faille tt_face_get_paintCOLRv1 de gravité élevée a été détectée au bout de plus de 10 mois.

Malgré tous nos efforts, des problèmes de sécurité liés aux polices de caractères ont touché à plusieurs reprises les utilisateurs finaux. Remplacer FreeType par une alternative Rust évitera plusieurs classes entières de failles.

Skrifa dans Chrome

Skia est la bibliothèque graphique utilisée par Chrome. Skia s'appuie sur FreeType pour charger les métadonnées et les glyphes à partir des polices. Skrifa est une bibliothèque Rust, qui fait partie de la famille de bibliothèques Fontations. Elle fournit un remplacement sécurisé des parties de FreeType utilisées par Skia.

Pour passer de FreeType à Skia, l'équipe Chrome a développé un nouveau backend de police Skia basé sur Skrifa et a déployé progressivement le changement auprès des utilisateurs:

Pour l'intégration dans Chrome, nous nous appuyons sur l'intégration fluide de Rust dans le codebase introduite par l'équipe de sécurité Chrome.

À l'avenir, nous passerons également aux polices Fontations pour les polices du système d'exploitation, à commencer par Linux et ChromeOS, puis sur Android.

La sécurité avant tout

Notre objectif principal est de réduire (et dans l'idéal, d'éliminer) les failles de sécurité causées par un accès hors limites à la mémoire. Rust fournit cela dès la sortie de la boîte, à condition d'éviter tout bloc de code non sécurisé.

Nos objectifs de performances nous obligent à effectuer une opération actuellement non sécurisée: la réinterprétation d'octets arbitraires en tant que structure de données fortement typée. Cela nous permet de lire les données d'un fichier de police sans effectuer de copies inutiles. Cette approche est essentielle pour produire un analyseur de police rapide.

Pour éviter de créer notre propre code dangereux, nous avons choisi d'externaliser cette responsabilité à bytemuck, une bibliothèque Rust conçue spécifiquement à cette fin, qui est largement testée et utilisée dans l'écosystème. En concentrant la réinterprétation des données brutes dans bytemuck, nous disposons de cette fonctionnalité au même endroit et nous l'auditons, et nous évitons de répéter du code non sécurisé à cette fin. Le projet de transmutation sécurisée vise à intégrer cette fonctionnalité directement dans le compilateur Rust. Nous effectuerons la transition dès qu'elle sera disponible.

L'exactitude est importante

Skrifa est construit à partir de composants indépendants, où la plupart des structures de données sont conçues pour être immuables. Cela améliore la lisibilité, la facilité de maintenance et le multithreading. Cela rend également le code plus facile à tester. Nous avons profité de cette opportunité pour créer une suite d'environ 700 tests unitaires couvrant l'ensemble de notre pile, des routines d'analyse de bas niveau aux machines virtuelles d'indication de haut niveau.

La correction implique également la fidélité, et FreeType est très apprécié pour sa génération de contours de haute qualité. Nous devons répondre à cette qualité pour être un remplaçant approprié. Pour ce faire, nous avons créé un outil personnalisé appelé fauntlet qui compare la sortie de Skrifa et de FreeType pour des lots de fichiers de polices dans un large éventail de configurations. Cela nous permet de nous assurer que nous pouvons éviter les régressions de qualité.

De plus, avant l'intégration dans Chromium, nous avons exécuté un large ensemble de comparaisons de pixels dans Skia, en comparant le rendu FreeType au rendu Skrifa et Skia pour nous assurer que les différences de pixels sont absolument minimes, dans tous les modes de rendu requis (pour différents modes d'anticrénelage et d'hinting).

Les tests de fuzz sont un outil important pour déterminer comment un logiciel réagira aux entrées mal formées et malveillantes. Nous testons en continu notre nouveau code depuis juin 2024. Cela couvre les bibliothèques Rust elles-mêmes et le code d'intégration. Bien que le fuzzeur ait détecté (au moment de la rédaction de cet article) 39 bugs, il est à noter qu'aucun d'entre eux n'a été critique pour la sécurité. Ils peuvent entraîner des résultats visuels indésirables ou même des plantages contrôlés, mais ne provoquent pas de failles exploitables.

En avant !

Nous sommes très satisfaits des résultats de nos efforts pour utiliser Rust pour le texte. Fournir un code plus sûr aux utilisateurs et améliorer la productivité des développeurs est un grand succès pour nous. Nous prévoyons de continuer à rechercher des opportunités d'utiliser Rust dans nos piles de texte. Pour en savoir plus, Oxidize présente certains des futurs projets de Google Fonts.