Опубликовано: 19 марта 2025 г.
Skrifa написан на Rust и создан в качестве замены FreeType, чтобы сделать обработку шрифтов в Chrome безопасной для всех наших пользователей. Skifra использует безопасность памяти Rust и позволяет нам быстрее совершенствовать технологию шрифтов в Chrome. Переход с FreeType на Skrifa позволяет нам быть гибкими и бесстрашными при внесении изменений в код шрифта. Теперь мы тратим гораздо меньше времени на исправление ошибок безопасности, что приводит к более быстрому обновлению и повышению качества кода.
В этом посте рассказывается, почему Chrome отказался от FreeType, а также некоторые интересные технические подробности улучшений, которые этот шаг позволил сделать.
Зачем заменять FreeType?
Сеть уникальна тем, что она позволяет пользователям получать ненадежные ресурсы из самых разных ненадежных источников, ожидая, что все будет работать и что при этом они будут в безопасности. Это предположение в целом верно, но выполнение этого обещания, данного пользователям, обходится дорого. Например, для безопасного использования веб-шрифта (шрифта, доставляемого по сети) Chrome использует несколько мер безопасности:
- Обработка шрифтов изолирована по правилу двух : они ненадежны, а потребляющий код небезопасен.
- Перед обработкой шрифты проходят через OpenType Sanitizer .
- Все библиотеки, участвующие в распаковке и обработке шрифтов, проходят фазз-тестирование .
Chrome поставляется с FreeType и использует его в качестве основной библиотеки обработки шрифтов на Android, ChromeOS и Linux. Это означает, что многие пользователи подвергаются риску, если во FreeType есть уязвимость.
Библиотека FreeType используется Chrome для вычисления показателей и загрузки контуров с подсказками из шрифтов. В целом, использование FreeType стало огромной победой для Google. Он выполняет сложную работу, и делает ее хорошо, мы широко на нее полагаемся и вносим в нее свой вклад. Однако он написан небезопасным кодом и возник в то время, когда вредоносный ввод был менее вероятен. Простое отслеживание потока проблем, обнаруженных с помощью фаззинга, обходится Google как минимум в 0,25 штатных инженеров-программистов. Хуже того, мы, очевидно, не находим всего или находим что-то только после того, как код отправлен пользователям.
Такая картина проблем не уникальна для FreeType: мы наблюдаем, что другие небезопасные библиотеки допускают проблемы, даже когда мы привлекаем лучших разработчиков программного обеспечения, которых только можем найти, проверяем код каждого изменения и требуем тестирования.
Почему проблемы продолжают появляться
Когда мы оценивали безопасность FreeType, мы заметили три основных класса проблем (неполный список):
Использование небезопасного языка
Шаблон/проблема | Пример |
---|---|
Ручное управление памятью |
|
Непроверенный доступ к массиву | CVE-2022-27404 |
Целочисленные переполнения | Во время выполнения встроенных виртуальных машин для TrueType хинтинга отрисовки CFF и хинтинга https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow |
Неправильное использование обнуления и ненулевого распределения. | Обсуждение в https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94 , впоследствии обнаружено 8 проблем с фаззером. |
Неверные приведения | См. следующую строку об использовании макросов. |
Конкретные проблемы проекта
Шаблон/проблема | Пример |
---|---|
Макросы скрывают отсутствие явного ввода размера |
|
В новый код постоянно добавляются ошибки, даже если он написан с осторожностью. |
|
Отсутствие тестов |
|
Проблемы с зависимостями
Фаззинг неоднократно выявлял проблемы в библиотеках, от которых зависит FreeType, таких как bzip2, libpng и zlib. В качестве примера сравните freetype_bdf_fuzzer: Использование неинициализированного значения в inflate .
Фаззинга недостаточно
Фаззинг — автоматическое тестирование с использованием широкого спектра входных данных, в том числе рандомизированных недействительных — предназначен для выявления многих типов проблем, которые возникают в стабильной версии Chrome. Мы анализируем FreeType в рамках проекта Google oss-fuzz . Он действительно находит проблемы, но шрифты оказались в некоторой степени устойчивыми к фаззингу по следующим причинам.
Файлы шрифтов сложны и сравнимы с видеофайлами, поскольку содержат несколько различных типов информации. Файлы шрифтов представляют собой формат контейнера для нескольких таблиц, где каждая таблица служит разным целям при совместной обработке текста и шрифтов для создания правильно позиционированного глифа на экране. В файле шрифта вы найдете:
- Статические метаданные, такие как имена шрифтов и параметры переменных шрифтов.
- Сопоставление символов Юникода с глифами.
- Сложный набор правил и грамматика для расположения глифов на экране.
- Визуальная информация: формы глифов и информация об изображениях, описывающая, как выглядят глифы, размещенные на экране.
- Визуальные таблицы, в свою очередь, могут включать в себя программы подсказок TrueType, которые представляют собой мини-программы, выполняемые для изменения формы глифа.
- Строки символов в таблицах CFF или CFF2, которые представляют собой императивные инструкции рисования кривых и указания подсказок, выполняемые в механизме рендеринга CFF.
Сложность файлов шрифтов эквивалентна наличию собственного языка программирования и обработки конечных автоматов, требующих для их выполнения определенных виртуальных машин.
Из-за сложности формата фаззинг имеет недостатки при поиске проблем в файлах шрифтов.
Хорошего покрытия кода или прогресса фаззера трудно добиться по следующим причинам:
- Фаззинг программ подсказок TrueType, символьных строк CFF и макета OpenType с использованием простых мутаторов в стиле переворота/сдвига/вставки/удаления битов с трудом позволяет достичь всех комбинаций состояний.
- Фаззинг должен, по крайней мере, создавать частично валидные структуры. Случайные мутации происходят редко, что затрудняет достижение хорошего покрытия, особенно для более глубоких уровней кода.
- Текущие усилия по фаззингу в ClusterFuzz и oss-fuzz еще не используют мутацию с учетом структуры. Использование мутаторов, учитывающих грамматику или структуру, может помочь избежать создания вариантов, которые отвергаются на ранней стадии, за счет увеличения времени на разработку и появления шансов, которые пропускают части пространства поиска.
Данные в нескольких таблицах должны быть синхронизированы, чтобы фаззинг продвигался вперед:
- Обычные шаблоны мутаций фаззеров не дают частично достоверных данных, поэтому многие итерации отклоняются, и прогресс становится медленным.
- Отображение глифов, таблицы макетов OpenType и рисунок глифов связаны и зависят друг от друга, образуя комбинаторное пространство, углы которого трудно достичь с помощью фаззинга.
- Например, на поиск опасной уязвимости tt_face_get_paint COLRv1 потребовалось более 10 месяцев.
Несмотря на все наши усилия, проблемы безопасности шрифтов неоднократно доходили до конечных пользователей. Замена FreeType альтернативой Rust предотвратит несколько целых классов уязвимостей.
Скрифа в Chrome
Skia — графическая библиотека, используемая Chrome. Skia использует FreeType для загрузки метаданных и форм букв из шрифтов. Skrifa — это библиотека Rust, часть семейства библиотек Fontations , которая обеспечивает безопасную замену частей FreeType, используемых Skia.
Чтобы перевести FreeType на Skia, команда Chrome разработала новый сервер шрифтов Skia на основе Skrifa и постепенно представила изменения пользователям:
- В Chrome 128 (август 2024 г.) мы включили шрифты для использования в менее часто используемых форматах шрифтов, таких как цветные шрифты и CFF2, в качестве безопасного пробного запуска.
- В Chrome 133 (февраль 2025 г.) мы включили шрифты для всех используемых веб-шрифтов в Linux, Android и ChromeOS, а также для использования веб-шрифтов в качестве резервного варианта в Windows и Mac — в тех случаях, когда система не поддерживает формат шрифта, но Chrome должен его отображать.
Для интеграции в Chrome мы полагаемся на плавную интеграцию Rust в кодовую базу, представленную командой безопасности Chrome .
В будущем мы перейдем на Fontations и для шрифтов операционных систем, начиная с Linux и ChromeOS, а затем и для Android.
Безопасность прежде всего
Наша основная цель — уменьшить (или, в идеале, устранить!) уязвимости безопасности, вызванные внешним доступом к памяти. Rust предоставляет это из коробки, если вы избегаете небезопасных блоков кода.
Наши цели по производительности требуют от нас выполнения одной операции, которая в настоящее время небезопасна: переинтерпретации произвольных байтов как строго типизированной структуры данных. Это позволяет нам читать данные из файла шрифта без выполнения ненужных копий и важно для создания быстрого анализатора шрифтов.
Чтобы избежать нашего собственного небезопасного кода, мы решили передать эту ответственность на аутсорсинг bytemuck — библиотеке Rust, разработанной специально для этой цели и широко тестируемой и используемой во всей экосистеме. Концентрация повторной интерпретации необработанных данных в bytemuck гарантирует, что эта функциональность будет сосредоточена в одном месте и проверена, а также позволит избежать повторения небезопасного кода для этой цели. Проект Safe Transmute направлен на включение этой функциональности непосредственно в компилятор Rust, и мы осуществим переход, как только он станет доступен.
Корректность имеет значение
Skrifa построен из независимых компонентов, причем большинство структур данных спроектированы так, чтобы быть неизменяемыми. Это улучшает читаемость, удобство сопровождения и многопоточность. Это также делает код более подходящим для модульного тестирования. Мы воспользовались этой возможностью и создали набор из примерно 700 модульных тестов, которые охватывают весь наш стек, от процедур синтаксического анализа низкого уровня до виртуальных машин высокого уровня с хинтингом.
Правильность также подразумевает точность, и FreeType высоко ценится за создание высококачественных контуров. Мы должны соответствовать этому качеству, чтобы быть подходящей заменой. С этой целью мы создали специальный инструмент под названием fauntlet , который сравнивает выходные данные Skrifa и FreeType для пакетов файлов шрифтов в широком диапазоне конфигураций. Это дает нам некоторую уверенность в том, что мы сможем избежать снижения качества.
Кроме того, перед интеграцией в Chromium мы провели широкий набор сравнений пикселей в Skia, сравнивая рендеринг FreeType с рендерингом Skrifa и Skia, чтобы убедиться, что различия в пикселях абсолютно минимальны во всех необходимых режимах рендеринга (в различных режимах сглаживания и хинтинга).
Нечеткое тестирование — важный инструмент для определения того, как часть программного обеспечения будет реагировать на неверные и вредоносные входные данные. С июня 2024 года мы постоянно проводим фаззинг нашего нового кода. Это касается самих библиотек Rust и кода интеграции. Хотя фаззер обнаружил (на момент написания этой статьи) 39 ошибок, стоит отметить, что ни одна из них не была критичной для безопасности . Они могут вызвать нежелательные визуальные результаты или даже контролируемые сбои, но не приведут к появлению уязвимостей, которые можно использовать.
Вперед!
Мы очень довольны результатами наших усилий по использованию Rust для текста. Предоставление более безопасного кода пользователям и повышение продуктивности разработчиков — это огромная победа для нас. Мы планируем продолжать искать возможности использования Rust в наших текстовых стеках. Если вы хотите узнать больше, Oxidize излагает некоторые планы на будущее в отношении Google Fonts.