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 come sostituto di FreeType per rendere l'elaborazione dei caratteri in Chrome sicura per tutti i nostri utenti. Skrifa sfrutta la sicurezza della memoria di Rust e ci consente di eseguire iterazioni più rapide sui miglioramenti della tecnologia dei caratteri in Chrome. Il passaggio da FreeType a Skrifa ci consente di essere agili e audaci quando apportiamo modifiche al nostro codice dei caratteri. Ora dedichiamo molto meno tempo alla correzione dei bug di sicurezza, con conseguenti aggiornamenti più rapidi e una migliore qualità del codice.

Questo post spiega perché Chrome ha abbandonato FreeType e fornisce alcuni dettagli tecnici interessanti sui miglioramenti che questo cambiamento ha reso possibili.

Perché sostituire FreeType?

Il web è unico in quanto consente agli utenti di recuperare risorse non attendibili da un'ampia varietà di fonti non attendibili con l'aspettativa che tutto funzioni e che sia sicuro farlo. Questa ipotesi è generalmente corretta, ma mantenere questa promessa agli utenti ha un costo. Ad esempio, per utilizzare in sicurezza un font web (un font distribuito tramite la rete), Chrome utilizza diverse misure di sicurezza:

Chrome viene fornito con FreeType e lo utilizza come libreria principale per l'elaborazione dei caratteri su Android, ChromeOS e Linux. Ciò significa che un gran numero di utenti è esposto se esiste una vulnerabilità in FreeType.

La libreria FreeType viene utilizzata da Chrome per calcolare le metriche e caricare i contorni hinted dai caratteri. Nel complesso, l'utilizzo di FreeType è stato un grande successo per Google. Svolge un lavoro complesso e lo fa bene, ci affidiamo molto a questo strumento e contribuiamo al suo sviluppo. Tuttavia, è scritto in codice non sicuro e ha origine in un'epoca in cui gli input dannosi erano meno probabili. Il semplice monitoraggio del flusso di problemi rilevati dal fuzzing costa a Google almeno 0,25 ingegneri software a tempo pieno. Peggio ancora, non troviamo tutto o troviamo le cose solo dopo che il codice è stato distribuito agli utenti.

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

Perché i problemi continuano a presentarsi

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

Utilizzo di un linguaggio non sicuro

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

Problemi specifici del progetto

Pattern/Problema Esempio
Le macro nascondono la mancanza di digitazione esplicita delle dimensioni
  • Le macro come FT_READ_* e FT_PEEK_* oscurano i tipi di numeri interi utilizzati, nascondendo il fatto 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 difensivo.
  • COLRv1 e OT-SVG supportano entrambi i problemi prodotti
  • Il fuzzing rileva alcuni problemi, ma non necessariamente tutti, #32421, #52404
Mancanza di test
  • Creare caratteri di prova richiede tempo ed è difficile

Problemi di dipendenza

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

Il fuzzing non è sufficiente

Il fuzzing, ovvero il test automatizzato con un'ampia gamma di input, inclusi quelli casuali non validi, ha lo scopo di trovare molti dei tipi di problemi che vengono inseriti nella release stabile di Chrome. Eseguiamo il fuzzing di FreeType nell'ambito del progetto oss-fuzz di Google. Rileva i problemi, ma 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 diversi di informazioni. 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 insieme per produrre un glifo posizionato correttamente sullo schermo. In un file di caratteri troverai:

  • Metadati statici come nomi di caratteri e parametri per i caratteri variabili.
  • Mappature dai caratteri Unicode ai glifi.
  • Un insieme complesso di regole e grammatica per il layout dello schermo dei glifi.
  • Informazioni visive: forme dei glifi e informazioni sull'immagine che descrivono l'aspetto dei glifi posizionati sullo schermo.
    • Le tabelle visive possono a loro volta includere programmi di hinting 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 delle curve e l'hinting eseguite nel motore di rendering CFF.

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

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

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

  • Il fuzzing dei programmi di hinting TrueType, delle stringhe di caratteri CFF e del layout OpenType utilizzando mutatori semplici in stile bit-flipping/spostamento/inserimento/eliminazione fa fatica a raggiungere tutte le combinazioni di stati.
  • Il fuzzing deve produrre almeno strutture parzialmente valide. La mutazione casuale raramente lo fa, rendendo difficile ottenere una buona copertura, in particolare per i livelli di codice più profondi.
  • Gli attuali sforzi di fuzzing in ClusterFuzz e oss-fuzz non utilizzano ancora la mutazione consapevole della struttura. L'utilizzo di mutatori che tengono conto della grammatica o della struttura potrebbe aiutare a evitare la produzione di varianti che vengono rifiutate in anticipo, a costo di richiedere più tempo per lo sviluppo e introdurre possibilità che non coprono parti dello spazio di ricerca.

I dati in più tabelle devono essere sincronizzati per consentire l'avanzamento del fuzzing:

  • I normali pattern di mutazione dei fuzzing non producono dati parzialmente validi, quindi molte iterazioni vengono rifiutate e i progressi diventano lenti.
  • 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, la vulnerabilità tt_face_get_paint COLRv1 di gravità elevata ha richiesto più di 10 mesi per essere trovata.

Nonostante i nostri sforzi, i problemi di sicurezza dei caratteri hanno ripetutamente raggiunto gli utenti finali. La sostituzione di FreeType con un'alternativa Rust impedirà più classi di vulnerabilità.

Skrifa in Chrome

Skia è la libreria grafica utilizzata da Chrome. Skia si basa su FreeType per caricare i metadati e le forme delle lettere dai caratteri. Skrifa è una libreria Rust che fa parte della famiglia di librerie Fontations e 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 dei caratteri Skia basato su Skrifa e ha implementato gradualmente la modifica per gli utenti:

Per l'integrazione in Chrome, ci affidiamo all'integrazione fluida di Rust nel codice sorgente introdotto dal team di sicurezza di Chrome.

In futuro passeremo a Fontations anche per i caratteri del sistema operativo, a partire da Linux e ChromeOS, poi su Android.

La sicurezza prima di tutto

Il nostro obiettivo principale è ridurre (o idealmente eliminare) le vulnerabilità di sicurezza causate dall'accesso alla memoria al di fuori dei limiti. Rust lo fornisce pronto all'uso, a condizione che tu eviti blocchi di codice unsafe.

I nostri obiettivi di rendimento ci impongono di eseguire un'operazione attualmente non sicura: la reinterpretazione di byte arbitrari come struttura dati fortemente tipizzata. Ciò consente di leggere i dati da un file di caratteri senza eseguire copie inutili ed è essenziale per produrre un parser di caratteri veloce.

Per evitare il nostro codice non sicuro, abbiamo scelto di esternalizzare questa responsabilità a bytemuck, una libreria Rust progettata specificamente per questo scopo e ampiamente testata e utilizzata in tutto l'ecosistema. Concentrare la reinterpretazione dei dati non elaborati in bytemuck ci consente di avere questa funzionalità in un unico punto e controllata ed evitare di ripetere codice non sicuro per lo scopo. Il progetto safe transmute mira a incorporare questa funzionalità direttamente nel compilatore Rust e faremo il passaggio non appena sarà disponibile.

L'accuratezza è importante

Skrifa è composto 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, il codice è più adatto ai test delle unità. Abbiamo approfittato di questa opportunità e abbiamo prodotto una suite di circa 700 test unitari che coprono il nostro intero stack, dalle routine di analisi di basso livello alle macchine virtuali di suggerimento di alto livello.

La correttezza implica anche la fedeltà e FreeType è molto apprezzato per la generazione di contorni di alta qualità. Dobbiamo eguagliare questa qualità per essere un sostituto adeguato. A questo scopo, abbiamo creato uno strumento personalizzato chiamato fauntlet che confronta l'output di Skrifa e FreeType per batch di file di caratteri in un'ampia gamma di configurazioni. In questo modo, abbiamo la certezza di poter evitare regressioni della qualità.

Inoltre, prima dell'integrazione in Chromium, abbiamo eseguito una vasta serie di confronti tra pixel in Skia, confrontando il rendering di FreeType con quello di Skrifa e Skia per garantire che le differenze tra pixel siano assolutamente minime, in tutte le modalità di rendering richieste (in diverse modalità di antialiasing e hinting).

Il fuzz testing è uno strumento importante per determinare come un software reagirà a input malformati e dannosi. Da giugno 2024 eseguiamo test fuzzing continui sul nostro nuovo codice. Questo copre 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 è critico per la sicurezza. Possono causare risultati visivi indesiderati o persino arresti controllati, ma non portano a vulnerabilità sfruttabili.

Avanti!

Siamo molto soddisfatti dei risultati dei nostri sforzi per utilizzare Rust per il testo. Fornire agli utenti codice più sicuro e aumentare la produttività degli sviluppatori è un grande successo per noi. Abbiamo intenzione di continuare a cercare opportunità per utilizzare Rust nei nostri stack di testo. Se vuoi saperne di più, Oxidize descrive alcuni dei piani futuri di Google Fonts.