Data publikacji: 19 marca 2025 r.
Skrifa została napisana w Rust i powstała jako zamiennik FreeType, aby zapewnić bezpieczeństwo przetwarzania czcionek w Chrome wszystkim użytkownikom. Skifra korzysta z bezpieczeństwa pamięci Rust i umożliwia nam szybsze ulepszanie technologii czcionek w Chrome. Przejście z FreeType na Skrifa pozwala nam być elastycznym i bez obaw wprowadzać zmiany w kodzie czcionek. Teraz poświęcamy znacznie mniej czasu na naprawianie błędów związanych z bezpieczeństwem, co przekłada się na szybsze aktualizacje i lepszą jakość kodu.
W tym poście wyjaśniamy, dlaczego Chrome przestał korzystać z FreeType, oraz podajemy kilka interesujących szczegółów technicznych dotyczących ulepszeń, które ta zmiana umożliwiła.
Dlaczego warto zastąpić FreeType?
Internet jest wyjątkowy, ponieważ pozwala użytkownikom pobierać niesprawdzone zasoby z różnych niesprawdzonych źródeł z oczekiwaniem, że wszystko będzie działać i że będzie to bezpieczne. To założenie jest w ogóle słuszne, ale dotrzymanie tej obietnicy ma swoją cenę. Na przykład aby bezpiecznie korzystać z fontu internetowego (fontu dostarczanego przez sieć), Chrome stosuje kilka zabezpieczeń:
- Przetwarzanie czcionek odbywa się w piaskownicy zgodnie z zasadami 2: czcionki są niegodne zaufania, a kod, który je wykorzystuje, jest niebezpieczny.
- Przed przetworzeniem czcionki są przekazywane przez Sanitizer OpenType.
- Wszystkie biblioteki zaangażowane w rozpakowywanie i przetwarzanie czcionek są testowane pod kątem błędów.
Chrome jest dostarczany z FreeType i wykorzystuje go jako główną bibliotekę do przetwarzania czcionek na Androidzie, ChromeOS i Linuxie. Oznacza to, że wielu użytkowników jest narażonych na ataki, jeśli w FreeType występuje luka.
Biblioteka FreeType służy Chrome do obliczania danych i wczytywania zarysów czcionek. Ogólnie rzecz biorąc, korzystanie z FreeType było dla Google ogromnym sukcesem. Jest ono złożone, ale też bardzo przydatne. Korzystamy z niego na wiele sposobów i wnosimy do niego swój wkład. Jest on jednak napisany w niebezpiecznym kodzie i pochodzi z czasu, gdy złośliwe dane wejściowe były mniej prawdopodobne. Tylko nadążanie za strumieniem problemów znalezionych przez fuzzing kosztuje Google co najmniej 0,25 pełnoetatowych inżynierów oprogramowania. Co gorsza, nie zawsze znajdujemy wszystkie błędy lub znajdujemy je dopiero po wysłaniu kodu do użytkowników.
Ten schemat problemów nie jest charakterystyczny tylko dla FreeType. Zauważyliśmy, że inne niebezpieczne biblioteki również mają problemy, mimo że korzystamy z usług najlepszych inżynierów oprogramowania, sprawdzamy każdą zmianę kodu i wymagamy testów.
Dlaczego problemy wciąż się pojawiają
Podczas oceny zabezpieczeń FreeType zaobserwowaliśmy 3 główne klasy problemów (niepełna lista):
Używanie niebezpiecznego języka
Wzór/problem | Przykład |
---|---|
Ręczne zarządzanie pamięcią |
|
Niesprawdzony dostęp do tablicy | CVE-2022-27404 |
Przepełnienie liczb całkowitych | Podczas wykonywania wbudowanych maszyn wirtualnych do wyświetlania i podpowiadania rysowania CFF dla TrueType https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow |
Nieprawidłowe użycie alokacji z przypisaniem wartości zerowej i bez wartości zerowej | Dyskusja w https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, później znaleziono 8 problemów z fuzzerem |
Nieprawidłowe wartości zwracane | Zapoznaj się z poniższym wierszem dotyczącym korzystania z makra |
Problemy związane z projektem
Wzór/problem | Przykład |
---|---|
Makra ukrywają brak wyraźnego rozmiaru |
|
Nowy kod zawsze wprowadza błędy, nawet jeśli jest napisany w sposób zabezpieczający. |
|
Brak testów |
|
Problemy z zależnością
W przypadku fuzzingu wielokrotnie wykryto problemy w bibliotekach, od których zależy FreeType, takich jak bzip2, libpng i zlib. Porównaj na przykład freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate.
Niewystarczające zaciemnianie
Testowanie losowe (fuzzing) to automatyczne testowanie z użyciem szerokiego zakresu danych wejściowych, w tym losowych nieprawidłowych danych, które ma na celu wykrycie wielu typów problemów występujących w stabilnej wersji Chrome. W ramach projektu oss-fuzz firmy Google przeprowadzamy testy fuzz na FreeType. Znalezienie problemów jest możliwe, ale czcionki są w pewnym stopniu odporne na fuzzing z tych powodów:
Pliki czcionek są złożone i podobnie do plików wideo zawierają wiele różnych typów informacji. Pliki czcionek to format kontenera dla wielu tabel, z których każda służy do innego celu w przetwarzaniu tekstu i czcionek, aby razem tworzyć prawidłowo umieszczone na ekranie znaki. W pliku czcionki znajdziesz:
- statyczne metadane, takie jak nazwy czcionek i parametry czcionek zmiennych;
- Mapowania znaków Unicode na glify.
- Złożone reguły i gramatyka układu znaków na ekranie.
- Informacje wizualne: kształty glifów i informacje o obrazach opisujące wygląd glifów umieszczonych na ekranie.
- Tabele wizualne mogą z kolei zawierać programy podpowiedzi TrueType, czyli miniprogramy, które służą do zmiany kształtu glifu.
- Ciągi znaków w tabelach CFF lub CFF2, które są imperatywnymi instrukcjami rysowania krzywych i podpowiadania wykonywanymi w silniku renderowania CFF.
Pliki czcionek są skomplikowane, ponieważ mają własny język programowania i przetwarzanie maszyny stanów, które wymagają konkretnych maszyn wirtualnych do ich wykonania.
Ze względu na złożoność formatu fuzzing nie jest skuteczny w wykrywaniu problemów w plikach czcionek.
Trudno jest uzyskać dobry zasięg kodu lub postęp fuzzera z tych powodów:
- Rozmyte programy podpowiedzi TrueType, ciągi znaków CFF i układ OpenType za pomocą prostych modyfikatorów typu bit-flipping/shift/insertion/deletion mają problemy z osiągnięciem wszystkich kombinacji stanów.
- Fuzzing musi tworzyć co najmniej częściowo prawidłowe struktury. Losowa mutacja rzadko to robi, przez co trudno jest osiągnąć dobre pokrycie, zwłaszcza w przypadku głębszych poziomów kodu.
- Obecne działania związane z fuzzingiem w ClusterFuzz i oss-fuzz nie wykorzystują jeszcze mutacji uwzględniającej strukturę. Korzystanie z mutatorów uwzględniających gramatykę lub strukturę może pomóc w uniknięciu tworzenia wariantów, które są odrzucane na wczesnym etapie, ale kosztem dłuższego czasu rozwoju i wprowadzania zmian, które pomijają części przestrzeni wyszukiwania.
Aby fuzzing mógł się rozwijać, dane w kilku tabelach muszą być zsynchronizowane:
- Zwykłe wzorce mutacji fuzzerów nie generują częściowo prawidłowych danych, więc wiele iteracji jest odrzucanych, a postęp jest powolny.
- Mapowanie znaków, tabele układu OpenType i rysowanie znaków są ze sobą powiązane i zależą od siebie, tworząc przestrzeń kombinatoryczną, której rogów trudno jest dosięgnąć za pomocą zmiękczenia.
- Na przykład znalezienie luki o wysokiej powagi tt_face_get_paint COLRv1 zajęło ponad 10 miesięcy.
Pomimo naszych starań problemy z zabezpieczeniami czcionek wielokrotnie docierały do użytkowników. Zastąpienie FreeTypea alternatywą opartą na Rust zapobiegnie wielu typom podatności.
Skrifa w Chrome
Skia to biblioteka graficzna używana przez Chrome. Skia korzysta z FreeType do wczytywania metadanych i form liter z czcionek. Skrifa to biblioteka Rust, która należy do rodziny bibliotek Fontations i stanowi bezpieczną alternatywę dla części FreeType używanych przez Skirafę.
Aby przejść z FreeType na Skia, zespół Chrome opracował nowy backend czcionek Skia na podstawie Skrifa i stopniowo wdrożył tę zmianę wśród użytkowników:
- W Chrome 128 (sierpień 2024 r.) włączyliśmyFontations do stosowania w rzadziej używanych formatach czcionek, takich jak czcionki kolorowe i CFF2, jako bezpieczny test.
- W Chrome 133 (luty 2025 r.) włączyliśmy Fontations dla wszystkich czcionek internetowych w Linuxie, Androidzie i ChromeOS oraz dla czcionek internetowych jako czcionek zastępczych w systemach Windows i Mac – w przypadkach, gdy system nie obsługuje danego formatu czcionki, a Chrome musi go wyświetlić.
W przypadku integracji z Chrome polegamy na płynnej integracji Rust z kodem źródłowym wprowadzonym przez zespół ds. zabezpieczeń Chrome.
W przyszłości przejdziemy na Fontations również w przypadku czcionek systemowych, zaczynając od Linuxa i ChromeOS, a potem Androida.
Bezpieczeństwo przede wszystkim
Naszym głównym celem jest ograniczenie (a najlepiej wyeliminowanie) luk w zabezpieczeniach spowodowanych dostępem do pamięci poza jej granicami. Rust zapewnia to „out of the box”, o ile unikasz niebezpiecznych bloków kodu.
Aby osiągnąć nasze cele dotyczące wydajności, musimy wykonać jedną operację, która jest obecnie niebezpieczna: reinterpretację dowolnych bajtów jako silnie typowanej struktury danych. Umożliwia to odczytywanie danych z pliku czcionki bez niepotrzebnego kopiowania i jest niezbędne do tworzenia szybkiego analizatora czcionek.
Aby uniknąć nieprawidłowego kodu, zdecydowaliśmy się zlecić to zadanie bytemuck, czyli bibliotece Rust zaprojektowanej specjalnie do tego celu, która jest szeroko testowana i używana w całym ekosystemie. Skupienie reinterpretacji danych nieprzetworzonych w bytemuck zapewnia, że ta funkcja jest dostępna w jednym miejscu i jest sprawdzana, a także zapobiega powtarzaniu niebezpiecznego kodu. Projekt bezpiecznego przetwarzania transkrypcji ma na celu wdrożenie tej funkcji bezpośrednio w kompilatorze Rust. Przejdźmy na tę wersję, gdy tylko będzie ona dostępna.
Poprawność
Skrifa składa się z niezależnych komponentów, w których większość struktur danych jest zaprojektowana tak, aby była niezmienna. Poprawia to czytelność, łatwość konserwacji i wielowątkowość. Dzięki temu kod łatwiej poddaje się testom jednostkowym. Korzystając z tej okazji, opracowaliśmy zestaw około 700 testów jednostkowych, które obejmują cały nasz pakiet od niskiego poziomu procedur analizy do wysokiego poziomu maszyn wirtualnych z podpowiedziami.
Prawidłowość oznacza też wierność, a FreeType jest bardzo ceniony za generowanie obrysów wysokiej jakości. Musimy dopasować jakość, aby była odpowiednią alternatywą. W tym celu opracowaliśmy specjalne narzędzie o nazwie fauntlet, które porównuje dane wyjściowe Skrifa i FreeType w przypadku partii plików czcionek w różnych konfiguracjach. Dzięki temu możemy uniknąć pogorszenia jakości.
Ponadto przed integracją z Chromium przeprowadziliśmy w Skia wiele porównań pikseli, porównując renderowanie FreeType z renderowaniem Skrifa i Skia, aby mieć pewność, że różnice między pikselami są minimalne we wszystkich wymaganych trybach renderowania (w różnych trybach wygładzania krawędzi i podpowiadania).
Testowanie fuzz to ważne narzędzie do określania, jak oprogramowanie reaguje na źle sformułowane i złośliwe dane wejściowe. Od czerwca 2024 r. stale testujemy nasz nowy kod. Dotyczy to bibliotek Rust i kodu integracji. Chociaż fuzzer znalazł (w momencie pisania tego tekstu) 39 błędów, warto zauważyć, że żaden z nich nie był krytyczny dla bezpieczeństwa. Mogą one powodować niepożądane efekty wizualne lub nawet kontrolowane awarie, ale nie spowodują żadnych podatności na ataki.
Do przodu!
Jesteśmy bardzo zadowoleni z wyników naszych działań związanych z wykorzystywaniem Rust do przetwarzania tekstu. Udostępnianie użytkownikom bezpieczniejszego kodu oraz zwiększanie produktywności programistów to dla nas ogromne osiągnięcie. Planujemy nadal szukać możliwości wykorzystania Rust w naszych pakietach tekstowych. Jeśli chcesz dowiedzieć się więcej, Oxidize przedstawia niektóre z planów dotyczących Google Fonts.