1 pontos por GN⁺ 2024-03-17 | 1 comentários | Compartilhar no WhatsApp
  • O Ladybird consegue lidar até certo ponto com conteúdo web normal, mas ao rodar o fuzzer de DOM Domato, do Google Project Zero, casos de borda ocultos no motor do navegador apareceram rapidamente
  • Em entradas anormais, mas realisticamente possíveis, como DOMs criados em JavaScript para contornar regras do parser, documentos sem window e referências circulares em SVG, foram encontrados e corrigidos 5 bugs reais
  • Suposições implícitas internas da implementação — como assumir um ancestral table para ``, assumir window em documentos de DOMParser e um erro na busca de irmãos em Element.before() — levavam a crashes ou loops infinitos
  • O problema de acesso a contentWindow de um iframe removido não era uma falha exclusiva do Ladybird; ele também se relacionava com a suposição de browsing context na especificação HTML, levando a uma issue no WHATWG HTML
  • Fuzzers como o Domato expõem problemas de segurança e estabilidade que testes com páginas web normais dificilmente capturam; o próximo desafio do Ladybird é estabilizar o suficiente para suportar fuzzing contínuo e então executá-lo automaticamente

Teste de estresse no Ladybird com Domato

  • O Ladybird consegue lidar até certo ponto com conteúdo web bem formado, mas aqui a ideia foi jogar entradas estranhas com uma ferramenta de pesquisa de segurança para ver que tipos de problemas apareciam
  • A ferramenta usada foi o fuzzer de DOM Domato, do Google Project Zero
    • O Domato gera páginas web aleatórias compostas por HTML, CSS e JavaScript em sua maioria válidos, mas incomuns
    • As páginas geradas foram carregadas na build de depuração do Ladybird e seu comportamento foi observado
  • Como o README do Domato destaca muitos bugs encontrados em navegadores importantes, a expectativa era que também fosse possível achar falhas relevantes no Ladybird

Desreferência de ponteiro nulo quando está dentro de

  • O primeiro problema foi encontrado em menos de 1 segundo, e uma saída do Domato de 562 KiB pôde ser reduzida à forma abaixo

let mfrac = document.createElement("mfrac");
mfrac.appendChild(document.createElement("th"));
document.body.appendChild(mfrac);

  • Em uma build do Ladybird com UBSAN ativado, a chamada a table_containing_cell em HTMLTableCellElement.cpp provocou uma desreferência de ponteiro nulo
  • A causa era que a implementação de e no Ladybird assumia que sempre havia um `` acima na árvore DOM
    • O parser HTML não permite marcação como ``
    • Em navegadores que seguem a especificação, carregar essa marcação cria apenas um `` vazio internamente
    • Mas, ao criar nós diretamente pela API DOM do JavaScript, é possível contornar parte das regras do parser e colocar dentro de
  • O código problemático era usado para implementar um comportamento antigo em que e aplicam borda e padding CSS não apenas à caixa da tabela, mas também a cada célula
  • A correção removeu a suposição de que e sempre têm um ancestral ``
    • table_containing_cell(*this) foi substituído por first_ancestor_of_type()
    • Se não houver ancestral tabela, a função retorna imediatamente
    • O commit da correção está aqui

Atribuição de manipulador de evento `` em documento sem window

  • O segundo problema também foi encontrado em menos de 1 segundo, e uma saída do Domato de 472 KiB foi reduzida ao código abaixo

var parser = new DOMParser();
var doc = parser.parseFromString("", "text/html");
var body = doc.createElement("body");
body.onblur = null;

  • O Ladybird abortou por falha na validação de GCPtr
  • O ponto central é o comportamento especial das propriedades de manipulador de evento onfoo em ``
    • Para compatibilidade com conteúdo web legado, atribuições a document.body.onfoo devem ser encaminhadas para window.onfoo
    • Porém, documentos criados com DOMParser não têm objeto window
  • O modelo interno de objetos do Ladybird estava estruturado incorretamente, assumindo que todo document sempre teria window
  • Depois da correção, Document::window() passou a retornar um valor anulável, e vários pontos passaram a tratar null
    • Ao atribuir document.body.onblur em um documento sem window, não acontece nada, como nos outros navegadores

Referência circular em `` de SVG

  • O terceiro problema foi uma recursão infinita quando um gradiente SVG referenciava a si mesmo

  • O SVG precisa suportar tanto SVG inline dentro de HTML quanto o formato de imagem externo, e um gradiente pode referenciar outro gradiente para herdar cores
  • A implementação do Ladybird não considerava o caso em que um gradiente referencia a si mesmo, então, ao seguir a cadeia de referências, entrava em loop continuamente
  • Bloquear apenas o caso em que ele referencia a si mesmo diretamente não resolve referências circulares com várias etapas

  • O tratamento correto é rastrear todos os gradientes já visitados e interromper o rastreamento da cadeia ao encontrar novamente um gradiente já visitado
  • O Firefox mostra uma reclamação no console de desenvolvedor para esse tipo de gradiente

Acesso a propriedade window de iframe removido e bug na especificação HTML

  • O quarto problema acontecia ao chamar getSelection() em um contentWindow obtido antes da remoção do iframe

window.onload = function() {
let iframe = document.querySelector("iframe")
let iframeWindow = iframe.contentWindow;
iframe.remove();
iframeWindow.getSelection();
}

  • O Ladybird gerava um erro de tempo de execução em WindowProxy.cpp por vincular uma referência de ponteiro nulo a BrowsingContext
  • Quando um iframe é removido do DOM, seu content document é desconectado do próprio browsing context
  • Ao obter ou definir propriedades do objeto window, o algoritmo da especificação HTML "check if an access between two browsing contexts should be reported" é executado
    • Esse algoritmo verifica o browsing context da window que acessa e da window acessada
    • A especificação assumia incorretamente que, no momento do acesso à propriedade, ambas as windows ainda teriam browsing contexts conectados
  • Foi aberta uma issue para a especificação HTML, e no Ladybird foi adicionado primeiro um null check
  • Ao encontrar bugs na especificação enquanto trabalha no Ladybird, é possível melhorar a especificação para todos com um bug report ou proposta de correção

Loop infinito em Element.before()

  • O quinto problema se manifestava como carregamento de página que nunca terminava e uso de CPU em 100%

two.before(one);

  • A causa era um erro na lógica da implementação de before() para encontrar, entre os irmãos anteriores de ``, o primeiro que não estivesse incluído nos argumentos
  • O loop antigo obtinha node->previous_sibling() novamente a cada iteração
while (auto previous_sibling = node->previous_sibling()) {
    // check if previous_sibling is one of the arguments
}
  • Na prática, o correto era percorrer a cadeia de irmãos, avançando continuamente com previous_sibling->previous_sibling()
for (auto sibling = node->previous_sibling(); sibling; sibling = sibling->previous_sibling()) {
    // check if previous_sibling is one of the arguments
}

Resultados do fuzzing e próximos passos

  • Nesta sessão, foram encontrados 5 bugs reais; um deles era um bug na especificação HTML, e todos foram corrigidos
  • Ficou claro que o Ladybird quebra muito rapidamente ao encontrar entradas estranhas e inesperadas
  • Fuzzers como o Domato são um recurso útil para quem quer tornar software mais robusto
  • O próximo passo é estabilizar o Ladybird até um nível em que ele consiga suportar entradas contínuas de fuzzing
  • Quando estiver estável o suficiente, o plano é executá-lo automaticamente em algum lugar na nuvem para encontrar ainda mais problemas

1 comentários

 
GN⁺ 2024-03-17
Comentários do Hacker News
  • Mostra bem por que várias implementações independentes de uma especificação são valiosas.
    Só este texto já encontrou uma lacuna na especificação, e parece que havia mais — ou que ainda vão aparecer outras.

    • Sim. Já encontramos e relatamos muitos problemas em toda a especificação de HTML, CSS e JS.
      Várias implementações independentes são importantes para a saúde de longo prazo da plataforma web, então estamos tentando cumprir esse papel também.
    • Fico curioso para saber por que esse fuzzer não encontrou bugs em navegadores populares.
    • Essa conclusão parece um pouco um salto lógico.
      Por exemplo, é parecido com eu tuitar “berinjela é meu legume favorito”, alguém me corrigir dizendo “na verdade, é uma fruta”, e eu então dizer que “o valor do Twitter foi comprovado”.
      Não estou dizendo que este trabalho em si, ou várias implementações de uma especificação, não tenham valor, mas acho que este exemplo específico ainda não sustenta essa implicação.
  • Gosto de como este projeto continua mostrando que uma equipe pequena também pode criar coisas incríveis.
    Acho que seria muito mais difícil fazer algo assim dentro de uma empresa com muitos stakeholders.

    • O projeto é legal, mas fico me perguntando se a abordagem de começar com “lidar razoavelmente bem com conteúdo web normal” e depois ir corrigindo de trás para frente a especificação, o comportamento de fato dos navegadores e possíveis questões de segurança pode realmente levar a um navegador de produção.
      Em um projeto de hobby, sempre dá para voltar e refazer, mas é difícil afastar a sensação de que algumas dessas coisas deveriam ter entrado na arquitetura desde o início.
  • Já implementaram SVG? Está avançando muito mais rápido do que eu imaginava, então estou acompanhando com interesse.

    • Implementamos uma parte considerável da especificação de SVG, mas ainda falta muita coisa.
      Em especial, animação é uma grande parte que ainda está faltando.
  • No caso da issue nº 3, também parece uma boa ideia impor um limite máximo de profundidade para gradientes que apontam para outros gradientes.
    Isso pode servir como defesa em profundidade contra erros ou limitações na lógica de “já vi esta referência antes?”.
    Não conheço bem gradientes SVG, e talvez exista algum motivo legítimo para cadeias de referência com 1000 itens, mas, em um ambiente real, se eu visse isso, acharia muito provável que fosse um ataque ou uma entrada de fuzzer.

    • Na área de defesa contra malware, sempre víamos esse tipo de abuso estrutural, e nunca vi um caso legítimo com mais de 5 níveis de profundidade.
  • Estou escrevendo este comentário no Ladybird.
    Agora o Hacker News funciona no Ladybird.
    Uso o Ladybird por alguns minutos por dia para navegar em sites como Hacker News ou OSnews.
    É lento e frágil, mas funciona. Considerando que o projeto é tão jovem e que literalmente tudo foi escrito do zero, isso por si só já é impressionante.
    Estou realmente ansioso para ver o Ladybird amadurecer.

  • É interessante, mas me incomoda que quase todo desenvolvedor encerre as coisas como aparece na issue nº 1: “Achei! Fiz o commit da correção, pronto!”.
    Não deveria ser assim; é preciso entender exatamente o que estava errado. Por exemplo, se o problema era a suposição de que “o pai sempre existe”, então é preciso procurar o mesmo tipo de erro em toda a base de código.
    É preciso usar criatividade para descobrir onde mais a mesma coisa pode acontecer. Nunca está em apenas um lugar.
    O fato de o software moderno ser um pesadelo cheio de bugs e difícil de confiar se deve em grande parte a restrições capitalistas, mas ainda assim dá para fazer melhor.

  • Fico curioso para saber se o Ladybird vai aparecer no Web Engines Hackfest deste ano.

  • Mudando um pouco de assunto, fico curioso sobre o que aconteceu com os vídeos de hacking no YouTube.
    Antes eu ficava esperando os vídeos novos, mas acho que não vejo um há um bom tempo.

    • Para ser sincero, depois de publicar bem mais de 1000 vídeos, fiquei um pouco esgotado.
      Ainda publico vídeos mensais de atualização, mas já se passaram alguns meses desde o último vídeo de hacking.
      Mesmo assim, continuo trabalhando no Ladybird todos os dias e, graças ao patrocínio generoso da Shopify e de outros lugares no ano passado, agora também gerencio dois engenheiros em tempo integral.