- Ladybird verarbeitet normal aufgebaute Webinhalte bis zu einem gewissen Grad, doch beim Einsatz von Google Project Zeros DOM-Fuzzer Domato traten schnell verborgene Edge Cases der Browser-Engine zutage
- In realistisch möglichen, aber ungewöhnlichen Eingaben wie per JavaScript erzeugten DOMs, die Parser-Regeln umgehen, Dokumenten ohne window und zirkulären SVG-Referenzen wurden fünf echte Bugs gefunden und behoben
- Implizite interne Annahmen in der Implementierung führten zu Crashes oder Endlosschleifen, etwa die Annahme eines table-Vorfahren von ``, die Annahme eines window in
DOMParser-Dokumenten oder eine fehlerhafte Geschwistersuche in Element.before()
- Das Problem beim Zugriff auf
contentWindow eines entfernten iframe war nicht nur ein Ladybird-Fehler, sondern hing auch mit einer Annahme der HTML-Spezifikation zu browsing context zusammen und führte zu einem WHATWG-HTML-Issue
- Fuzzer wie Domato legen Sicherheits- und Stabilitätsprobleme offen, die sich mit Tests normaler Webseiten nur schwer finden lassen; Ladybirds nächste Aufgabe ist es, so weit zu stabilisieren, dass kontinuierliches Fuzzing durchgehalten und anschließend automatisiert ausgeführt werden kann
Stresstest für Ladybird mit Domato
- Ladybird verarbeitet gut strukturierte Webinhalte bis zu einem gewissen Grad; hier wurde geprüft, welche Probleme auftreten, wenn ein Security-Research-Tool ungewöhnliche Eingaben liefert
- Verwendet wurde der DOM-Fuzzer Domato von Google Project Zero
- Domato erzeugt zufällige Webseiten aus einer Mischung von größtenteils validem, aber ungewöhnlichem HTML, CSS und JavaScript
- Die erzeugten Seiten wurden in einen Debug-Build von Ladybird geladen und ihr Verhalten beobachtet
- Da das Domato-README mit vielen in großen Browsern gefundenen Bugs wirbt, lag die Annahme nahe, dass sich auch in Ladybird relevante Defekte finden lassen
Null-Pointer-Dereferenzierung, wenn in steht
- Das erste Problem wurde in weniger als einer Sekunde gefunden; die 562 KiB große Domato-Ausgabe ließ sich auf die folgende Form reduzieren
let mfrac = document.createElement("mfrac");
mfrac.appendChild(document.createElement("th"));
document.body.appendChild(mfrac);
- In einem Ladybird-Build mit aktiviertem UBSAN verursachte der Aufruf von
table_containing_cell in HTMLTableCellElement.cpp eine Null-Pointer-Dereferenzierung
- Die Ursache lag darin, dass Ladybirds Implementierungen von
und davon ausgingen, dass weiter oben im DOM-Baum immer ein `` vorhanden ist
- Der HTML-Parser erlaubt Markup wie `` nicht
- Ein spezifikationskonformer Browser erzeugt beim Laden des obigen Markups ein einzelnes `` mit leerem Inhalt
- Erstellt man Knoten jedoch direkt über die JavaScript-DOM-API, lassen sich Teile der Parser-Regeln umgehen und ein
in ein einfügen
- Der problematische Code diente dazu, das alte Verhalten von
und umzusetzen, bei dem CSS-border und padding nicht nur auf die Tabellenbox, sondern auch auf jede Zelle angewendet werden
- Die Korrektur entfernte die Annahme, dass
und immer einen ``-Vorfahren haben
- Statt
table_containing_cell(*this) wird first_ancestor_of_type() verwendet
- Wenn es keinen Tabellen-Vorfahren gibt, wird sofort zurückgegeben
- Der Fix-Commit ist hier zu finden
Zuweisung eines ``-Event-Handlers in einem Dokument ohne window
- Das zweite Problem wurde ebenfalls innerhalb von weniger als einer Sekunde gefunden; die 472 KiB große Domato-Ausgabe wurde auf folgenden Code reduziert
var parser = new DOMParser();
var doc = parser.parseFromString("", "text/html");
var body = doc.createElement("body");
body.onblur = null;
- Ladybird brach wegen eines fehlgeschlagenen
GCPtr-Checks ab
- Der Kernpunkt ist das Sonderverhalten der
onfoo-Event-Handler-Attribute von ``
- Aus Kompatibilitätsgründen mit altem Webcontent muss eine Zuweisung an
document.body.onfoo an window.onfoo weitergereicht werden
- Ein mit
DOMParser erzeugtes Dokument hat jedoch kein window-Objekt
- Ladybirds internes Objektmodell war fälschlicherweise so strukturiert, dass jedes document immer ein window besitzt
- Nach der Korrektur gibt
Document::window() einen nullable Wert zurück, und an mehreren Stellen wird null behandelt
- Weist man
document.body.onblur in einem Dokument ohne window zu, passiert wie in anderen Browsern einfach nichts
Zirkuläre Referenz in SVG-``
- Das dritte Problem war eine unendliche Rekursion, die auftrat, wenn ein SVG-Gradient auf sich selbst verwies
- SVG muss sowohl Inline-SVG innerhalb von HTML als auch externe Bildformate unterstützen; Gradients können auf andere Gradients verweisen und Farben von ihnen erben
- Ladybirds Implementierung berücksichtigte nicht, dass ein Gradient auf sich selbst verweisen kann, und lief beim Verfolgen der Referenzkette immer weiter in einer Schleife
- Wenn man nur den direkten Selbstverweis verhindert, werden zirkuläre Referenzen über mehrere Stufen nicht behandelt
- Die richtige Behandlung besteht darin, alle besuchten Gradients zu verfolgen und das Verfolgen der Kette abzubrechen, sobald ein bereits besuchter Gradient erneut erreicht wird
- Firefox beschwert sich bei dieser Art von Gradient in der Entwicklerkonsole
Zugriff auf window-Eigenschaften eines entfernten iframe und ein Bug in der HTML-Spezifikation
- Das vierte Problem trat auf, wenn nach dem Entfernen eines iframe
getSelection() auf dem zuvor gespeicherten contentWindow aufgerufen wurde
window.onload = function() {
let iframe = document.querySelector("iframe")
let iframeWindow = iframe.contentWindow;
iframe.remove();
iframeWindow.getSelection();
}
- Ladybird meldete in
WindowProxy.cpp einen Laufzeitfehler durch Bindung einer Null-Pointer-Referenz auf BrowsingContext
- Wird ein iframe aus dem DOM entfernt, wird sein content document von seinem browsing context getrennt
- Beim Lesen oder Setzen von Eigenschaften eines window-Objekts wird der HTML-Spezifikationsalgorithmus
"check if an access between two browsing contexts should be reported" ausgeführt
- Dieser Algorithmus prüft die browsing contexts des zugreifenden window und des Ziel-window
- Die Spezifikation geht fälschlicherweise davon aus, dass beide window-Objekte zum Zeitpunkt des Eigenschaftszugriffs über einen verbundenen browsing context verfügen
- Für die HTML-Spezifikation wurde ein Issue eröffnet; in Ladybird wurde zunächst ein Null-Check ergänzt
- Wenn bei der Arbeit an Ladybird Spezifikations-Bugs gefunden werden, kann man durch Bugreports oder Änderungsvorschläge die Spezifikation für alle verbessern
Endlosschleife in Element.before()
- Das fünfte Problem zeigte sich dadurch, dass das Laden der Seite nie endete und die CPU zu 100 % ausgelastet war
two.before(one);
- Die Ursache war ein Fehler in der Logik der
before()-Implementierung, die unter den vorherigen Geschwistern von `` das erste Geschwister finden sollte, das nicht in den Argumenten enthalten ist
- Die bestehende Schleife holte jedes Mal erneut
node->previous_sibling()
while (auto previous_sibling = node->previous_sibling()) {
// check if previous_sibling is one of the arguments
}
- Tatsächlich musste sie entlang der Geschwisterkette weiterlaufen und jeweils mit
previous_sibling->previous_sibling() fortfahren
for (auto sibling = node->previous_sibling(); sibling; sibling = sibling->previous_sibling()) {
// check if previous_sibling is one of the arguments
}
Fuzzing-Ergebnisse und nächste Schritte
- In dieser Session wurden fünf echte Bugs gefunden; einer davon war ein Bug in der HTML-Spezifikation, und alle wurden behoben
- Es zeigte sich, dass Ladybird bei ungewöhnlichen und unerwarteten Eingaben sehr schnell zusammenbricht
- Fuzzer wie Domato sind eine nützliche Ressource für alle, die Software robuster machen wollen
- Der nächste Schritt besteht darin, Ladybird so weit zu stabilisieren, dass es kontinuierliche Fuzzing-Eingaben aushält
- Sobald die Stabilität ausreicht, soll es irgendwo in der Cloud automatisch ausgeführt werden, um weitere Probleme zu finden
1 Kommentare
Hacker-News-Kommentare
Zeigt sehr gut, warum mehrere unabhängige Implementierungen einer Spezifikation wertvoll sind.
Allein durch diesen Artikel wurde eine Lücke in der Spezifikation gefunden, und es gab vermutlich weitere oder es werden künftig noch weitere auftauchen.
Für die langfristige Gesundheit der Web-Plattform sind mehrere unabhängige Implementierungen wichtig, und wir versuchen, genau diese Rolle zu übernehmen.
Es ist etwa so, als würde ich twittern: „Aubergine ist mein Lieblingsgemüse“, jemand korrigiert mich sofort mit „eigentlich eine Frucht“, und man würde daraus schließen: „Der Wert von Twitter ist bewiesen.“
Das heißt nicht, dass diese Arbeit oder mehrere Implementierungen einer Spezifikation keinen Wert hätten, aber ich finde, dass diese konkrete Beispiel allein diese Implikation noch nicht trägt.
Mir gefällt, dass dieses Projekt immer wieder zeigt, dass auch kleine Teams Erstaunliches bauen können.
In einem Unternehmen mit vielen Stakeholdern wäre es vermutlich viel schwieriger gewesen, so etwas hinzubekommen.
Bei einem Hobbyprojekt kann man jederzeit zurückgehen und Dinge neu bauen, aber bei manchen dieser Dinge werde ich das Gefühl nicht los, dass sie von Anfang an in die Architektur hätten einfließen müssen.
Haben sie SVG schon implementiert? Ich verfolge das mit Interesse, weil es viel schneller vorankommt, als ich erwartet hätte.
Insbesondere Animationen sind noch eine große Lücke.
Bei Issue #3 scheint es sinnvoll, auch eine maximale Tiefenbegrenzung für Gradienten einzuführen, die auf andere Gradienten verweisen.
Das wäre Defense in Depth für den Fall, dass die Logik „Haben wir diese Referenz schon gesehen?“ Fehler oder Grenzen hat.
Ich kenne mich mit SVG-Gradienten nicht aus; vielleicht gibt es legitime Gründe, Referenzketten mit jeweils 1000 Einträgen zu haben, aber wenn man so etwas in freier Wildbahn sieht, ist es meiner Meinung nach höchstwahrscheinlich ein Angriff oder Fuzzer-Input.
Ich schreibe diesen Kommentar in Ladybird.
Hacker News funktioniert jetzt in Ladybird.
Ich nutze Ladybird täglich ein paar Minuten, um Seiten wie Hacker News oder OSNews anzuschauen.
Es ist langsam und fragil, aber es funktioniert. Wenn man bedenkt, wie jung das Projekt ist und dass buchstäblich alles von Grund auf neu geschrieben wurde, ist das allein schon beeindruckend.
Ich freue mich wirklich darauf, dass Ladybird reifer wird.
Interessant, aber mich stört, dass fast alle Entwickler bei Dingen wie Issue #1 einfach mit „Gefunden! Fix committet, fertig!“ aufhören.
Das sollte man nicht tun; man muss verstehen, was genau falsch war. Wenn zum Beispiel die Annahme „ein Parent existiert immer“ das Problem war, sollte man in der gesamten Codebase nach derselben Art von Fehler suchen.
Man muss kreativ werden und herausfinden, wo dasselbe noch passieren könnte. Es ist nie nur an einer Stelle.
Dass moderne Software ein schwer vertrauenswürdiger, bugverseuchter Albtraum ist, liegt größtenteils an kapitalistischen Zwängen, aber wir können es trotzdem besser machen.
Ich frage mich, ob Ladybird dieses Jahr beim Web Engines Hackfest vertreten sein wird.
Etwas anderes Thema, aber ich frage mich, was aus den Hacking-Videos auf YouTube geworden ist.
Früher habe ich auf neue Videos gewartet, aber ich habe schon länger keines mehr gesehen.
Monatliche Update-Videos lade ich immer noch hoch, aber seit dem letzten Hacking-Video sind ein paar Monate vergangen.
Ich arbeite trotzdem jeden Tag an Ladybird und manage dank der großzügigen Unterstützung von Shopify und anderen im vergangenen Jahr inzwischen auch zwei Vollzeit-Engineers.