Geheugenveiligheid voor weblettertypen

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

Gepubliceerd: 19 maart 2025

Skrifa is geschreven in Rust en ontwikkeld als vervanging voor FreeType om de verwerking van lettertypen in Chrome veilig te maken voor al onze gebruikers. Skrifa maakt gebruik van de geheugenveiligheid van Rust, waardoor we sneller kunnen werken aan verbeteringen in de lettertypetechnologie van Chrome. De overstap van FreeType naar Skrifa stelt ons in staat om zowel flexibel als onbevreesd wijzigingen aan te brengen in onze lettertypecode. We besteden nu veel minder tijd aan het oplossen van beveiligingsproblemen, wat resulteert in snellere updates en een betere codekwaliteit.

In dit bericht wordt uitgelegd waarom Chrome is overgestapt van FreeType en worden enkele interessante technische details gedeeld over de verbeteringen die deze overstap mogelijk heeft gemaakt.

Waarom FreeType vervangen?

Het web is uniek omdat gebruikers onbetrouwbare bronnen kunnen ophalen met de verwachting dat alles gewoon werkt en dat ze daarbij veilig zijn. Deze aanname klopt over het algemeen, maar het nakomen van die belofte aan gebruikers brengt kosten met zich mee. Om bijvoorbeeld een webfont (een lettertype dat via het netwerk wordt verzonden) veilig te kunnen gebruiken, past Chrome verschillende beveiligingsmaatregelen toe:

Chrome wordt geleverd met FreeType en gebruikt het als de primaire lettertypeverwerkingsbibliotheek op Android, ChromeOS en Linux. Dat betekent dat veel gebruikers kwetsbaar zijn als er een beveiligingslek in FreeType zit.

De FreeType-bibliotheek wordt door Chrome gebruikt om statistieken te berekenen en gehintte contouren van lettertypen te laden. Over het algemeen is het gebruik van FreeType een enorme winst voor Google geweest. Het voert een complexe taak goed uit, we maken er veelvuldig gebruik van en dragen er ook aan bij. De code is echter onveilig en stamt uit een tijd waarin kwaadaardige invoer minder waarschijnlijk was. Alleen al het bijhouden van de stroom problemen die door fuzzing worden gevonden, kost Google minstens 0,25 voltijdse software-engineers. Erger nog, we vinden aantoonbaar niet alles of ontdekken problemen pas nadat de code al bij de gebruikers is uitgebracht.

Dit probleempatroon is niet uniek voor FreeType; we zien dat andere onveilige bibliotheken ook problemen vertonen, zelfs wanneer we de beste softwareontwikkelaars inzetten, elke wijziging codebeoordelen en tests vereisen.

Waarom blijven problemen zich steeds weer voordoen?

Bij onze evaluatie van de beveiliging van FreeType hebben we drie hoofdcategorieën van problemen geconstateerd (niet-uitputtend):

Gebruik van onveilige taal

Patroon/Probleem Voorbeeld
Handmatig geheugenbeheer
Ongecontroleerde toegang tot arrays CVE-2022-27404
Integer-overloop Tijdens de uitvoering van ingebedde virtuele machines voor TrueType-hinting van CFF-tekeningen en hinting
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
Onjuist gebruik van nulstelling versus niet-nulstelling Discussie in https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94 , daarna 8 fuzzer-problemen gevonden
Ongeldige casts Zie de volgende regel over het gebruik van macro's.

Projectspecifieke problemen

Patroon/Probleem Voorbeeld
Macro's verhullen het gebrek aan expliciete typering van de grootte.
  • Macro's zoals FT_READ_* en FT_PEEK_* verbergen welke integer-typen worden gebruikt, waardoor niet duidelijk wordt dat C99-typen met expliciete groottes (int16_t, enz.) niet worden gebruikt.
Nieuwe code introduceert steevast bugs, zelfs wanneer deze defensief is geschreven.
  • Zowel COLRv1 als OT-SVG-ondersteuning leverden problemen op.
  • Fuzzing vindt sommige, maar niet noodzakelijkerwijs alle, #32421 , #52404
Gebrek aan tests
  • Het maken van testlettertypen is tijdrovend en moeilijk.

Afhankelijkheidsproblemen

Fuzzing heeft herhaaldelijk problemen aan het licht gebracht in bibliotheken waarvan FreeType afhankelijk is, zoals bzip2, libpng en zlib. Vergelijk bijvoorbeeld freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate .

Vervagen is niet genoeg

Fuzzing – geautomatiseerd testen met een breed scala aan invoer, inclusief willekeurig ongeldige invoer – is bedoeld om veel van de problemen te vinden die in de stabiele versie van Chrome terechtkomen. We fuzzen FreeType als onderdeel van Google's oss-fuzz -project. Het vindt inderdaad problemen, maar lettertypen blijken enigszins resistent te zijn tegen fuzzing, om de volgende redenen.

Lettertypebestanden zijn complex, vergelijkbaar met videobestanden, omdat ze verschillende soorten informatie bevatten. Lettertypebestanden zijn een containerformaat voor meerdere tabellen, waarbij elke tabel een ander doel dient bij het verwerken van tekst en lettertypen om een ​​correct gepositioneerd teken op het scherm te produceren. In een lettertypebestand vindt u het volgende:

  • Statische metadata zoals lettertypenamen en parameters voor variabele lettertypen.
  • Afbeeldingen van Unicode-tekens naar glyphs.
  • Een complexe set regels en grammatica voor de schermindeling van glyphs.
  • Visuele informatie: Glyfvormen en beeldinformatie die beschrijven hoe de op het scherm geplaatste glyphs eruitzien.
    • De visuele tabellen kunnen op hun beurt TrueType-hintingprogramma's bevatten, dit zijn miniprogramma's die worden uitgevoerd om de vorm van het teken te wijzigen.
    • Tekenreeksen in de CFF- of CFF2-tabellen die imperatieve instructies voor het tekenen en suggereren van curven bevatten en die worden uitgevoerd in de CFF-renderingengine.

De complexiteit van lettertypebestanden is vergelijkbaar met die van een eigen programmeertaal en toestandsmachine, waardoor specifieke virtuele machines nodig zijn om ze uit te voeren.

Vanwege de complexiteit van het formaat heeft fuzzing beperkingen bij het opsporen van problemen in lettertypebestanden.

Goede code coverage of fuzzer-vooruitgang is om de volgende redenen moeilijk te bereiken:

  • Het fuzzen van TrueType-hintingprogramma's, CFF-tekenreeksen en OpenType-layout met behulp van eenvoudige mutators in de stijl van bit-flipping/shift/insertion/delete, levert problemen op bij het bereiken van alle mogelijke toestandscombinaties.
  • Fuzzing moet op zijn minst gedeeltelijk geldige structuren opleveren. Willekeurige mutatie doet dat zelden, waardoor een goede dekking moeilijk te bereiken is, met name voor diepere codelagen.
  • De huidige fuzzing-methoden in ClusterFuzz en oss-fuzz maken nog geen gebruik van structuurbewuste mutatie. Het gebruik van grammatica- of structuurbewuste mutators zou kunnen helpen voorkomen dat varianten in een vroeg stadium worden afgewezen, ten koste van een langere ontwikkeltijd en het introduceren van kansen die delen van de zoekruimte missen.

Gegevens in meerdere tabellen moeten gesynchroniseerd zijn om fuzzing te laten slagen:

  • De gebruikelijke mutatiepatronen van fuzzers produceren geen gedeeltelijk geldige data, waardoor veel iteraties worden afgewezen en de voortgang traag verloopt.
  • De glyph-mapping, de OpenType-layouttabellen en het tekenen van glyphs zijn met elkaar verbonden en van elkaar afhankelijk, waardoor een combinatorische ruimte ontstaat waarvan de hoeken moeilijk te bereiken zijn met behulp van fuzzing.
  • Zo duurde het bijvoorbeeld meer dan 10 maanden om de ernstige COLRv1-kwetsbaarheid tt_face_get_paint te vinden.

Ondanks onze beste inspanningen hebben beveiligingsproblemen met lettertypen herhaaldelijk eindgebruikers bereikt. Door FreeType te vervangen door een alternatief in Rust worden meerdere complete categorieën van kwetsbaarheden voorkomen.

Skrifa in Chrome

Skia is de grafische bibliotheek die door Chrome wordt gebruikt. Skia gebruikt FreeType om metadata en lettervormen uit lettertypen te laden. Skrifa is een Rust-bibliotheek, onderdeel van de Fontations -bibliotheekfamilie, die een veilig alternatief biedt voor de onderdelen van FreeType die door Skia worden gebruikt.

Om FreeType naar Skia over te zetten, ontwikkelde het Chrome-team een ​​nieuwe Skia-lettertypebackend op basis van Skrifa en rolde de wijziging geleidelijk uit naar gebruikers:

Voor de integratie in Chrome vertrouwen we op de soepele integratie van Rust in de codebase die door het Chrome-beveiligingsteam is geïntroduceerd.

In de toekomst zullen we ook voor besturingssysteemlettertypen overstappen op Fontations, te beginnen met Linux en ChromeOS, en vervolgens op Android.

Veiligheid staat voorop.

Ons voornaamste doel is het verminderen (of idealiter elimineren!) van beveiligingslekken die ontstaan ​​door ongeoorloofde toegang tot geheugen. Rust biedt dit standaard, zolang je maar geen onveilige codeblokken gebruikt.

Onze prestatiedoelen vereisen dat we een bewerking uitvoeren die momenteel onveilig is: het herinterpreteren van willekeurige bytes als een sterk getypeerde datastructuur. Dit stelt ons in staat om de gegevens uit een lettertypebestand te lezen zonder onnodige kopieën te maken en is essentieel voor het ontwikkelen van een snelle lettertypeparser.

Om te voorkomen dat we zelf onveilige code schrijven, hebben we ervoor gekozen deze verantwoordelijkheid uit te besteden aan bytemuck , een Rust-bibliotheek die specifiek voor dit doel is ontworpen en die uitgebreid is getest en gebruikt binnen het ecosysteem. Door de herinterpretatie van ruwe data in bytemuck te concentreren, zorgen we ervoor dat deze functionaliteit op één plek beschikbaar is en gecontroleerd wordt, en voorkomen we dat we onveilige code herhalen. Het Safe Transmute-project is erop gericht deze functionaliteit direct in de Rust-compiler te integreren en we zullen overstappen zodra deze beschikbaar is.

Correctheid is belangrijk

Skrifa is opgebouwd uit onafhankelijke componenten, waarbij de meeste datastructuren zijn ontworpen om onveranderlijk te zijn. Dit verbetert de leesbaarheid, het onderhoud en de multithreading. Het maakt de code ook beter geschikt voor unit-testen. We hebben van deze mogelijkheid gebruikgemaakt en een reeks van ongeveer 700 unit-tests ontwikkeld die onze volledige stack bestrijken, van routines voor het parsen op laag niveau tot virtuele machines voor hinting op hoog niveau.

Correctheid impliceert ook getrouwheid, en FreeType staat hoog aangeschreven vanwege de hoogwaardige contouren die het genereert. We moeten deze kwaliteit evenaren om een ​​geschikte vervanger te zijn. Daarom hebben we een speciaal ontwikkeld hulpmiddel genaamd fauntlet , dat de output van Skrifa en FreeType vergelijkt voor batches lettertypebestanden met een breed scala aan configuraties. Dit geeft ons de zekerheid dat we kwaliteitsvermindering kunnen voorkomen.

Daarnaast hebben we, vóór de integratie in Chromium, een uitgebreide reeks pixelvergelijkingen uitgevoerd in Skia, waarbij we de FreeType-rendering vergeleken met de Skrifa-rendering en de Skia-rendering om ervoor te zorgen dat de pixelverschillen absoluut minimaal zijn in alle vereiste renderingmodi (over verschillende antialiasing- en hinting-modi).

Fuzz-testen zijn een belangrijk hulpmiddel om te bepalen hoe software reageert op onjuiste en kwaadaardige invoer. We testen onze nieuwe code al sinds juni 2024 continu met fuzzing. Dit omvat zowel de Rust-bibliotheken zelf als de integratiecode. Hoewel de fuzzer (op het moment van schrijven) 39 bugs heeft gevonden, is het belangrijk om te benadrukken dat geen van deze bugs een beveiligingsrisico vormt . Ze kunnen ongewenste visuele effecten of zelfs gecontroleerde crashes veroorzaken, maar leiden niet tot exploiteerbare kwetsbaarheden.

Voorwaarts!

We zijn zeer tevreden met de resultaten van onze inspanningen om Rust voor tekst te gebruiken. Het leveren van veiligere code aan gebruikers en het verhogen van de productiviteit van ontwikkelaars is een enorme winst voor ons. We zijn van plan om te blijven zoeken naar mogelijkheden om Rust in onze teksttechnologieën te gebruiken. Als u meer wilt weten, beschrijft Oxidize enkele toekomstige plannen van Google Fonts.