Publicado em: 19 de março de 2025
O Skrifa foi escrito em Rust e criado como substituto do FreeType para tornar o processamento de fontes no Chrome seguro para todos os nossos usuários. O Skrifa aproveita a segurança de memória do Rust e permite que iteremos mais rápido nas melhorias da tecnologia de fontes no Chrome. A migração do FreeType para o Skrifa nos permite ser ágeis e ousados ao fazer mudanças no código da fonte. Agora gastamos muito menos tempo corrigindo bugs de segurança, o que resulta em atualizações mais rápidas e melhor qualidade do código.
Esta postagem explica por que o Chrome deixou de usar o FreeType e compartilha alguns detalhes técnicos interessantes sobre as melhorias que essa mudança possibilitou.
Por que substituir o FreeType?
A Web é única porque permite que os usuários busquem recursos não confiáveis de uma ampla variedade de fontes não confiáveis com a expectativa de que as coisas simplesmente funcionem e que eles estejam seguros ao fazer isso. Essa proposição geralmente está correta, mas manter essa promessa aos usuários tem um custo. Por exemplo, para usar uma fonte da Web com segurança (uma fonte entregue pela rede), o Chrome emprega várias mitigações de segurança:
- O processamento de fontes é em sandbox de acordo com a regra de dois: elas não são confiáveis, e o código de consumo não é seguro.
- As fontes são transmitidas pelo OpenType Sanitizer antes do processamento.
- Todas as bibliotecas envolvidas na descompactação e no processamento de fontes são testadas com fuzzing.
O Chrome é fornecido com o FreeType e o usa como a principal biblioteca de processamento de fontes no Android, ChromeOS e Linux. Isso significa que um grande número de usuários fica exposto se houver uma vulnerabilidade no FreeType.
A biblioteca FreeType é usada pelo Chrome para calcular métricas e carregar contornos sugeridos de fontes. No geral, o uso do FreeType foi um grande sucesso para o Google. Ele faz um trabalho complexo e bem feito. Confiamos muito nele e contribuímos para ele. No entanto, ele foi escrito em código não seguro e tem origem em uma época em que entradas maliciosas eram menos prováveis. Apenas acompanhar o fluxo de problemas encontrados pelo fuzzing custa ao Google pelo menos 0,25 engenheiros de software em tempo integral. Pior ainda, não encontramos tudo ou encontramos apenas depois que o código foi enviado aos usuários.
Esse padrão de problemas não é exclusivo do FreeType. Observamos que outras bibliotecas não seguras admitem problemas mesmo quando usamos os melhores engenheiros de software que conseguimos encontrar, revisamos o código de cada mudança e exigimos testes.
Por que os problemas continuam aparecendo
Ao avaliar a segurança do FreeType, observamos três classes principais de problemas (não exaustivas):
Uso de linguagem não segura
| Padrão/problema | Exemplo |
|---|---|
| Gerenciamento manual de memória |
|
| Acesso a matriz sem verificação | CVE-2022-27404 |
| Estouros de números inteiros | Durante a execução de máquinas virtuais incorporadas para dicas TrueType de desenho e dicas CFF https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow |
| Uso incorreto da alocação de zeragem e não zeragem | Discussão em https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, 8 problemas de fuzzer encontrados depois |
| Transmissões inválidas | Confira a linha a seguir sobre o uso de macros |
Problemas específicos do projeto
| Padrão/problema | Exemplo |
|---|---|
| As macros ocultam a falta de digitação de tamanho explícito |
|
| Um novo código sempre adiciona bugs, mesmo quando escrito de forma defensiva. |
|
| Falta de testes |
|
Problemas de dependência
O fuzzing identificou repetidamente problemas nas bibliotecas de que o FreeType depende, como bzip2, libpng e zlib. Por exemplo, compare freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate.
O fuzzing não é suficiente
O fuzzing, que é um teste automatizado com uma ampla variedade de entradas, incluindo entradas inválidas aleatórias, tem como objetivo encontrar muitos dos tipos de problemas que chegam à versão estável do Chrome. Fazemos fuzzing do FreeType como parte do projeto oss-fuzz do Google. Ele encontra problemas, mas as fontes se mostraram um pouco resistentes ao fuzzing pelos seguintes motivos.
Os arquivos de fontes são complexos, comparáveis aos arquivos de vídeo, porque contêm vários tipos diferentes de informações. Os arquivos de fontes são um formato de contêiner para várias tabelas, em que cada uma tem uma finalidade diferente no processamento de texto e fontes juntos para produzir um glifo posicionado corretamente na tela. Em um arquivo de fontes, você encontra:
- Metadados estáticos, como nomes de fontes e parâmetros para fontes variáveis.
- Mapeamentos de caracteres Unicode para glifos.
- Um conjunto de regras e uma gramática complexos para o layout de tela dos glifos.
- Informações visuais: formas de glifos e informações de imagem que descrevem a aparência dos glifos colocados na tela.
- As tabelas visuais podem incluir programas de sugestão TrueType, que são miniprogramas executados para mudar o formato do glifo.
- Strings de caracteres nas tabelas CFF ou CFF2, que são instruções imperativas de desenho e sugestão de curvas executadas no mecanismo de renderização CFF.
Há complexidade em arquivos de fontes equivalente a ter uma linguagem de programação própria e processamento de máquina de estado, exigindo máquinas virtuais específicas para executá-los.
Devido à complexidade do formato, o fuzzing tem limitações na detecção de problemas em arquivos de fontes.
É difícil alcançar uma boa cobertura de código ou progresso do fuzzer pelos seguintes motivos:
- A fuzzing de programas de sugestão TrueType, strings de caracteres CFF e layout OpenType usando mutadores simples de inversão/deslocamento/inserção/exclusão de bits tem dificuldade para alcançar todas as combinações de estados.
- O fuzzing precisa produzir pelo menos estruturas parcialmente válidas. A mutação aleatória raramente faz isso, o que dificulta a cobertura, principalmente para níveis mais profundos de código.
- Os esforços atuais de fuzzing no ClusterFuzz e no oss-fuzz ainda não estão usando mutação com reconhecimento de estrutura. O uso de mutadores com reconhecimento de gramática ou estrutura pode ajudar a evitar a produção de variantes rejeitadas no início, mas custa mais tempo de desenvolvimento e introduz chances que perdem partes do espaço de pesquisa.
Os dados em várias tabelas precisam estar sincronizados para que o fuzzing avance:
- Os padrões de mutação comuns dos fuzzers não produzem dados parcialmente válidos, então muitas iterações são rejeitadas e o progresso fica lento.
- O mapeamento de glifos, as tabelas de layout OpenType e o desenho de glifos estão conectados e dependem uns dos outros, formando um espaço combinatório cujos cantos são difíceis de alcançar com fuzzing.
- Por exemplo, a vulnerabilidade de alta gravidade tt_face_get_paint COLRv1 levou mais de 10 meses para ser encontrada.
Apesar dos nossos esforços, problemas de segurança de fontes têm chegado repetidamente aos usuários finais. A substituição do FreeType por uma alternativa em Rust evita várias classes inteiras de vulnerabilidade.
Skrifa no Chrome
O Skia é a biblioteca gráfica usada pelo Chrome. O Skia depende do FreeType para carregar metadados e formas de letras de fontes. Skrifa é uma biblioteca Rust, parte da família de bibliotecas Fontations, que oferece uma substituição segura para as partes do FreeType usadas pelo Skia.
Para fazer a transição do FreeType para o Skia, a equipe do Chrome desenvolveu um novo back-end de fontes do Skia baseado no Skrifa e lançou gradualmente a mudança para os usuários:
- No Chrome 128 (agosto de 2024), ativamos o Fontations para uso em formatos de fonte menos usados com frequência, como fontes de cores e CFF2, como um teste seguro.
- No Chrome 133 (fevereiro de 2025), ativamos o Fontations para todo o uso de fontes da Web no Linux, Android e ChromeOS, e para o uso de fontes da Web como substituição no Windows e Mac. Isso acontece quando o sistema não oferece suporte a um formato de fonte, mas o Chrome precisa mostrar.
Para a integração ao Chrome, contamos com a integração tranquila do Rust à base de código introduzida pela equipe de segurança do Chrome.
No futuro, também vamos mudar para o Fontations nas fontes do sistema operacional, começando pelo Linux e ChromeOS e depois no Android.
Segurança em primeiro lugar
Nosso objetivo principal é reduzir (ou, idealmente, eliminar) as vulnerabilidades de segurança causadas por acesso fora dos limites à memória. O Rust oferece isso de imediato, desde que você evite blocos de código inseguros.
Nossas metas de performance exigem que realizemos uma operação que atualmente é insegura: a reinterpretação de bytes arbitrários como uma estrutura de dados fortemente tipada. Isso permite ler os dados de um arquivo de fonte sem realizar cópias desnecessárias e é essencial para produzir um analisador de fontes rápido.
Para evitar nosso próprio código não seguro, optamos por terceirizar essa responsabilidade para bytemuck, uma biblioteca Rust projetada especificamente para essa finalidade e amplamente testada e usada em todo o ecossistema. Concentrar a reinterpretação de dados brutos no bytemuck garante que tenhamos essa funcionalidade em um só lugar e auditada, além de evitar a repetição de código não seguro para essa finalidade. O projeto de transmutação segura (link em inglês) visa incorporar essa funcionalidade diretamente ao compilador Rust. Faremos a mudança assim que ela estiver disponível.
A correção é importante
O Skrifa é criado com componentes independentes, em que a maioria das estruturas de dados é projetada para ser imutável. Isso melhora a legibilidade, a capacidade de manutenção e a multithreading. Isso também torna o código mais adequado para testes de unidade. Aproveitamos essa oportunidade e produzimos um conjunto de aproximadamente 700 testes de unidade que cobrem nossa pilha completa, desde rotinas de análise de baixo nível até máquinas virtuais de sugestão de alto nível.
A correção também implica fidelidade, e o FreeType é altamente considerado por sua geração de contornos de alta qualidade. Precisamos corresponder a essa qualidade para ser uma substituição adequada. Para isso, criamos uma ferramenta personalizada chamada fauntlet que compara a saída do Skrifa e do FreeType para lotes de arquivos de fontes em uma ampla variedade de configurações. Isso nos dá alguma garantia de que podemos evitar regressões na qualidade.
Além disso, antes da integração ao Chromium, executamos um amplo conjunto de comparações de pixels no Skia, comparando a renderização do FreeType com a do Skrifa e a do Skia para garantir que as diferenças de pixels sejam absolutamente mínimas em todos os modos de renderização necessários (em diferentes modos de suavização de serrilhado e sugestão).
O teste de fuzzing é uma ferramenta importante para determinar como um software vai reagir a entradas malformadas e maliciosas. Estamos fazendo fuzzing contínuo do nosso novo código desde junho de 2024. Isso abrange as próprias bibliotecas Rust e o código de integração. Embora o fuzzer tenha encontrado (até o momento da redação deste artigo) 39 bugs, vale a pena observar que nenhum deles é crítico para a segurança. Elas podem causar resultados visuais indesejados ou até mesmo falhas controladas, mas não levam a vulnerabilidades exploráveis.
Avante!
Estamos muito satisfeitos com os resultados dos nossos esforços para usar Rust em texto. Oferecer um código mais seguro aos usuários e aumentar a produtividade dos desenvolvedores é uma grande vitória para nós. Planejamos continuar buscando oportunidades para usar o Rust em nossas pilhas de texto. Se quiser saber mais, o Oxidize descreve alguns dos planos futuros do Google Fonts.