Segurança da memória para fontes da Web

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

Publicado em 19 de março de 2025

O Skrifa é escrito em Rust e criado como um substituto do FreeType para tornar o processamento de fontes no Chrome seguro para todos os usuários. O Skifra aproveita a segurança de memória do Rust e permite que façamos iterações mais rápidas 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 passamos muito menos tempo corrigindo bugs de segurança, resultando 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 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 vão funcionar e que eles estão seguros ao fazer isso. Essa suposição geralmente está correta, mas manter essa promessa para os 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 Chrome é enviado com o FreeType e o usa como a biblioteca principal de processamento de fontes no Android, ChromeOS e Linux. Isso significa que muitos usuários serão expostos se houver uma vulnerabilidade no FreeType.

A biblioteca FreeType é usada pelo Chrome para calcular métricas e carregar contornos indicados de fontes. No geral, o uso do FreeType foi uma grande vitória para o Google. Ele faz um trabalho complexo e o faz bem. Nós confiamos muito nele e contribuímos com ele. No entanto, ele é escrito em um código não seguro e tem origem em um momento em que entradas maliciosas eram menos prováveis. Apenas acompanhar o fluxo de problemas encontrados por fuzzing custa ao Google pelo menos 0,25 engenheiros de software em tempo integral. Pior ainda, não encontramos tudo ou encontramos coisas apenas depois que o código é 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 podemos 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 insegura

Padrão/problema Exemplo
Gerenciamento manual de memória
Acesso à matriz não verificado CVE-2022-27404
Estouros de números inteiros Durante a execução de máquinas virtuais incorporadas para a sugestão de TrueType de desenho e sugestão de CFF
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
Uso incorreto da alocação de zero em vez de não zero Discussão em https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, 8 problemas de fuzzer encontrados depois disso.
Transmissões inválidas Consulte a linha a seguir sobre o uso de macros

Problemas específicos do projeto

Padrão/problema Exemplo
Macros ocultam a falta de digitação de tamanho explícita
  • Macros como FT_READ_* e FT_PEEK_* ocultam quais tipos de inteiros estão sendo usados, ocultando que os tipos C99 com tamanhos explícitos (int16_t, etc.) não são usados.
O novo código adiciona bugs de forma consistente, mesmo quando escrito de forma defensiva.
  • O COLRv1 e o OT-SVG oferecem suporte para os dois problemas produzidos
  • O fuzzing encontra alguns, mas não necessariamente todos, #32421, #52404
Falta de testes
  • Criar fontes de teste é demorado e difícil

Problemas de dependência

O fuzzing identificou problemas repetidamente nas bibliotecas que o FreeType depende, como bzip2, libpng e zlib. Como exemplo, compare freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate.

O fuzzing não é suficiente

O fuzzing, testes automatizados com uma ampla gama de entradas, incluindo as inválidas aleatórias, tem como objetivo encontrar muitos dos tipos de problemas que chegam à versão estável do Chrome. Usamos o 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 fonte são complexos, comparáveis a arquivos de vídeo, porque contêm vários tipos diferentes de informações. Os arquivos de fonte são um formato de contêiner para várias tabelas, em que cada tabela tem uma finalidade diferente no processamento de texto e fontes para produzir um glifo posicionado corretamente na tela. Em um arquivo de fonte, você vai encontrar:

  • Metadados estáticos, como nomes e parâmetros de fontes variáveis.
  • Mapeamentos de caracteres Unicode para glifos.
  • Um conjunto de regras e uma gramática complexas para o layout de tela de 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ões TrueType, que são miniprogramas executados para mudar a forma do glifo.
    • Strings de caracteres nas tabelas CFF ou CFF2 que são instruções de desenho de curva e sugestões obrigatórias executadas no mecanismo de renderização CFF.

Há complexidade nos arquivos de fonte equivalentes a ter seu próprio processamento de máquina de estado e linguagem de programação, exigindo máquinas virtuais específicas para execução.

Devido à complexidade do formato, o fuzzing tem falhas na descoberta de problemas em arquivos de fontes.

É difícil alcançar uma boa cobertura de código ou progresso do fuzzer pelos seguintes motivos:

  • Programas de sugestões de TrueType, strings de caracteres CFF e layout OpenType que usam modificadores simples de estilo de inversão/mudança/inserção/exclusão de bits têm dificuldades 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, especialmente para níveis mais profundos de código.
  • Os esforços atuais de fuzzing no ClusterFuzz e oss-fuzz ainda não usam mutação ciente da estrutura. O uso de modificadores que reconhecem a gramática ou a estrutura pode ajudar a evitar a produção de variantes que são rejeitadas mais cedo, com o custo de levar mais tempo para desenvolver e introduzir 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 usuais de 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 a exibição de glifos estão conectados e dependem uns dos outros, formando um espaço combinatório em que os 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, os problemas de segurança de fontes chegaram repetidamente aos usuários finais. Substituir o FreeType por uma alternativa Rust vai evitar várias classes de vulnerabilidade.

Skrifa no Chrome

A Skia é a biblioteca gráfica usada pelo Chrome. O Skia depende do FreeType para carregar metadados e formas de letras de fontes. A Skrifa é uma biblioteca Rust que faz parte da família de bibliotecas Fontations, oferecendo uma substituição segura para as partes do FreeType usadas pela Skia.

Para fazer a transição do FreeType para o Skia, a equipe do Chrome desenvolveu um novo back-end de fonte Skia com base na Skrifa e lançou gradualmente a mudança para os usuários:

Para a integração no Chrome, contamos com a integração suave do Rust na base de código introduzida pela equipe de segurança do Chrome.

No futuro, também vamos mudar para o Fontations para fontes do sistema operacional, começando pelo Linux e ChromeOS, depois no Android.

Segurança em primeiro lugar

Nosso objetivo principal é reduzir (ou, de preferência, eliminar) as vulnerabilidades de segurança causadas por acesso fora dos limites à memória. O Rust oferece isso pronto para uso, desde que você evite blocos de código não seguros.

Nossas metas de desempenho exigem que realizemos uma operação que atualmente não é segura: reinterpretação de bytes arbitrários como uma estrutura de dados fortemente tipada. Isso nos 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, escolhemos terceirizar essa responsabilidade para o bytemuck, que é uma biblioteca Rust projetada especificamente para essa finalidade e é amplamente testada e usada em todo o ecossistema. A concentração da reinterpretação de dados brutos em bytemuck garante que temos 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 objetivo do projeto de transmutação segura (link em inglês) é incorporar essa funcionalidade diretamente ao compilador Rust. Vamos fazer 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 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 cerca de 700 testes de unidade que abrangem toda a pilha, 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 gerar 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 da Skrifa e da FreeType para lotes de arquivos de fonte em uma ampla gama 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 conjunto amplo de comparações de pixels no Skia, comparando a renderização do FreeType com a renderização do Skrifa e 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 anti-aliasing e sugestões).

O teste de fuzz é uma ferramenta importante para determinar como um software vai reagir a entradas malformadas e maliciosas. Estamos testando nosso novo código desde junho de 2024. Isso abrange as bibliotecas Rust e o código de integração. Embora o fuzzer tenha encontrado 39 bugs (até o momento em que este artigo foi escrito), é importante observar que nenhum deles foi crítico para a segurança. Elas podem causar resultados visuais indesejados ou até falhas controladas, mas não levam a vulnerabilidades que podem ser exploradas.

Para frente!

Estamos muito satisfeitos com os resultados dos nossos esforços para usar o Rust para 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 nas nossas pilhas de texto. Para saber mais, Oxidize descreve alguns dos planos futuros do Google Fonts.