Free — no signup required

Fuzzing: The Bull in the China Shop

3 min read

Random Chaos at the Byte Level

Fuzzing (or Fuzz Testing) is a cousin to Property-Based Testing, but it operates at a lower level and with less structure. Where PBT generates semantically valid data of the right type (e.g., "a list of integers"), a fuzzer often generates raw, malformed, or mutated byte sequences and hurls them at a program's input interfaces.

Think of it this way: PBT is a clever QA engineer who generates edge-case inputs within the rules. A fuzzer is someone who takes a valid JPEG file and randomly flips bits in it, then tries to open it in your image viewer to see if it crashes.

Why Fuzzing Matters

Fuzzing is primarily used for security research and finding crashes in code that parses complex, untrusted inputs — file format parsers, network protocol handlers, image decoders, archive extractors, and compilers.

If your program accepts a user-uploaded image and resizes it, a fuzzer will feed it:
- Corrupted image headers that claim the image is 100GB but the file is 1KB.
- Files with valid headers but garbage pixel data.
- Files containing null bytes in unexpected positions.
- Inputs that trigger integer overflows in dimension calculations.

These inputs can expose memory safety bugs — buffer overflows, use-after-free errors, null pointer dereferences — that a human tester would never think to construct manually, but that a real attacker absolutely would.

Coverage-Guided Fuzzing

Modern fuzzers like AFL++ (American Fuzzy Lop) and libFuzzer (built into LLVM) are not purely random. They are coverage-guided: they instrument the binary to track which code paths each input exercises. When a mutated input causes the program to execute a new code path, the fuzzer keeps that input as a seed for further mutation. This means the fuzzer actively explores the program's logic rather than blindly spraying random bytes.

Go has built-in fuzzing support since Go 1.18:

func FuzzParseDate(f *testing.F) {
    // Seed corpus — known valid inputs
    f.Add("2024-01-15")
    f.Add("1999-12-31")

    f.Fuzz(func(t *testing.T, s string) {
        // The fuzzer mutates 's' and calls this function thousands of times.
        // We just check it doesn't panic — no crash is the property.
        result, err := ParseDate(s)
        if err == nil && result.IsZero() {
            t.Error("parsed successfully but returned zero time")
        }
    })
}

Run with: go test -fuzz=FuzzParseDate -fuzztime=30s

PBT vs. Fuzzing: When to Use Which

Dimension Property-Based Testing Fuzzing
Input structure Semantically valid, typed Raw bytes, often malformed
Primary target Business logic, algorithms Parsers, security, crash safety
Properties checked Explicit invariants you define Implicit: "does not crash/panic"
Typical language Application code C, C++, Go, Rust, security-critical code
Tooling Hypothesis, jqwik, rapid AFL++, libFuzzer, go-fuzz

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free