Fuzzilli Documentation

repository·main·Indexed 25 days ago

https://github.com/googleprojectzero/fuzzilli

A coverage-guided fuzzer for dynamic language interpreters, specifically JavaScript engines. It utilizes a custom intermediate language (FuzzIL) to perform mutations on control and data flow. The project includes support for building via Docker, scaling distributed fuzzing on Google Compute Engine (GCE) using a tree hierarchy of root, intermediate, and leaf nodes, and tools for crash triaging.

Tokens
18.6K
Snippets
37
Records
85
Agent score
81%

What's inside Fuzzilli

  1. What is FuzzIL and how does it work?

    main

    FuzzIL is a custom intermediate language that serves as the foundation for Fuzzilli. Instead of generating JavaScript directly, Fuzzilli operates exclusively on FuzzIL programs, which are only lifted to JavaScript at the final stage for execution.

    This approach ensures:

    • Syntactical Correctness: FuzzIL can only express code that is guaranteed to be syntactically valid JavaScript.
    • Semantic Correctness: It helps ensure variables are defined before use and facilitates meaningful mutations.
    • Ease of Lifting: It is designed to be easily converted into JavaScript by the JavaScriptLifter.

    FuzzIL programs are essentially lists of instructions where every instruction is an operation with input/output variables and optional parameters. Control flow is managed through 'blocks' (e.g., BeginIf, EndIf).

  2. Understand the limitations of the Fuzzilli Mutation Engine

    main

    The Fuzzilli MutationEngine samples from the universe of syntactically valid JavaScript programs by applying $N$ consecutive mutations to programs already in the corpus (programs that have triggered new coverage).

    Key Limitations:

    • Reachability: The engine struggles to find samples that are "far away" from the current corpus, as they require a highly specific, unlikely sequence of mutations to reach.
    • Complexity Bias: The engine is biased toward finding bugs that are "close" to existing corpus samples. It primarily relies on code coverage feedback, which rewards triggering new code areas but does not necessarily reward combining multiple distinct operations into a single complex dataflow.
    • Feature Combination: It may struggle to find vulnerabilities that require multiple different operations to be performed on related objects or triggering the same callback mechanism (e.g., a Proxy trap) in different contexts, as coverage guidance does not explicitly reward these combinations.

    Strategies to overcome these limitations:

    • Seeding with PoCs: Use the FuzzIL compiler to compile existing JavaScript proof-of-concept (PoC) code or regression tests into a FuzzIL corpus. This directs the fuzzer toward areas similar to known vulnerabilities.
    • HybridEngine/Code Generation: Use specialized CodeGenerators to create new programs from scratch that target specific bug types or engine components (e.g., targeting JIT compilation) rather than relying solely on mutation.
  3. Understand FixupMutator for code improvement

    main

    The FixupMutator is a runtime-assisted mutator designed to improve the quality and effectiveness of existing code. Its primary goal is to convert guarded operations into unguarded ones.

    Currently implemented capabilities include:

    • Removing unnecessary try-catch blocks and guards.

    Planned (work-in-progress) capabilities include:

    • Fixing accesses to non-existent properties and elements.
    • Fixing invalid function, method, or constructor calls.
    • Fixing arithmetic operations that result in NaN.
  4. Ensure Determinism in Coverage

    main

    Because modern JavaScript engines use background threads (JIT, GC) that can cause non-deterministic behavior, a sample might trigger an edge once but not on subsequent runs. To prevent non-deterministic samples from polluting the corpus and wasting mutation cycles, Fuzzilli ensures samples are deterministic by:

    1. Repeatedly executing the sample.
    2. Forming the intersection of all triggered edges.
    3. Continuing until the intersection of triggered edges becomes stable.

    When a crash occurs due to non-determinism, Fuzzilli includes the original failure message (e.g., assertion failure and stacktrace) and the exit code as a comment in the reproducer sample to assist in analysis.

  5. How to combine types using Union, Intersection, and Merge

    main

    FuzzIL uses three operators to model the complex and dynamic nature of JavaScript types:

    1. Union (|): Expresses that a variable is one of several types. Used for function parameters that accept multiple types (e.g., String.prototype.replace accepting string | RegExp) or for variables whose type changes due to conditional execution.
    2. Intersection (&): Computes the overlap between two types. Used to determine if a variable might possess a specific type (e.g., checking if a variable could be a BigInt to determine valid arithmetic operations).
    3. Merge (+): A unique operator used to model values that satisfy multiple roles simultaneously. A merged type t1 + t2 can be used whenever either t1 or t2 is required. This is essential for modeling JavaScript objects that are also primitives or functions.

    Examples of Merged Types:

    • String: .string + .object(...) + .array (can be used as a string, an object with properties, or an iterable).
    • Function: .function(...) + .object(...) (can be called and has properties).
    • Array: .array + .object(...) (is iterable and has properties).

    Note: You cannot merge union types (e.g., (t1 | t2) + (t3 | t4) is invalid/unsupported), but you can union merged types.

  6. How the Fuzzilli processing and threading model works

    main

    Fuzzilli uses a sequential threading model where every Fuzzer instance is associated with a specific DispatchQueue. All interactions with a fuzzer instance must occur on its associated queue to avoid race conditions.

    Key behaviors:

    • Sequential Processing: Work items are processed one after another on the fuzzer's queue, preventing concurrency issues.
    • Internal Execution: Code invoked directly by Fuzzilli (such as Mutators, Module initializers, CodeGenerators, or Event and Timer handlers) always executes on the fuzzer's dispatch queue. You do not need to manually enqueue these tasks.
    • External Execution: If you are writing code that runs on a separate thread or a different DispatchQueue (e.g., custom networking or threading logic), you must wrap your interactions with the fuzzer using the sync or async methods to ensure they are enqueued onto the fuzzer's queue.
    fuzzer.async {
        // Can now interact with the fuzzer         
        fuzzer.importProgram(someProgram)
    }
  7. Understand the Fuzzilli Docker image structure

    main

    The fuzzilli Docker image is optimized for size and does not contain source code or temporary build artifacts. It contains:

    • Fuzzilli binary: Located at ~/Fuzzilli.
    • JavaScript engines: Located in subdirectories of the home directory based on the engine name (e.g., ~/jsc, ~/spidermonkey, ~/v8, ~/duktape, ~/jerryscript). These directories include the engine binary, required libraries, and resource files.
  8. Understand ProbingMutator for property and API discovery

    main

    The ProbingMutator uses runtime-assisted probing to determine how values are used and what properties they possess. It works by:

    1. Insert Probe operations: Places Probe operations on random variables.
    2. Proxy-based Inspection: Executes the program where Probe operations replace the object's prototype with a Proxy. This Proxy records all accesses to non-existent properties.
    3. Install Findings: The mutator processes the recorded accesses and installs the missing properties or callbacks into the program.

    This helps in:

    • Detecting and triggering unexpected callbacks.
    • Discovering required keys for builtin API 'config' objects.
    • Making existing code more meaningful by ensuring accessed properties actually exist.
  9. Use ProgramBuilder to mutate FuzzIL programs

    main
    Since FuzzIL Program objects are immutable, you must use the ProgramBuilder class to perform mutations. The ProgramBuilder acts as a central component for constructing new programs by either generating new instructions or copying existing ones from other programs while managing variable scope and visibility.
  10. Understand the Fuzzilli GCE instance hierarchy

    main

    Fuzzilli on GCE uses a hierarchical network structure to scale fuzzing. The hierarchy consists of a root node, multiple intermediate nodes, and multiple leaf nodes.

    Data Flow Mechanics:

    • Corpus Synchronization: An edge from parent A to child B means they synchronize their corpuses. Newly added samples are sent from A to B and B to A.
    • Crash/Stats Reporting: Newly found crashes and fuzzing statistics are sent only from the child node up to the parent node.
    • Global Management: The root node manages the global corpus by receiving and sharing samples that increase coverage. It is also responsible for receiving all crashing files and storing them to disk.

    The ./start.sh script automatically calculates the number of levels required so that no parent node exceeds a specific number of child nodes.

  11. Understanding Type Subsumption in FuzzIL

    main

    Type subsumption (the "is a" relationship) is used by the type system to find compatible variables for a given operation. It is expressed via <= and >= operators.

    Subsumption Rules:

    • Base Types: A base type only subsumes itself (e.g., an integer is not a string).
    • Unions: A union t1 | t2 subsumes both t1 and t2 (e.g., a "string or number" is a "string").
    • Merged Types: A merged type t1 + t2 is subsumed by both t1 and t2 (e.g., a JavaScript function is both a function and an object).
    • Inheritance: Subclasses and objects with additional properties/methods follow standard inheritance rules (e.g., a Uint8Array with an extra property is still a Uint8Array).
  12. How Fuzzilli determines interesting samples via Coverage

    main

    Fuzzilli uses code coverage as a guidance metric to decide if a generated program should be added to the corpus.

    1. Instrumentation: Target JavaScript engines must be compiled with -fsanitize-coverage=trace-pc-guard and include a code stub to collect edge coverage via the REPRL interface.
    2. Detection: After executing a program, Fuzzilli processes the coverage bitmap to see if any new branches in the engine's control flow graph were discovered.
    3. Selection: If new coverage is found, the sample is considered "interesting," undergoes minimization, and is then added to the corpus.

    Note on JIT: For JIT compilers, Fuzzilli collects coverage on the compiler code itself rather than the generated code. This simplifies instrumentation and is effective because JIT-compiled code is typically only generated after the underlying JavaScript has been executed many times.