Speichersicherheit für Webschriften

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

Veröffentlicht: 19. März 2025

Skrifa wurde in Rust geschrieben und als Ersatz für FreeType entwickelt, um die Schriftartenverarbeitung in Chrome für alle Nutzer sicherer zu machen. Skrifa nutzt die Speichersicherheit von Rust und ermöglicht es uns, schneller an Verbesserungen der Schriftarttechnologie in Chrome zu arbeiten. Durch den Wechsel von FreeType zu Skrifa können wir agil und furchtlos Änderungen an unserem Schriftartcode vornehmen. Wir verbringen jetzt viel weniger Zeit mit der Behebung von Sicherheitsfehlern, was zu schnelleren Updates und einer besseren Codequalität führt.

In diesem Beitrag wird erläutert, warum Chrome nicht mehr FreeType verwendet, und es werden einige interessante technische Details zu den Verbesserungen beschrieben, die dadurch möglich wurden.

Warum wird FreeType ersetzt?

Das Web ist insofern einzigartig, als Nutzer nicht vertrauenswürdige Ressourcen aus einer Vielzahl nicht vertrauenswürdiger Quellen abrufen können, in der Erwartung, dass alles funktioniert und dass sie dabei sicher sind. Diese Annahme ist im Allgemeinen richtig, aber die Einhaltung dieses Versprechens an die Nutzer ist mit Kosten verbunden. Wenn Sie beispielsweise eine Web-Schriftart (eine über das Netzwerk bereitgestellte Schriftart) sicher verwenden möchten, setzt Chrome mehrere Sicherheitsmaßnahmen ein:

  • Die Schriftartverarbeitung erfolgt in einer Sandbox gemäß der Regel der zwei: Sie ist nicht vertrauenswürdig und der verwendende Code ist unsicher.
  • Schriftarten werden vor der Verarbeitung durch den OpenType Sanitizer geleitet.
  • Alle Bibliotheken, die zum Dekomprimieren und Verarbeiten von Schriftarten verwendet werden, wurden Fuzz-Tests unterzogen.

Chrome wird mit FreeType ausgeliefert und verwendet es als primäre Bibliothek für die Schriftartenverarbeitung unter Android, ChromeOS und Linux. Das bedeutet, dass viele Nutzer betroffen sind, wenn es eine Sicherheitslücke in FreeType gibt.

Die FreeType-Bibliothek wird von Chrome verwendet, um Messwerte zu berechnen und gerenderte Konturen aus Schriftarten zu laden. Insgesamt war die Verwendung von FreeType ein großer Erfolg für Google. Es erledigt eine komplexe Aufgabe gut, wir verlassen uns stark darauf und leisten einen Beitrag dazu. Sie ist jedoch in unsicherem Code geschrieben und stammt aus einer Zeit, in der böswillige Eingaben weniger wahrscheinlich waren. Allein die Behebung der durch Fuzzing gefundenen Probleme kostet Google mindestens 0,25 Vollzeit-Softwareentwickler. Schlimmer noch: Wir finden nicht alles oder erst, nachdem der Code an die Nutzer ausgeliefert wurde.

Dieses Muster von Problemen ist nicht auf FreeType beschränkt. Wir stellen fest, dass auch andere unsichere Bibliotheken Probleme aufweisen, selbst wenn wir die besten Softwareentwickler einsetzen, jede Änderung im Code überprüfen und Tests erforderlich sind.

Warum immer wieder Probleme auftreten

Bei der Bewertung der Sicherheit von FreeType haben wir drei Hauptklassen von Problemen beobachtet (nicht erschöpfend):

Verwendung einer unsicheren Sprache

Muster/Problem Beispiel
Manuelle Speicherverwaltung
Ungeprüfter Arrayzugriff CVE-2022-27404
Ganzzahlüberläufe Während der Ausführung eingebetteter virtueller Maschinen für TrueType-Hinting von CFF-Zeichnungen und -Hinweisen
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
Falsche Verwendung von Zuweisung mit und ohne Nullsetzung Diskussion unter https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, 8 Fuzzer-Probleme gefunden
Ungültige Besetzungen Weitere Informationen zur Makronutzung finden Sie in der folgenden Zeile.

Projektspezifische Probleme

Muster/Problem Beispiel
Makros verschleiern das Fehlen einer expliziten Größenangabe
  • Makros wie FT_READ_* und FT_PEEK_* verschleiern, welche Ganzzahltypen verwendet werden, und verbergen, dass C99-Typen mit expliziten Größen (int16_t usw.) nicht verwendet werden.
Durch neuen Code werden immer wieder Fehler eingeführt, auch wenn er defensiv geschrieben ist.
  • COLRv1 und OT-SVG unterstützen beide Arten von Problemen.
  • Beim Fuzzing werden einige, aber nicht unbedingt alle #32421, #52404 gefunden.
Fehlende Tests
  • Das Erstellen von Testschriften ist zeitaufwendig und schwierig

Probleme mit Abhängigkeiten

Durch Fuzzing wurden wiederholt Probleme in Bibliotheken erkannt, von denen FreeType abhängt, z. B. bzip2, libpng und zlib. Ein Beispiel ist freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate.

Fuzzing reicht nicht aus

Beim Fuzzing werden automatisierte Tests mit einer Vielzahl von Eingaben durchgeführt, darunter auch zufällige ungültige Eingaben. So sollen viele der Probleme gefunden werden, die in die stabile Version von Chrome gelangen. Wir führen Fuzzing-Tests für FreeType im Rahmen des oss-fuzz-Projekts von Google durch. Es werden zwar Probleme gefunden, aber Schriftarten sind aus folgenden Gründen relativ resistent gegen Fuzzing:

Schriftartdateien sind komplex und mit Videodateien vergleichbar, da sie mehrere verschiedene Arten von Informationen enthalten. Schriftartdateien sind ein Containerformat für mehrere Tabellen, wobei jede Tabelle einen anderen Zweck bei der Verarbeitung von Text und Schriftarten hat, um ein korrekt positioniertes Glyphen auf dem Bildschirm zu erzeugen. Eine Schriftartdatei enthält:

  • Statische Metadaten wie Schriftartnamen und Parameter für variable Schriftarten.
  • Zuordnungen von Unicode-Zeichen zu Glyphen.
  • Ein komplexer Regelsatz und eine komplexe Grammatik für das Bildschirm-Layout von Glyphen.
  • Visuelle Informationen: Glyphenformen und Bildinformationen, die beschreiben, wie die auf dem Bildschirm platzierten Glyphen aussehen.
    • Die visuellen Tabellen können wiederum TrueType-Hinting-Programme enthalten, die als Mini-Programme ausgeführt werden, um die Glyphenform zu ändern.
    • Zeichenfolgen in den CFF- oder CFF2-Tabellen, die imperative Anweisungen zum Zeichnen von Kurven und zum Hinting enthalten, die in der CFF-Rendering-Engine ausgeführt werden.

Schriftartdateien sind komplex und haben eine eigene Programmiersprache und State Machine Processing, für die spezielle virtuelle Maschinen erforderlich sind.

Aufgrund der Komplexität des Formats hat Fuzzing Schwächen beim Auffinden von Problemen in Schriftartdateien.

Eine gute Codeabdeckung oder ein guter Fuzzer-Fortschritt ist aus folgenden Gründen schwierig zu erreichen:

  • Beim Fuzzing von TrueType-Hinting-Programmen, CFF-Char-Strings und OpenType-Layouts mit einfachen Mutatoren vom Typ „Bit-Flipping“/„Shift“/„Insertion“/„Deletion“ (Bit-Umkehrung/Verschiebung/Einfügung/Löschung) ist es schwierig, alle Kombinationen von Status zu erreichen.
  • Beim Fuzzing müssen zumindest teilweise gültige Strukturen erzeugt werden. Bei zufälligen Mutationen ist das selten der Fall, sodass eine gute Abdeckung schwer zu erreichen ist, insbesondere für tiefere Codeebenen.
  • Beim aktuellen Fuzzing in ClusterFuzz und oss-fuzz wird noch keine strukturbezogene Mutation verwendet. Durch die Verwendung von Mutatoren, die auf Grammatik oder Struktur achten, lassen sich möglicherweise Varianten vermeiden, die frühzeitig abgelehnt werden. Das kann jedoch mehr Zeit in Anspruch nehmen und dazu führen, dass Teile des Suchraums nicht berücksichtigt werden.

Die Daten in mehreren Tabellen müssen synchronisiert werden, damit das Fuzzing voranschreiten kann:

  • Die üblichen Mutationsmuster von Fuzzern erzeugen keine teilweise gültigen Daten. Daher werden viele Iterationen abgelehnt und der Fortschritt verlangsamt sich.
  • Die Glyphenzuordnung, die OpenType-Layouttabellen und das Zeichnen von Glyphen sind miteinander verbunden und voneinander abhängig. Sie bilden einen kombinatorischen Raum, dessen Ecken mit Fuzzing schwer zu erreichen sind.
  • So dauerte es beispielsweise mehr als 10 Monate, bis die Sicherheitslücke tt_face_get_paintCOLRv1 mit hohem Schweregrad gefunden wurde.

Trotz unserer Bemühungen sind Schriftart-Sicherheitsprobleme wiederholt bei Endnutzern aufgetreten. Wenn FreeType durch eine Rust-Alternative ersetzt wird, werden mehrere ganze Klassen von Sicherheitslücken verhindert.

Skrifa in Chrome

Skia ist die Grafikbibliothek, die von Chrome verwendet wird. Skia verwendet FreeType, um Metadaten und Glyphen aus Schriftarten zu laden. Skrifa ist eine Rust-Bibliothek, die zur Fontations-Bibliotheksfamilie gehört und einen sicheren Ersatz für die von Skia verwendeten Teile von FreeType bietet.

Um FreeType auf Skia umzustellen, hat das Chrome-Team ein neues Skia-Schriftart-Backend auf Grundlage von Skrifa entwickelt und die Änderung nach und nach für Nutzer eingeführt:

Für die Integration in Chrome setzen wir auf die reibungslose Integration von Rust in die Codebasis, die vom Chrome-Sicherheitsteam eingeführt wurde.

Künftig werden wir auch für Betriebssystemschriftarten auf Fontations umstellen, beginnend mit Linux und ChromeOS, dann auf Android.

Sicherheit geht vor

Unser primäres Ziel ist es, Sicherheitslücken zu reduzieren (oder idealerweise zu beseitigen!), die durch den Zugriff auf Speicher außerhalb des zulässigen Bereichs verursacht werden. Rust bietet dies standardmäßig, solange Sie unsafe-Codeblöcke vermeiden.

Unsere Leistungsziele erfordern einen Vorgang, der derzeit unsicher ist: die Neuinterpretation beliebiger Bytes als stark typisierte Datenstruktur. So können wir die Daten aus einer Schriftartdatei lesen, ohne unnötige Kopien zu erstellen. Das ist wichtig, um einen schnellen Schriftartparser zu entwickeln.

Um eigenen unsicheren Code zu vermeiden, haben wir diese Verantwortung an bytemuck ausgelagert. Das ist eine Rust-Bibliothek, die speziell für diesen Zweck entwickelt wurde und im gesamten Ökosystem weit verbreitet ist. Durch die Konzentration der Neuinterpretation von Rohdaten in bytemuck wird sichergestellt, dass diese Funktion an einem Ort verfügbar ist und geprüft wird. Außerdem wird so vermieden, dass unsicherer Code für diesen Zweck wiederholt wird. Das safe transmute-Projekt zielt darauf ab, diese Funktion direkt in den Rust-Compiler zu integrieren. Wir werden die Umstellung vornehmen, sobald sie verfügbar ist.

Richtigkeit ist wichtig

Skrifa besteht aus unabhängigen Komponenten, wobei die meisten Datenstrukturen unveränderlich sind. Dies verbessert die Lesbarkeit, Wartbarkeit und Multithreading-Fähigkeit. Außerdem lässt sich der Code so besser unit-testen. Wir haben diese Gelegenheit genutzt und eine Reihe von etwa 700 Einheitentests erstellt, die unseren gesamten Stack abdecken, von Parsing-Routinen auf niedriger Ebene bis hin zu virtuellen Maschinen für das Hinting auf hoher Ebene.

Korrektheit bedeutet auch Genauigkeit und FreeType wird für die Erstellung hochwertiger Konturen sehr geschätzt. Wir müssen diese Qualität erreichen, um einen geeigneten Ersatz zu bieten. Dazu haben wir ein spezielles Tool namens fauntlet entwickelt, das die Ausgabe von Skrifa und FreeType für Batches von Schriftartdateien in einer Vielzahl von Konfigurationen vergleicht. So können wir sicher sein, dass wir keine Qualitätseinbußen haben.

Außerdem haben wir vor der Integration in Chromium eine Vielzahl von Pixelvergleichen in Skia durchgeführt, bei denen wir das FreeType-Rendering mit dem Skrifa- und Skia-Rendering verglichen haben, um sicherzustellen, dass die Pixeldifferenzen in allen erforderlichen Rendering-Modi (über verschiedene Antialiasing- und Hinting-Modi hinweg) absolut minimal sind.

Fuzzing ist ein wichtiges Tool, um zu ermitteln, wie Software auf fehlerhafte und schädliche Eingaben reagiert. Wir führen seit Juni 2024 kontinuierlich Fuzzing-Tests für unseren neuen Code durch. Das umfasst die Rust-Bibliotheken selbst und den Integrationscode. Der Fuzzer hat (Stand heute) 39 Bugs gefunden. Es ist jedoch wichtig zu wissen, dass keiner dieser Bugs sicherheitskritisch war. Sie können zu unerwünschten visuellen Ergebnissen oder sogar zu kontrollierten Abstürzen führen, aber nicht zu ausnutzbaren Sicherheitslücken.

Nur Mut!

Wir sind sehr zufrieden mit den Ergebnissen unserer Bemühungen, Rust für Text zu verwenden. Sichereren Code für Nutzer bereitzustellen und gleichzeitig die Produktivität von Entwicklern zu steigern, ist ein großer Vorteil für uns. Wir werden weiterhin nach Möglichkeiten suchen, Rust in unseren Text-Stacks einzusetzen. Weitere Informationen finden Sie unter Oxidize, wo einige der zukünftigen Pläne von Google Fonts dargelegt werden.