How Domato Fuzzes the Chromium Blink Engine

This article provides a technical breakdown of how the Domato fuzzer generates grammar-based HTML, CSS, and JavaScript inputs to stress-test the Blink rendering engine in the Chromium web browser. Developed by Google Project Zero, Domato bypasses early syntax rejection by using structured context-free grammars to create syntactically valid, yet semantically extreme, web documents. By combining complex initial Document Object Model (DOM) states with rapid, chaotic JavaScript mutations, Domato exposes deep memory corruption and logic vulnerabilities within Blink.

The Need for Grammar-Based Generation

Traditional mutation-based fuzzers operate by flipping random bits in existing files. When applied to web browsers, this approach fails because the Blink engine's parsers immediately discard syntactically invalid HTML or JavaScript before the input ever reaches the core rendering logic.

Domato solves this through generation-based fuzzing driven by separate, customizable grammar files for HTML, CSS, and JavaScript. These grammars consist of production rules that define the syntax of each language. A rule consists of a non-terminal symbol (representing a specific type of object or value) and its possible expansions. When Domato runs, it recursively resolves these symbols into concrete code, ensuring the output passes Blink's initial parsing stage and executes deep within the engine.

Constructing the Initial DOM State (HTML and CSS)

To stress-test Blink, Domato first establishes a complex baseline environment using its HTML and CSS grammars.

  1. HTML Tree Generation: Domato selects random combinations of standard, deprecated, and obscure HTML tags from its grammar. It nests these elements deeply and assigns them unique id attributes (e.g., id="id_1", id="id_2"). These identifiers are crucial because they allow the JavaScript engine to reference and manipulate these specific nodes later.
  2. Attribute Overload: For each generated HTML tag, the fuzzer applies a variety of attributes populated with edge-case values, such as massive integers, negative values, unexpected strings, or empty parameters.
  3. CSS Complexity: Simultaneously, Domato uses its CSS grammar to generate intricate styling rules. It combines complex selectors with conflicting layout properties—such as mixing flexbox, grid, and table layouts, or applying extreme transformation matrices. This forces Blink’s style and layout engines to perform complex, non-standard calculations before a single line of JavaScript is even executed.

Dynamic Mutation via JavaScript

Static HTML and CSS rarely trigger severe vulnerabilities on their own. The core strength of Domato lies in how it uses JavaScript to violently mutate the DOM tree while Blink is actively trying to render it.

Domato’s JavaScript generation relies on stateful grammar tracking:

  • Variable and Type Tracking: As Domato generates JavaScript, it remembers the types of objects it creates. For example, if a grammar rule generates a command that returns an HTMLCanvasElement, Domato stores this in a variable and records its type. Subsequent grammar rules requiring an HTMLCanvasElement will dynamically pull from this pool of existing variables.
  • DOM Manipulation: The fuzzer generates thousands of lines of JavaScript that grab the HTML elements created earlier (via document.getElementById) and subject them to rapid changes. It appends, clones, removes, and replaces nodes across the DOM tree, breaking parent-child relationships unexpectedly.
  • API Fuzzing: Domato invokes methods and modifies properties on DOM nodes, BOM (Browser Object Model) APIs, and Web APIs using edge-case arguments defined in the grammar.
  • Error Suppression: Because random combinations of valid syntax will still frequently result in runtime exceptions (like calling a method on a null object), Domato wraps almost every generated JavaScript statement in a try { ... } catch(e) {} block. This ensures that a single runtime error does not halt the execution of the remaining fuzzing script.

The generated HTML, CSS, and JavaScript are combined into a single template file and fed into Chromium. This structured chaos stresses the Blink engine in specific ways:

  • Layout and Style Recalculation: By alternating between DOM tree modifications and calls that force synchronous layout updates (like querying offsetWidth or getComputedStyle), Domato forces Blink to constantly tear down and rebuild its internal LayoutTree and StyleEngine structures.
  • Garbage Collection Pressure: Domato rapidly creates and destroys references to DOM nodes. It also intentionally inserts garbage collection triggers into the JavaScript. This stresses the boundary between V8 (Chromium's JavaScript engine) and Oilpan (Blink's C++ garbage collector), frequently uncovering Use-After-Free (UAF) vulnerabilities where C++ code attempts to access a DOM node that was prematurely freed.
  • Event Handler Re-entrancy: Domato heavily utilizes event listeners (such as DOMSubtreeModified or onload). When the fuzzer mutates an element, it can trigger a synchronous event listener that executes more Domato-generated JavaScript in the middle of a C++ rendering operation. This violates assumptions made by Blink developers regarding object lifecycles and state consistency, leading to memory corruption and security crashes.