Caloogy Article
中文

Fuzzing: Let Machines Find Crashes for You

Date
Jun 9, 2026
Series
Methodology
Reading
4 min
Topics
TestingFuzzingAflLibfuzzer

The one-sentence version

Humans writing test cases can only think of scenarios they can imagine. Fuzzing lets machines automatically generate massive amounts of strange inputs to feed into a program to see if it crashes, hangs, or violates certain rules. Modern fuzzing is coverage-guided: it observes which code paths each input traverses, retains inputs that reach new paths and mutates them further, acting like a tireless, increasingly intelligent tester. Google's OSS-Fuzz has used this method to discover tens of thousands of security vulnerabilities in open-source projects.

This article has three layers:

  1. Layer 1: The Intuition: Letting ten thousand monkeys press keys randomly, but only rewarding the monkeys that find new rooms.
  2. Layer 2: Real-World Use: AFL, OSS-Fuzz, Heartbleed, smart contract invariant testing, AI assistance.
  3. Layer 3: Principles and Engineering details.

Layer 1: The Intuition

1.1 Ten thousand monkeys, only rewarding those who find new rooms

Imagine a labyrinthine building where some rooms contain bombs (bugs). You send ten thousand monkeys inside to run around randomly:

In this way, the scope of exploration goes deeper and deeper, eventually reaching corners that human testers could never imagine.

1.2 What it excels at finding

Type Examples
Crashes Null pointer dereference, array out of bounds, division by zero
Memory safety issues Buffer overflow, use-after-free (combined with sanitizers)
Hangs Infinite loops, excessive execution time
Rule violations Inconsistent results after parsing and re-serializing; "balance must never be negative" being broken

It is particularly good at testing code that parses untrusted input: file formats, network protocols, compression, image decoding, regex engines.


Layer 2: Real-World Use


Layer 3: Principles and Engineering

3.1 The coverage-guided loop

Reached a new path

Crash / Assertion violation

Nothing new

Corpus
(Interesting inputs)

Pick an input and mutate
Flip bits, insert, concatenate...

Run program, record coverage

Save as discovery

Lightweight "probes" are inserted during compilation to record which branches the program traverses. An input is considered "interesting" and added to the corpus if it triggers a new combination of branches.

3.2 Writing a test entry point

Fuzzing requires an "entry function": it receives an arbitrary byte sequence and feeds it to the code under test. Taking the libFuzzer style as an example:

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    parse_my_format(data, size);   // Function under test: must not crash and must not violate assertions
    return 0;
}

A good entry point should be:

One can also add property checks, such as "parsing and then re-serializing should yield the same content" (round-trip testing), or "the output of two implementations must be identical" (differential testing, see Multi-source Cross-validation).

3.3 Sanitizers

Many memory errors do not cause immediate crashes but instead silently corrupt data. Enabling sanitizers during compilation allows these issues to be caught the moment they occur:

Sanitizer What it finds
AddressSanitizer Out-of-bounds read/write, use-after-free
UndefinedBehaviorSanitizer Integer overflow, illegal shifts, and other undefined behaviors
MemorySanitizer Use of uninitialized memory

3.4 Seed Corpus

Starting from some valid sample inputs (such as a few normal image files or a few normal protocol messages) is much more efficient than starting from nothing. The corpus should be continuously preserved as part of regression testing.

3.5 Relationship with other testing methods

Testing Question it answers
Unit Testing Are the cases I thought of correct?
Fuzzing Will something go wrong in the cases I didn't think of?
Property-based Testing Does a certain rule hold true under a large number of random inputs? (A close relative of fuzzing)
Formal Verification Does a certain rule hold true for all possible inputs?

Fuzzing is best suited for continuous execution: integrated into CI to run for a period during every code change, or running 24/7 like OSS-Fuzz.


Summary

Layer Key points
Intuition Massive random inputs, only rewarding those that discover new paths
Excels at Code parsing untrusted input; crashes, memory errors, hangs, property violations
Key Coverage-guided + Sanitizers + Good seed corpus
Practice Write fast and deterministic entry points; add round-trip and differential checks; run continuously