Seguridad de la memoria para las fuentes web

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

Fecha de publicación: 19 de marzo de 2025

Skrifa está escrito en Rust y se creó como un reemplazo de FreeType para que el procesamiento de fuentes en Chrome sea seguro para todos nuestros usuarios. Skifra aprovecha la seguridad de la memoria de Rust y nos permite iterar más rápido en las mejoras de la tecnología de fuentes en Chrome. Pasar de FreeType a Skrifa nos permite ser ágiles y valientes cuando realizamos cambios en el código de nuestra fuente. Ahora dedicamos mucho menos tiempo a corregir errores de seguridad, lo que genera actualizaciones más rápidas y una mejor calidad del código.

En esta publicación, se explica por qué Chrome dejó de usar FreeType y se incluyen algunos detalles técnicos interesantes sobre las mejoras que permitió este cambio.

¿Por qué reemplazar FreeType?

La Web es única en el sentido de que permite a los usuarios recuperar recursos no confiables de una gran variedad de fuentes no confiables con la expectativa de que todo funcione y que estén seguros al hacerlo. Por lo general, esta suposición es correcta, pero cumplir esa promesa a los usuarios tiene un costo. Por ejemplo, para usar una fuente web de forma segura (una fuente que se entrega a través de la red), Chrome emplea varias mitigaciones de seguridad:

  • El procesamiento de fuentes se aísla según la regla de dos: no son confiables y el código que las consume no es seguro.
  • Las fuentes se pasan a través del limpiador de OpenType antes de procesarlas.
  • Todas las bibliotecas involucradas en la descompresión y el procesamiento de fuentes se prueban con fuzz.

Chrome se envía con FreeType y lo usa como la biblioteca principal de procesamiento de fuentes en Android, ChromeOS y Linux. Eso significa que muchos usuarios están expuestos si hay una vulnerabilidad en FreeType.

Chrome usa la biblioteca FreeType para calcular métricas y cargar contornos con sugerencias de fuentes. En general, el uso de FreeType ha sido un gran éxito para Google. Realiza un trabajo complejo y lo hace bien, lo usamos mucho y le aportamos. Sin embargo, está escrito en código no seguro y tiene sus orígenes en una época en la que era menos probable que se ingresaran datos maliciosos. Solo mantenerse al día con el flujo de problemas que se encuentran con la generación de fuzz le cuesta a Google al menos 0.25 ingenieros de software de tiempo completo. Peor aún, observamos que no encontramos todo o solo encontramos elementos después de que el código se envió a los usuarios.

Este patrón de problemas no es exclusivo de FreeType. Observamos que otras bibliotecas no seguras admiten problemas incluso cuando usamos a los mejores ingenieros de software que podemos encontrar, revisamos el código cada vez que se realiza un cambio y requerimos pruebas.

Por qué los problemas siguen apareciendo

Cuando evaluamos la seguridad de FreeType, observamos tres clases principales de problemas (no exhaustivos):

Uso de un lenguaje no seguro

Patrón o problema Ejemplo
Administración manual de la memoria
Acceso no verificado al array CVE-2022-27404
Desbordamientos de números enteros Durante la ejecución de máquinas virtuales incorporadas para la indicación TrueType de dibujo y de indicaciones CFF
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
Uso incorrecto de la asignación de cero en comparación con la no asignación de cero Discusión en https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, se encontraron 8 problemas de generador de fuzz después
Transmisiones no válidas Consulta la siguiente fila sobre el uso de macros

Problemas específicos del proyecto

Patrón o problema Ejemplo
Las macros ocultan la falta de escritura de tamaño explícita
  • Las macros como FT_READ_* y FT_PEEK_* ocultan los tipos de números enteros que se usan y ocultan que no se usan los tipos C99 con tamaños explícitos (int16_t, etc.)
El código nuevo agrega errores de forma constante, incluso cuando se escribe de forma defensiva.
  • COLRv1 y OT-SVG admiten ambos problemas producidos.
  • El fuzzing encuentra algunos, pero no necesariamente todos, #32421, #52404
Falta de pruebas
  • La creación de fuentes de prueba es difícil y consume mucho tiempo.

Problemas de dependencias

El fuzzing identificó en repetidas ocasiones problemas en las bibliotecas de las que depende FreeType, como bzip2, libpng y zlib. A modo de ejemplo, compara freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate.

El fuzzing no es suficiente

El fuzzing, que consiste en pruebas automatizadas con una amplia variedad de entradas, incluidas las no válidas de forma aleatoria, está diseñado para encontrar muchos de los tipos de problemas que se producen en la versión estable de Chrome. Usamos FreeType como parte del proyecto oss-fuzz de Google. Encuentra problemas, pero los tipos de letra demostraron ser algo resistentes a la generación de fuzz por los siguientes motivos.

Los archivos de fuentes son complejos y comparables a los archivos de video, ya que contienen varios tipos de información diferentes. Los archivos de fuentes son un formato de contenedor para varias tablas, en el que cada tabla tiene un propósito diferente para procesar texto y fuentes en conjunto y producir un glifo posicionado correctamente en la pantalla. En un archivo de fuente, encontrarás lo siguiente:

  • Metadatos estáticos, como nombres de fuentes y parámetros para fuentes variables
  • Asignaciones de caracteres Unicode a glifos.
  • Un conjunto de reglas y una gramática complejos para el diseño de pantalla de los glifos.
  • Información visual: Formas de glifos e información de imagen que describen cómo se ven los glifos colocados en la pantalla.
    • A su vez, las tablas visuales pueden incluir programas de insinuación TrueType, que son miniprogramas que se ejecutan para cambiar la forma del glifo.
    • Cadenas de caracteres en las tablas CFF o CFF2 que son instrucciones imperativas de dibujo de curvas y sugerencias que se ejecutan en el motor de renderización CFF.

La complejidad de los archivos de fuentes equivale a tener su propio lenguaje de programación y procesamiento de máquinas de estado, lo que requiere máquinas virtuales específicas para ejecutarlos.

Debido a la complejidad del formato, la generación de fuzz tiene deficiencias para encontrar problemas en los archivos de fuentes.

Es difícil lograr una buena cobertura de código o un buen progreso del generador de fuzz por los siguientes motivos:

  • La generación de fuzz de programas de sugerencias TrueType, cadenas de caracteres CFF y diseño OpenType con mutadores simples de estilo de inversión de bits, desplazamiento, inserción o eliminación tiene dificultades para llegar a todas las combinaciones de estados.
  • El fuzzing debe producir, al menos, estructuras parcialmente válidas. La mutación aleatoria rara vez lo hace, lo que dificulta lograr una buena cobertura, en especial para niveles de código más profundos.
  • Los esfuerzos actuales de fuzzing en ClusterFuzz y oss-fuzz aún no usan la mutación consciente de la estructura. El uso de mutadores conscientes de la gramática o la estructura podría ayudar a evitar la producción de variantes que se rechazan antes, a costa de tardar más tiempo en desarrollarse y presentar posibilidades que omiten partes del espacio de búsqueda.

Los datos de varias tablas deben estar sincronizados para que la generación de fuzzing avance:

  • Los patrones de mutación habituales de los generadores de fuzz no producen datos parcialmente válidos, por lo que se rechazan muchas iteraciones y el progreso se vuelve lento.
  • La asignación de glifos, las tablas de diseño OpenType y el dibujo de glifos están conectados y dependen entre sí, formando un espacio combinatorio cuyas esquinas son difíciles de alcanzar con la generación de fuzz.
  • Por ejemplo, la vulnerabilidad de gravedad alta tt_face_get_paintCOLRv1 tardó más de 10 meses en encontrarse.

A pesar de nuestros esfuerzos, los problemas de seguridad de las fuentes llegaron a los usuarios finales de forma reiterada. Reemplazar FreeType por una alternativa de Rust evitará varias clases completas de vulnerabilidad.

Skrifa en Chrome

Skia es la biblioteca de gráficos que usa Chrome. Skia se basa en FreeType para cargar metadatos y formas de letras de las fuentes. Skrifa es una biblioteca de Rust, parte de la familia de bibliotecas Fontations, que proporciona un reemplazo seguro para las partes de FreeType que usa Skia.

Para migrar FreeType a Skia, el equipo de Chrome desarrolló un nuevo backend de fuentes de Skia basado en Skrifa y lanzó el cambio gradualmente a los usuarios:

Para la integración en Chrome, dependemos de la integración fluida de Rust en la base de código que presentó el equipo de seguridad de Chrome.

En el futuro, también cambiaremos a Fontations para las fuentes del sistema operativo, comenzando por Linux y ChromeOS, y luego en Android.

La seguridad ante todo

Nuestro objetivo principal es reducir (o, idealmente, eliminar) las vulnerabilidades de seguridad que se producen por el acceso fuera de los límites a la memoria. Rust proporciona esto listo para usar, siempre y cuando evites los bloques de código no seguros.

Nuestros objetivos de rendimiento requieren que realicemos una operación que, en la actualidad, no es segura: la reinterpretación de bytes arbitrarios como una estructura de datos fuertemente tipada. Esto nos permite leer los datos de un archivo de fuente sin realizar copias innecesarias y es esencial para producir un analizador de fuentes rápido.

Para evitar nuestro propio código no seguro, decidimos subcontratar esta responsabilidad a bytemuck, que es una biblioteca de Rust diseñada específicamente para este propósito y que se prueba y usa ampliamente en todo el ecosistema. Concentrar la reinterpretación de datos sin procesar en bytemuck garantiza que tengamos esta funcionalidad en un solo lugar y auditada, y evita repetir código no seguro para el propósito. El objetivo del proyecto de transmutación segura es incorporar esta funcionalidad directamente en el compilador de Rust, y realizaremos el cambio en cuanto esté disponible.

La precisión es importante

Skrifa se compila a partir de componentes independientes en los que la mayoría de las estructuras de datos están diseñadas para ser inmutables. Esto mejora la legibilidad, el mantenimiento y el procesamiento en varios subprocesos. También hace que el código sea más apto para las pruebas de unidades. Aprovechamos esta oportunidad y creamos un paquete de aproximadamente 700 pruebas de unidad que abarcan toda nuestra pila, desde rutinas de análisis de bajo nivel hasta máquinas virtuales de sugerencias de alto nivel.

La corrección también implica fidelidad, y FreeType es muy apreciado por su generación de contornos de alta calidad. Debemos coincidir con esta calidad para ser un reemplazo adecuado. Para ello, creamos una herramienta personalizada llamada fauntlet que compara el resultado de Skrifa y FreeType para lotes de archivos de fuentes en una amplia variedad de configuraciones. Esto nos brinda cierta seguridad de que podemos evitar las regresiones en la calidad.

Además, antes de la integración en Chromium, ejecutamos un amplio conjunto de comparaciones de píxeles en Skia, en el que comparamos la renderización de FreeType con la de Skrifa y Skia para garantizar que las diferencias de píxeles sean absolutamente mínimas, en todos los modos de renderización requeridos (en diferentes modos de suavizado de bordes y sugerencias).

Las pruebas de fuzz son una herramienta importante para determinar cómo reaccionará un software a entradas maliciosas y con el formato incorrecto. Desde junio de 2024, sometimos nuestro nuevo código a fuzzing de forma continua. Esto abarca las bibliotecas de Rust y el código de integración. Si bien el generador de fuzz encontró (al momento de escribir este artículo) 39 errores, vale la pena señalar que ninguno de ellos fue crítico para la seguridad. Pueden causar resultados visuales no deseados o incluso fallas controladas, pero no generarán vulnerabilidades aprovechables.

Adelante.

Estamos muy contentos con los resultados de nuestros esfuerzos por usar Rust para el texto. Proporcionar un código más seguro a los usuarios y aumentar la productividad de los desarrolladores es un gran logro para nosotros. Planeamos seguir buscando oportunidades para usar Rust en nuestras pilas de texto. Si quieres obtener más información, en Oxidize se describen algunos de los planes futuros de Google Fonts.