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. Skrifa tire parti de la sécurité mémoire de Rust et nous permet d'itérer plus rapidement sur les améliorations de la technologie de police dans Chrome. Passer de FreeType à Skrifa nous permet d'être à la fois agiles et audacieux lorsque nous modifions notre code de police. Nous passons désormais beaucoup moins de temps à corriger les bugs de sécurité, ce qui nous permet de proposer des mises à jour plus rapides et d'améliorer la qualité du code.

Cet article explique pourquoi Chrome a abandonné FreeType et présente quelques détails techniques intéressants sur les améliorations que ce changement a permis d'apporter.

Pourquoi remplacer FreeType ?

Le Web est unique en ce qu'il permet aux utilisateurs d'extraire des ressources non fiables à partir d'une grande variété de sources non fiables, en s'attendant à ce que tout fonctionne simplement 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 fournie sur le réseau), Chrome emploie plusieurs mesures de sécurité :

  • Le traitement des polices est sandboxé conformément à la règle des deux : elles ne sont pas fiables et le code consommateur n'est pas sûr.
  • Les polices sont transmises au sanitizer OpenType avant d'être traitées.
  • Toutes les bibliothèques impliquées dans la décompression et le traitement des polices sont testées par fuzzing.

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

La bibliothèque FreeType est utilisée par Chrome pour calculer les métriques et charger les contours suggérés des polices. Dans l'ensemble, l'utilisation de FreeType a été un énorme succès pour Google. Il effectue un travail complexe et le fait bien. Nous l'utilisons beaucoup et y contribuons. Toutefois, il est écrit dans un code non sécurisé et remonte à une époque où les entrées malveillantes étaient moins probables. Le simple fait de suivre le 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 n'observons pas tout ou ne trouvons des choses qu'une fois le code envoyé aux utilisateurs.

Ce type de problème n'est pas propre à FreeType. Nous avons constaté 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 disponibles, que nous examinons chaque modification du code et que nous exigeons des tests.

Pourquoi des problèmes continuent de se produire

Lors de l'évaluation de la sécurité de FreeType, nous avons observé trois principales classes de problèmes (liste non exhaustive) :

Utilisation d'une langue non sécurisée

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

Problèmes spécifiques à un projet

Motif/Problème Exemple
Les macros masquent l'absence de typographie de taille explicite
  • Les macros telles que FT_READ_* et FT_PEEK_* masquent les types entiers utilisés, en cachant 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 sont compatibles avec les deux types de problèmes.
  • Le fuzzing permet d'en trouver certains, mais pas nécessairement tous, #32421, #52404
Manque de tests
  • Créer des polices de test prend du temps et est difficile

Problèmes de dépendances

Le fuzzing a permis d'identifier à plusieurs reprises des problèmes dans les bibliothèques dont dépend FreeType, comme bzip2, libpng et zlib. Par exemple, comparez freetype_bdf_fuzzer : Use-of-uninitialized-value in inflate.

Le fuzzing ne suffit pas

Le fuzzing (tests automatisés avec une large gamme d'entrées, y compris des entrées aléatoires non valides) est destiné à trouver de nombreux types de problèmes qui se retrouvent dans la version stable de Chrome. Nous effectuons des tests fuzzing sur 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 police sont complexes, comparables aux fichiers vidéo, car ils contiennent plusieurs types d'informations différents. Les fichiers de police sont un format de conteneur pour plusieurs tables, où chaque table sert un objectif différent dans le traitement du texte et des polices ensemble pour produire un glyphe correctement positionné à l'écran. Dans un fichier de police, vous trouverez :

  • Métadonnées statiques telles que les noms de police et les paramètres des polices variables.
  • Mappages des caractères Unicode vers les glyphes.
  • Ensemble complexe de règles et de grammaire pour la mise en page des glyphes à l'écran.
  • Informations visuelles : formes des glyphes et informations sur les images décrivant l'apparence des glyphes placés à l'écran.
    • Les tables visuelles peuvent à leur tour inclure des programmes d'hinting TrueType, qui sont des mini-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 impératives de dessin et d'indication de courbes exécutées dans le moteur de rendu CFF.

Les fichiers de police sont complexes, car ils sont équivalents à un langage de programmation et à un 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 recherche de problèmes dans les fichiers de police.

Il est difficile d'obtenir une bonne couverture de code ou une bonne progression du fuzzer 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 mutateurs simples de type inversion/décalage/insertion/suppression de bits a du mal à atteindre toutes les combinaisons d'états.
  • Le fuzzing doit au moins produire des structures partiellement valides. La mutation aléatoire le fait rarement, ce qui rend difficile l'obtention d'une bonne couverture, en particulier pour les niveaux de code plus profonds.
  • Les efforts de fuzzing actuels dans ClusterFuzz et oss-fuzz n'utilisent pas encore la mutation tenant compte de la structure. L'utilisation de mutateurs sensibles à la grammaire ou à la structure peut aider à éviter la production de variantes qui sont rejetées tôt, au prix d'un temps de développement plus long et de chances de manquer des parties de l'espace de recherche.

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

  • Les schémas de mutation habituels des fuzzers ne produisent pas de données partiellement valides. De nombreuses itérations sont donc rejetées et la progression devient lente.
  • La mise en correspondance des glyphes, les tables de mise en page OpenType et le dessin des glyphes sont liés et dépendent les uns des autres, formant un espace combinatoire dont les coins sont difficiles à atteindre avec le fuzzing.
  • Par exemple, la faille tt_face_get_paint COLRv1 de gravité élevée a mis plus de 10 mois à être découverte.

Malgré nos efforts, des problèmes de sécurité liés aux polices ont été signalés à plusieurs reprises par les utilisateurs finaux. Le remplacement de FreeType par une alternative Rust permettra d'éviter 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 formes de lettres à 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é pour les 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 progressivement déployé le changement auprès des utilisateurs :

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

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

La sécurité avant tout

Notre objectif principal est de réduire (ou idéalement d'éliminer) les failles de sécurité causées par un accès à la mémoire hors limites. Rust fournit cette fonctionnalité prête à l'emploi tant que vous évitez les blocs de code unsafe.

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, ce qui est essentiel pour produire un analyseur de police rapide.

Pour éviter notre propre code non sécurisé, nous avons choisi d'externaliser cette responsabilité à bytemuck, une bibliothèque Rust conçue spécifiquement à cet effet, largement testée et utilisée dans l'écosystème. En concentrant la réinterprétation des données brutes dans bytemuck, nous nous assurons que cette fonctionnalité est centralisée et auditée, et nous évitons de répéter du code non sécurisé à cette fin. Le projet safe transmute 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 composé de composants indépendants où la plupart des structures de données sont conçues pour être immuables. Cela améliore la lisibilité, la maintenabilité et le multithreading. Cela rend également le code plus adapté aux tests unitaires. Nous avons profité de cette opportunité pour produire une suite d'environ 700 tests unitaires qui couvrent l'ensemble de notre pile, des routines d'analyse de bas niveau aux machines virtuelles d'indication de haut niveau.

La justesse implique également la fidélité, et FreeType est très apprécié pour sa génération de contours de haute qualité. Nous devons égaler cette qualité pour être un remplacement approprié. Pour ce faire, nous avons créé un outil sur mesure appelé fauntlet, qui compare la sortie de Skrifa et de FreeType pour des lots de fichiers de police dans un large éventail de configurations. Cela nous permet d'avoir l'assurance d'éviter les régressions de qualité.

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

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

En avant !

Nous sommes très satisfaits des résultats de nos efforts pour utiliser Rust pour le texte. Nous sommes très heureux de pouvoir fournir aux utilisateurs un code plus sûr et d'améliorer la productivité des développeurs. Nous prévoyons de continuer à chercher 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.