- 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
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.
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.
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.
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.
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.
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.
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.