- Ladybird puede manejar contenido web normal hasta cierto punto, pero al ejecutar Domato, el fuzzer DOM de Google Project Zero, los casos límite ocultos del motor del navegador aparecieron rápidamente
- En entradas anómalas pero realistas como DOM creados con JavaScript para esquivar reglas del parser, documentos sin window y referencias SVG circulares, se encontraron y corrigieron 5 bugs reales
- Suposiciones implícitas dentro de la implementación, como asumir un ancestro table para ``, asumir window en documentos de
DOMParser o un error al recorrer hermanos en Element.before(), terminaban en crashes o bucles infinitos
- El problema de acceder a
contentWindow de un iframe eliminado no era solo una falla de Ladybird, sino que también estaba ligado a una suposición de la especificación HTML sobre browsing context, lo que derivó en un issue de WHATWG HTML
- Fuzzers como Domato exponen problemas de seguridad y estabilidad que son difíciles de detectar probando solo páginas web normales, y la siguiente tarea de Ladybird es estabilizarse lo suficiente como para soportar fuzzing continuo y luego ejecutarlo automáticamente
Prueba de estrés de Ladybird con Domato
- Ladybird puede manejar contenido web bien formado hasta cierto punto, pero aquí se probó qué pasa al lanzarle entradas extrañas usando una herramienta de investigación de seguridad
- La herramienta usada fue Domato, el fuzzer DOM de Google Project Zero
- Domato genera páginas web aleatorias con HTML, CSS y JavaScript mayormente válidos, pero extraños
- Esas páginas generadas se cargan en una build de depuración de Ladybird y se observa su comportamiento
- Como el README de Domato destaca muchos bugs encontrados en navegadores importantes, se consideró probable que también pudiera hallar fallas relevantes en Ladybird
Desreferencia de puntero nulo cuando está dentro de
- El primer problema apareció en menos de 1 segundo, y una salida de Domato de 562 KiB pudo reducirse a esta forma
let mfrac = document.createElement("mfrac");
mfrac.appendChild(document.createElement("th"));
document.body.appendChild(mfrac);
- En una build de Ladybird con UBSAN activado, la llamada a
table_containing_cell en HTMLTableCellElement.cpp provocó una desreferencia de puntero nulo
- La causa fue que las implementaciones de Ladybird para
y asumían que siempre había un `` más arriba en el árbol DOM
- El parser HTML no permite marcado como ``
- Si un navegador que sigue la especificación carga ese marcado, crea un `` con contenido interno vacío
- Pero si los nodos se crean directamente con la API DOM de JavaScript, se pueden esquivar algunas reglas del parser y meter
dentro de
- El código problemático se usaba para implementar un comportamiento antiguo donde
y aplican border y padding CSS no solo a la caja de la tabla, sino también a cada celda
- La corrección consistió en eliminar la suposición de que
y siempre tienen un ancestro ``
- En lugar de
table_containing_cell(*this), se usa first_ancestor_of_type()
- Si no hay ancestro de tabla, se retorna de inmediato
- El commit de la corrección está aquí
Asignación de manejador de evento `` en documentos sin window
- El segundo problema también apareció en menos de 1 segundo, y una salida de Domato de 472 KiB se redujo a este código
var parser = new DOMParser();
var doc = parser.parseFromString("", "text/html");
var body = doc.createElement("body");
body.onblur = null;
- Ladybird se detenía por una falla de validación de
GCPtr
- El punto clave era el comportamiento especial de las propiedades de manejadores de evento
onfoo de ``
- Por compatibilidad con contenido web antiguo, una asignación a
document.body.onfoo debe redirigirse a window.onfoo
- Pero los documentos creados con
DOMParser no tienen objeto window
- El modelo interno de objetos de Ladybird estaba mal estructurado al asumir que todo document siempre tiene window
- Tras la corrección,
Document::window() devuelve un valor nullable y varios puntos ahora manejan null
- Al asignar
document.body.onblur en un documento sin window, no pasa nada, igual que en otros navegadores
Referencia circular en SVG ``
- El tercer problema fue una recursión infinita cuando un gradiente SVG se referenciaba a sí mismo
- SVG debe soportar tanto SVG inline dentro de HTML como el formato de imagen externo, y un gradiente puede referenciar a otro para heredar colores
- La implementación de Ladybird no contemplaba el caso en que un gradiente se referenciara a sí mismo, así que seguía la cadena de referencias y entraba en bucle indefinidamente
- Bloquear solo el caso de autorreferencia directa no resuelve referencias circulares de varios pasos
- La forma correcta de manejarlo es llevar registro de todos los gradientes visitados y detener el seguimiento de la cadena cuando se vuelve a encontrar uno ya visitado
- Firefox muestra una advertencia en la consola de desarrollador para este tipo de gradientes
Acceso a propiedades window de un iframe eliminado y bug en la especificación HTML
- El cuarto problema ocurría al llamar a
getSelection() sobre un contentWindow guardado previamente, después de eliminar el iframe
window.onload = function() {
let iframe = document.querySelector("iframe")
let iframeWindow = iframe.contentWindow;
iframe.remove();
iframeWindow.getSelection();
}
- Ladybird lanzaba un error de runtime por enlazar una referencia de puntero nulo a
BrowsingContext en WindowProxy.cpp
- Cuando un iframe se elimina del DOM, su content document se desacopla de su propio browsing context
- Al obtener o asignar propiedades del objeto window, se ejecuta el algoritmo de la especificación HTML
"check if an access between two browsing contexts should be reported"
- Ese algoritmo inspecciona el browsing context de la window que accede y de la window a la que se accede
- La especificación asume incorrectamente que ambas window tienen un browsing context conectado al momento del acceso a la propiedad
- Se abrió un issue para la especificación HTML, y en Ladybird se añadió mientras tanto una verificación de null
- Cuando durante el trabajo en Ladybird se encuentran bugs en la especificación, se puede mejorar para todos mediante reportes o propuestas de corrección
Bucle infinito en Element.before()
- El quinto problema se manifestaba como una carga de página que nunca terminaba y uso de CPU al 100%
two.before(one);
- La causa fue un error en la lógica de
before() que busca, entre los hermanos anteriores de ``, el primer hermano que no esté incluido en los argumentos
- El bucle original volvía a obtener
node->previous_sibling() en cada iteración
while (auto previous_sibling = node->previous_sibling()) {
// check if previous_sibling is one of the arguments
}
- En realidad, debía avanzar por la cadena de hermanos usando
previous_sibling->previous_sibling() continuamente
for (auto sibling = node->previous_sibling(); sibling; sibling = sibling->previous_sibling()) {
// check if previous_sibling is one of the arguments
}
Resultados del fuzzing y siguientes pasos
- En esta sesión se encontraron 5 bugs reales; uno de ellos era un bug de la especificación HTML, y todos fueron corregidos
- Quedó claro que Ladybird se rompe muy rápido cuando se enfrenta a entradas raras e inesperadas
- Fuzzers como Domato son un recurso útil para cualquiera que quiera hacer software más robusto
- El siguiente paso es estabilizar Ladybird hasta el punto de que pueda soportar entradas de fuzzing continuas
- Cuando esté lo bastante estable, el plan es ejecutarlo automáticamente en la nube para encontrar todavía más problemas
1 comentarios
Opiniones de Hacker News
Muestra muy bien por qué son valiosas las múltiples implementaciones independientes de una especificación.
Solo con este artículo ya se encontró un hueco en la especificación, y parece que hubo más o que seguirán apareciendo.
Para la salud a largo plazo de la plataforma web son importantes varias implementaciones independientes, así que nosotros también estamos intentando cumplir ese papel.
Por ejemplo, es como si tuiteara “la berenjena es mi verdura favorita”, alguien me corrigiera diciendo “en realidad es una fruta”, y luego yo dijera que eso “demuestra el valor de Twitter”.
No quiero decir que este trabajo en sí, ni que tener varias implementaciones de una especificación, carezcan de valor, pero creo que con este ejemplo concreto todavía no se sostiene esa implicación.
Me gusta que este proyecto siga demostrando que un equipo pequeño también puede crear cosas sorprendentes.
Creo que dentro de una empresa con muchas partes interesadas habría sido mucho más difícil lograr algo así.
Si fuera un proyecto hobby, siempre se puede volver atrás y rehacerlo, pero es difícil quitarse la sensación de que algunas de estas cosas deberían haber estado en la arquitectura desde el principio.
¿Ya implementaron SVG? Me parece interesante seguirlo porque está avanzando mucho más rápido de lo que esperaba.
En particular, la animación es una gran parte pendiente.
Para el issue #3, también parece buena idea poner un límite máximo de profundidad a los gradientes que apuntan a otros gradientes.
Podría servir como defensa en profundidad ante errores o límites en la lógica de “¿ya vi esta referencia antes?”.
No conozco bien los gradientes SVG, y tal vez podría haber una razón legítima para que haya cadenas de referencias de 1000 elementos, pero si uno ve algo así en un entorno real, creo que lo más probable es que sea un ataque o una entrada de fuzzer.
Estoy escribiendo este comentario desde Ladybird.
Hacker News ya funciona en Ladybird.
Uso Ladybird unos minutos al día para navegar sitios como Hacker News u OSnews.
Es lento y frágil, pero funciona. Considerando que el proyecto es tan joven y que literalmente todo se escribió desde cero, eso ya es impresionante.
Tengo muchas ganas de ver a Ladybird madurar.
Es interesante, pero me molesta que casi todos los desarrolladores terminen como se ve en el issue #1: “¡lo encontré! hice el commit con el fix, listo”.
No debería ser así: hay que entender exactamente qué salió mal. Por ejemplo, si el problema fue asumir que “el padre siempre existe”, entonces hay que buscar en todo el codebase errores del mismo tipo.
Hay que usar la creatividad para encontrar en qué otros lugares puede volver a pasar lo mismo. Nunca está en un solo lugar.
Que el software moderno sea una pesadilla llena de bugs y difícil de confiar se debe en gran medida a restricciones capitalistas, pero aun así podemos hacerlo mejor.
Me pregunto si Ladybird aparecerá en el Web Engines Hackfest de este año.
Cambiando un poco de tema, me pregunto qué pasó con los videos de hacking de YouTube.
Antes esperaba los videos nuevos, pero siento que hace tiempo que no veo ninguno.
Todavía subo videos mensuales de actualización, pero ya pasaron varios meses desde el último video de hacking.
Aun así, trabajo en Ladybird todos los días y, gracias al generoso patrocinio de Shopify y otros el año pasado, ahora también estoy gestionando a dos ingenieros de tiempo completo.