Sicurezza della memoria per i caratteri web

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

Data di pubblicazione: 19 marzo 2025

Skrifa è scritto in Rust ed è stato creato per sostituire FreeType al fine di rendere sicura l'elaborazione dei caratteri in Chrome per tutti i nostri utenti. Skifra sfrutta la sicurezza della memoria di Rust e ci consente di eseguire più rapidamente l'iterazione sui miglioramenti della tecnologia dei caratteri in Chrome. Il passaggio da FreeType a Skrifa ci consente di essere agili e sicuri quando apportiamo modifiche al codice dei caratteri. Ora impieghiamo molto meno tempo per correggere i bug di sicurezza, con conseguenti aggiornamenti più rapidi e una qualità del codice migliore.

Questo post spiega perché Chrome ha abbandonato FreeType e alcuni dettagli tecnici interessanti sui miglioramenti resi possibili da questo passaggio.

Perché sostituire FreeType?

Il web è unico in quanto consente agli utenti di recuperare risorse non attendibili da una vasta gamma di fonti non attendibili con l'aspettativa che tutto funzioni e che sia sicuro farlo. Questa assunzione è generalmente corretta, ma mantenere questa promessa agli utenti ha un costo. Ad esempio, per utilizzare in sicurezza un carattere web (un carattere caricato sulla rete), Chrome utilizza diverse misure di mitigazione della sicurezza:

Chrome viene fornito con FreeType e lo utilizza come libreria di elaborazione dei caratteri principale su Android, ChromeOS e Linux. Ciò significa che molti utenti sono esposti se esiste una vulnerabilità in FreeType.

La libreria FreeType viene utilizzata da Chrome per calcolare le metriche e caricare gli schemi suggeriti dai caratteri. Nel complesso, l'utilizzo di FreeType è stato un grande vantaggio per Google. Svolge un compito complesso e lo fa bene, ci basiamo molto su di essa e diamo il nostro contributo. Tuttavia, è scritto in codice non sicuro e ha avuto origine in un periodo in cui gli input dannosi erano meno probabili. Il solo tenere il passo con lo stream di problemi rilevati tramite il fuzzing costa a Google almeno 0,25 ingegneri software a tempo pieno. Inoltre, notiamo che non troviamo tutto o che troviamo elementi solo dopo che il codice è stato inviato agli utenti.

Questo modello di problemi non è esclusivo di FreeType, abbiamo notato che anche altre librerie non sicure presentano problemi anche quando utilizziamo i migliori ingegneri del software che possiamo trovare, sottoponiamo a revisione del codice ogni modifica e richiediamo test.

Perché i problemi continuano a verificarsi

Durante la valutazione della sicurezza di FreeType, abbiamo osservato tre classi principali di problemi (elenco non esaustivo):

Utilizzo di un linguaggio non sicuro

Pattern/Problema Esempio
Gestione manuale della memoria
Accesso all'array non controllato CVE-2022-27404
Sforzi di numeri interi Durante l'esecuzione di macchine virtuali incorporate per l'ottimizzazione TrueType del disegno e dell'ottimizzazione CFF
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
Utilizzo errato dell'allocazione con azzeramento rispetto a quella senza azzeramento Discussione in https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, 8 problemi di fuzzer trovati in seguito
Trasmissione non valida Consulta la riga seguente sull'utilizzo delle macro

Problemi specifici del progetto

Pattern/Problema Esempio
Le macro nascondono la mancanza di specificazione esplicita delle dimensioni
  • Macro come FT_READ_* e FT_PEEK_* nascondono i tipi di interi utilizzati, nascondendo che i tipi C99 con dimensioni esplicite (int16_t e così via) non vengono utilizzati
Il nuovo codice aggiunge costantemente bug, anche se scritto in modo sicuro.
  • I problemi sono stati generati sia con COLRv1 che con OT-SVG
  • Il fuzzing trova alcuni, ma non necessariamente tutti, #32421, #52404
Mancanza di test
  • La creazione di caratteri di prova è un processo lungo e complesso

Problemi di dipendenza

Il fuzzing ha identificato ripetutamente problemi nelle librerie su cui si basa FreeType, come bzip2, libpng e zlib. Ad esempio, confronta freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate.

Il fuzzing non è sufficiente

Il fuzzing, ovvero i test automatici con una vasta gamma di input, inclusi quelli non validi randomizzati, ha lo scopo di trovare molti dei tipi di problemi che si verificano nella release stabile di Chrome. Eseguiamo fuzz su FreeType nell'ambito del progetto oss-fuzz di Google. Sebbene riesca a trovare problemi, i caratteri si sono dimostrati piuttosto resistenti al fuzzing per i seguenti motivi.

I file dei caratteri sono complessi, paragonabili ai file video in quanto contengono più tipi di informazioni diverse. I file dei caratteri sono un formato contenitore per più tabelle, in cui ogni tabella ha uno scopo diverso nell'elaborazione di testo e caratteri per produrre un glifo posizionato correttamente sullo schermo. In un file del carattere troverai:

  • Metadati statici come nomi e parametri dei caratteri per i caratteri variabili.
  • Mappature dai caratteri Unicode ai glifi.
  • Regole e grammatica complesse per il layout dello schermo dei glifi.
  • Informazioni visive: forme dei glifari e informazioni sulle immagini che descrivono l'aspetto dei glifari posizionati sullo schermo.
    • Le tabelle visive possono a loro volta includere programmi di indicazione TrueType, che sono mini programmi eseguiti per modificare la forma dei glifi.
    • Stringhe di caratteri nelle tabelle CFF o CFF2 che sono istruzioni imperative per il disegno e gli indicatori delle curve eseguite nel motore di rendering CFF.

I file dei caratteri sono complessi in quanto hanno un proprio linguaggio di programmazione e un'elaborazione della macchina a stati, che richiedono macchine virtuali specifiche per essere eseguiti.

A causa della complessità del formato, il fuzzing presenta delle carenze nel rilevare i problemi nei file dei caratteri.

È difficile ottenere una buona copertura del codice o un buon avanzamento del fuzzer per i seguenti motivi:

  • I programmi di fuzzing degli indicatori TrueType, le stringhe di caratteri CFF e il layout OpenType che utilizzano semplici mutatori di tipo bit-flipping/shift/insertion/deletion hanno difficoltà a raggiungere tutte le combinazioni di stati.
  • Il fuzzing deve produrre almeno strutture parzialmente valide. La mutazione random raramente lo fa, rendendo difficile ottenere una buona copertura, in particolare per i livelli di codice più profondi.
  • I tentativi di fuzzing attuali in ClusterFuzz e oss-fuzz non utilizzano ancora la mutazione consapevole della struttura. L'utilizzo di mutatori attenti alla grammatica o alla struttura potrebbe contribuire a evitare la produzione di varianti che vengono rifiutate in anticipo, a costo di un tempo di sviluppo più lungo e dell'introduzione di probabilità che manchino parti dello spazio di ricerca.

Affinché il fuzzing possa progredire, i dati in più tabelle devono essere sincronizzati:

  • I pattern di mutazione usuali dei fuzzer non producono dati parzialmente validi, pertanto molte iterazioni vengono rifiutate e l'avanzamento diventa lento.
  • La mappatura dei glifi, le tabelle di layout OpenType e il disegno dei glifi sono collegati e dipendono l'uno dall'altro, formando uno spazio combinatorio i cui angoli sono difficili da raggiungere con il fuzzing.
  • Ad esempio, sono stati necessari più di 10 mesi per trovare la vulnerabilità tt_face_get_paint COLRv1 di gravità elevata.

Nonostante il nostro impegno, i problemi di sicurezza dei caratteri hanno raggiunto ripetutamente gli utenti finali. La sostituzione di FreeType con un'alternativa Rust eviterà più intere classi di vulnerabilità.

Skrifa in Chrome

Skia è la libreria grafica utilizzata da Chrome. Skia si basa su FreeType per caricare i metadati e le lettere dai caratteri. Skrifa è una libreria Rust, che fa parte della famiglia di librerie Fontations, che fornisce una sostituzione sicura per le parti di FreeType utilizzate da Skia.

Per eseguire la transizione da FreeType a Skia, il team di Chrome ha sviluppato un nuovo backend per i caratteri Skia basato su Skrifa e ha implementato gradualmente la modifica per gli utenti:

Per l'integrazione in Chrome, ci basiamo sull'integrazione fluida di Rust nel codebase introdotta dal team di sicurezza di Chrome.

In futuro, passeremo a Fontations anche per i caratteri dei sistemi operativi, iniziando da Linux e ChromeOS, poi su Android.

Sicurezza, prima di tutto

Il nostro obiettivo principale è ridurre (o idealmente eliminare) le vulnerabilità di sicurezza causate da accessi non consentiti alla memoria. Rust lo fornisce out-of-the-box, a condizione che tu eviti blocchi di codice non sicuri.

I nostri obiettivi di rendimento ci richiedono di eseguire un'operazione attualmente non sicura: la reinterpretazione di byte arbitrari come struttura di dati fortemente tipizzata. In questo modo possiamo leggere i dati da un file del carattere senza eseguire copie non necessarie ed è essenziale per produrre un parser dei caratteri veloce.

Per evitare di scrivere codice non sicuro, abbiamo scelto di esternalizzare questa responsabilità a bytemuck, una libreria Rust progettata appositamente per questo scopo, ampiamente testata e utilizzata nell'ecosistema. La concentrazione della reinterpretazione dei dati non elaborati in bytemuck ci consente di avere questa funzionalità in un unico posto e di sottoporla a controllo, nonché di evitare di ripetere codice non sicuro per lo scopo. Il progetto transmutazione sicura mira a incorporare questa funzionalità direttamente nel compilatore Rust e eseguiremo il passaggio non appena sarà disponibile.

La correttezza è importante

Skrifa è costituito da componenti indipendenti in cui la maggior parte delle strutture di dati è progettata per essere immutabile. Ciò migliora la leggibilità, la manutenibilità e il multithreading. Inoltre, rende il codice più adatto ai test di unità. Abbiamo sfruttato questa opportunità e abbiamo prodotto una suite di circa 700 test di unità che coprono l'intero stack, dalle routine di analisi a basso livello alle macchine virtuali con suggerimenti di alto livello.

La correttezza implica anche fedeltà e FreeType è molto apprezzato per la generazione di contorni di alta qualità. Dobbiamo eguagliare questa qualità per essere una sostituzione appropriata. A tal fine, abbiamo creato uno strumento personalizzato chiamato fauntlet che confronta l'output di Skrifa e FreeType per batch di file di caratteri in una ampia gamma di configurazioni. Questo ci offre una certa certezza di poter evitare regressioni della qualità.

Inoltre, prima dell'integrazione in Chromium, abbiamo eseguito un ampio insieme di confronti dei pixel in Skia, confrontando il rendering di FreeType con quello di Skrifa e Skia per garantire che le differenze di pixel siano assolutamente minime in tutte le modalità di rendering richieste (in diverse modalità di antialiasing e di indicazione).

I test di fuzz sono uno strumento importante per determinare in che modo un software reagirà a input non validi e dannosi. Stiamo sottoponendo a fuzzing continuo il nostro nuovo codice da giugno 2024. Sono coperte le librerie Rust stesse e il codice di integrazione. Sebbene il fuzzer abbia trovato (al momento della stesura di questo articolo) 39 bug, è importante notare che nessuno di questi è stato considerato di fondamentale importanza per la sicurezza. Possono causare risultati visivi indesiderati o persino arresti anomali controllati, ma non generano vulnerabilità sfruttabili.

Avanti!

Siamo molto soddisfatti dei risultati dei nostri sforzi per utilizzare Rust per il testo. Offrire agli utenti un codice più sicuro e aumentare la produttività degli sviluppatori è un grande traguardo per noi. Abbiamo intenzione di continuare a cercare opportunità per utilizzare Rust nelle nostre piattaforme di text. Se vuoi saperne di più, Oxidize illustra alcuni dei piani futuri di Google Fonts.