- Ladybird traite correctement jusqu’à un certain point du contenu web normal, mais l’exécution de Domato, le fuzzer DOM de Google Project Zero, a rapidement révélé des cas limites cachés dans le moteur du navigateur
- Cinq vrais bugs ont été trouvés et corrigés avec des entrées anormales réalistes, comme un DOM créé en JavaScript en contournant les règles du parseur, des documents sans window, ou des références SVG circulaires
- Des hypothèses implicites internes, comme supposer un ancêtre table pour ``, une window dans un document
DOMParser, ou une erreur de parcours des frères dans Element.before(), ont conduit à des crashs ou à des boucles infinies
- Le problème d’accès à
contentWindow sur une iframe supprimée n’était pas seulement un défaut propre à Ladybird : il touchait aussi aux hypothèses de la spécification HTML sur le browsing context, ce qui a débouché sur une issue WHATWG HTML
- Les fuzzers comme Domato exposent des problèmes de sécurité et de stabilité difficiles à détecter avec de simples tests de pages web normales ; la prochaine étape pour Ladybird est de se stabiliser assez pour supporter un fuzzing continu, puis de l’automatiser
Stress tester Ladybird avec Domato
- Ladybird traite assez bien le contenu web bien formé, mais l’objectif était de voir quels problèmes apparaissent en lui injectant des entrées étranges avec un outil de recherche en sécurité
- L’outil utilisé est Domato, le fuzzer DOM de Google Project Zero
- Domato génère des pages web aléatoires mêlant du HTML, CSS et JavaScript pour l’essentiel valides, mais étranges
- Les pages générées ont été chargées dans un build de débogage de Ladybird, puis leur comportement a été observé
- Comme le README de Domato met en avant les nombreux bugs trouvés dans les principaux navigateurs, il semblait probable de trouver aussi des défauts significatifs dans Ladybird
Déréférencement de pointeur nul quand est dans
- Le premier problème a été découvert en moins d’une seconde, et une sortie Domato de 562 Kio a pu être réduite à la forme suivante
let mfrac = document.createElement("mfrac");
mfrac.appendChild(document.createElement("th"));
document.body.appendChild(mfrac);
- Dans un build de Ladybird avec UBSAN activé, l’appel à
table_containing_cell dans HTMLTableCellElement.cpp provoquait un déréférencement de pointeur nul
- La cause venait du fait que les implémentations Ladybird de
et supposaient qu’il existait toujours un `` plus haut dans l’arbre DOM
- Le parseur HTML n’autorise pas un balisage comme ``
- Un navigateur conforme à la spécification, lorsqu’il charge le balisage ci-dessus, crée un seul `` vide à l’intérieur
- Mais en créant directement les nœuds via l’API DOM JavaScript, il est possible de contourner une partie des règles du parseur et de placer un
dans un
- Le code en cause servait à implémenter un ancien comportement où
et appliquent les bordures et le padding CSS non seulement aux boîtes de table, mais aussi à chaque cellule
- Le correctif a consisté à supprimer l’hypothèse selon laquelle
et ont toujours un ancêtre ``
first_ancestor_of_type() est utilisé à la place de table_containing_cell(*this)
- S’il n’y a pas d’ancêtre table, la fonction retourne immédiatement
- Le commit de correction est ici
Affectation d’un gestionnaire d’événement `` dans un document sans window
- Le deuxième problème a lui aussi été découvert en moins d’une seconde, et une sortie Domato de 472 Kio a été réduite au code suivant
var parser = new DOMParser();
var doc = parser.parseFromString("", "text/html");
var body = doc.createElement("body");
body.onblur = null;
- Ladybird s’arrêtait sur un échec de vérification
GCPtr
- Le point clé est le comportement spécial des attributs de gestionnaires d’événements
onfoo de ``
- Pour la compatibilité avec les anciens contenus web, une affectation à
document.body.onfoo doit être transmise à window.onfoo
- Mais un document créé avec
DOMParser n’a pas d’objet window
- Le modèle d’objets interne de Ladybird était structuré à tort comme si tout document avait toujours une window
- Après correction,
Document::window() renvoie une valeur nullable, et le cas null est traité à plusieurs endroits
- Dans un document sans window, affecter
document.body.onblur ne fait rien, comme dans les autres navigateurs
Référence circulaire dans un `` SVG
- Le troisième problème était une récursion infinie lorsqu’un dégradé SVG se référençait lui-même
- SVG doit prendre en charge à la fois les SVG inline dans du HTML et le format d’image externe, et un dégradé peut référencer un autre dégradé pour en hériter les couleurs
- L’implémentation de Ladybird ne tenait pas compte du cas où un dégradé se référence lui-même, et suivait la chaîne de références en bouclant indéfiniment
- Bloquer uniquement le cas d’une référence à soi-même ne permet pas de traiter les références circulaires en plusieurs étapes
- Le bon traitement consiste à suivre tous les dégradés visités et, lorsqu’un dégradé déjà visité est rencontré à nouveau, à interrompre le suivi de la chaîne
- Firefox affiche une plainte dans la console développeur pour ce type de dégradé
Accès à la propriété window d’une iframe supprimée et bug de la spécification HTML
- Le quatrième problème se produisait lorsqu’on supprimait une iframe, puis appelait
getSelection() sur le contentWindow conservé auparavant
window.onload = function() {
let iframe = document.querySelector("iframe")
let iframeWindow = iframe.contentWindow;
iframe.remove();
iframeWindow.getSelection();
}
- Ladybird émettait une erreur d’exécution de liaison de référence à pointeur nul vers
BrowsingContext dans WindowProxy.cpp
- Quand une iframe est supprimée du DOM, son content document est détaché de son browsing context
- Lorsqu’on lit ou écrit une propriété de l’objet window, l’algorithme de la spécification HTML
"check if an access between two browsing contexts should be reported" est exécuté
- Cet algorithme examine le browsing context de la window qui accède et celui de la window cible
- La spécification suppose à tort qu’au moment de l’accès à la propriété, les deux windows ont un browsing context connecté
- Une issue a été ouverte pour la spécification HTML, et un contrôle null a d’abord été ajouté à Ladybird
- Lorsqu’un bug de spécification est trouvé en travaillant sur Ladybird, il est possible d’améliorer la spécification pour tout le monde au moyen d’un rapport de bug ou d’une proposition de correctif
Boucle infinie dans Element.before()
- Le cinquième problème se manifestait par une page qui ne finissait pas de charger et utilisait 100 % du CPU
two.before(one);
- La cause était une erreur dans la logique de l’implémentation de
before(), qui cherche le premier frère précédent de `` ne faisant pas partie des arguments
- La boucle existante relisait
node->previous_sibling() à chaque itération
while (auto previous_sibling = node->previous_sibling()) {
// check if previous_sibling is one of the arguments
}
- En réalité, il fallait parcourir la chaîne des frères en continuant avec
previous_sibling->previous_sibling()
for (auto sibling = node->previous_sibling(); sibling; sibling = sibling->previous_sibling()) {
// check if previous_sibling is one of the arguments
}
Résultats du fuzzing et prochaines étapes
- Cette session a permis de trouver cinq vrais bugs, dont un bug de la spécification HTML, et tous ont été corrigés
- Elle a montré que Ladybird s’effondre très vite face à des entrées étranges et inattendues
- Les fuzzers comme Domato sont une ressource utile pour quiconque veut rendre un logiciel plus robuste
- La prochaine étape consiste à stabiliser Ladybird jusqu’à ce qu’il puisse supporter des entrées de fuzzing en continu
- Une fois suffisamment stabilisé, il est prévu de l’exécuter automatiquement quelque part dans le cloud afin de trouver davantage de problèmes
1 commentaires
Commentaires sur Hacker News
Cela montre bien pourquoi il est précieux d’avoir plusieurs implémentations indépendantes d’une spécification.
Rien qu’avec cet article, une faille dans la spécification a été trouvée, et il y en avait probablement d’autres, ou il y en aura encore.
Pour la santé à long terme de la plateforme web, plusieurs implémentations indépendantes sont importantes, et nous essayons nous aussi de jouer ce rôle.
C’est un peu comme si je tweetais « l’aubergine est mon légume préféré », que quelqu’un me corrigeait en disant « en fait, c’est un fruit », puis que j’en concluais que « la valeur de Twitter est prouvée ».
Cela ne veut pas dire que ce travail en soi, ni le fait d’avoir plusieurs implémentations d’une spécification, n’ont pas de valeur, mais je ne pense pas que cette implication tienne encore à partir de ce seul exemple.
J’aime que ce projet continue de montrer qu’une petite équipe peut créer des choses étonnantes.
J’imagine qu’il aurait été bien plus difficile de faire ce genre de travail au sein d’une entreprise avec de nombreuses parties prenantes.
Pour un projet amateur, on peut toujours revenir en arrière et refaire les choses, mais j’ai du mal à ne pas penser que certaines de ces considérations auraient dû être intégrées à l’architecture dès le départ.
Ils ont déjà implémenté SVG ? Je suis le projet avec intérêt, il avance beaucoup plus vite que je ne l’aurais pensé.
Les animations, en particulier, constituent un gros manque.
Pour l’issue #3, il me semblerait aussi judicieux de fixer une limite de profondeur maximale pour les dégradés qui pointent vers d’autres dégradés.
Cela ferait une défense en profondeur contre les erreurs ou limites de la logique du type « ai-je déjà vu cette référence ? ».
Je ne connais pas bien les dégradés SVG, et il existe peut-être une raison légitime d’avoir des chaînes de 1 000 références, mais dans un environnement réel, si je voyais ça, je penserais surtout à une attaque ou à une entrée de fuzzer.
J’écris ce commentaire dans Ladybird.
Hacker News fonctionne maintenant dans Ladybird.
J’utilise Ladybird quand je parcours quelques minutes par jour des sites comme Hacker News ou OSnews.
C’est lent et fragile, mais ça fonctionne. Vu à quel point le projet est jeune, et le fait que littéralement tout ait été écrit à partir de zéro, c’est déjà impressionnant.
J’ai vraiment hâte de voir Ladybird mûrir.
C’est intéressant, mais ça m’agace que presque tous les développeurs s’arrêtent à « trouvé ! commit de correction, terminé ! », comme on le voit dans l’issue #1.
Il ne faut pas faire ça : il faut comprendre exactement ce qui n’allait pas. Par exemple, si le problème était l’hypothèse selon laquelle « le parent existe forcément », il faut chercher le même type d’erreur dans toute la codebase.
Il faut faire preuve d’imagination pour trouver où la même chose peut encore se produire. Ce n’est jamais à un seul endroit.
Si le logiciel moderne est un cauchemar plein de bugs auquel il est difficile de faire confiance, c’est en grande partie à cause de contraintes capitalistes, mais on peut tout de même faire mieux.
Je me demande si Ladybird sera présent au Web Engines Hackfest cette année.
C’est un peu hors sujet, mais je me demande ce qu’il est advenu des vidéos de hacking sur YouTube.
Avant, j’attendais les nouvelles vidéos, mais j’ai l’impression de ne plus en avoir vu depuis un moment.
Je publie toujours les vidéos de mise à jour mensuelles, mais plusieurs mois se sont écoulés depuis la dernière vidéo de hacking.
Cela dit, je travaille toujours sur Ladybird tous les jours, et grâce au généreux sponsoring de Shopify et d’autres l’an dernier, je gère maintenant aussi deux ingénieurs à plein temps.