ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

Fuzz Testing

Discover vulnerabilities and crashes by feeding programs random, malformed, or unexpected inputs at scale. Covers coverage-guided fuzzing, grammar-based fuzzing, differential fuzzing, integration with CI/CD, and the patterns that find the bugs traditional testing misses.

Fuzz testing throws random, malformed, and unexpected inputs at your code to find crashes, memory leaks, and security vulnerabilities that human testers would never think to try. Fuzzing found critical vulnerabilities in Heartbleed (OpenSSL), Shellshock (Bash), and thousands of bugs in Chrome, Firefox, and the Linux kernel.


Why Fuzz

Traditional testing: Tests what you expect
  test("valid email", () => validate("user@example.com"))  ✓
  test("empty string", () => validate(""))                  ✓
  test("no @", () => validate("invalid"))                   ✓

Fuzz testing: Tests what you DON'T expect
  Input: "\x00\xff\xfe" → CRASH (null byte handling)
  Input: "a@" + "b" * 10000 → OOM (unbounded allocation)
  Input: "user@exam\nple.com" → Header injection
  Input: "user@[IPv6:::]" → Parser confusion
  
You cannot enumerate all edge cases.
Fuzzers explore inputs you would never write by hand.

Coverage-Guided Fuzzing

# Example: AFL (American Fuzzy Lop) workflow

# 1. Instrument your code for coverage tracking
# AFL modifies binary to report which code paths are hit

# 2. Provide seed inputs (valid examples)
# seeds/valid_json.txt: {"name": "test"}
# seeds/valid_xml.txt: <root><name>test</name></root>

# 3. Run the fuzzer
# afl-fuzz -i seeds/ -o findings/ -- ./my_parser @@

# AFL's algorithm:
# a. Take a seed input
# b. Mutate it (bit flip, byte insert, arithmetic)
# c. Run the program with mutated input
# d. If new code path covered → save as new seed
# e. If crash → save in findings/crashes/
# f. Repeat billions of times

# Python fuzzing with Atheris (Google)
import atheris
import sys
import json

def TestOneInput(data):
    """Called by fuzzer with random bytes."""
    try:
        fdp = atheris.FuzzedDataProvider(data)
        json_str = fdp.ConsumeUnicode(1024)
        
        # Function under test
        parsed = json.loads(json_str)
        
        # Differential: re-serialize and compare
        reserialized = json.dumps(parsed)
        reparsed = json.loads(reserialized)
        assert parsed == reparsed, "Round-trip failure"
        
    except (json.JSONDecodeError, UnicodeDecodeError):
        pass  # Expected failures, not bugs

atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()

Grammar-Based Fuzzing

# Generate syntactically valid but semantically interesting inputs

from hypothesis import given, strategies as st

# SQL injection fuzzing
sql_payloads = st.one_of(
    st.just("' OR 1=1 --"),
    st.just("'; DROP TABLE users; --"),
    st.just("' UNION SELECT * FROM passwords --"),
    st.text(
        alphabet=st.characters(
            whitelist_categories=("L", "N"),
            whitelist_characters="'\"\\;-/* "
        ),
        min_size=1,
        max_size=1000
    ),
)

@given(user_input=sql_payloads)
def test_search_endpoint_safe_from_sql_injection(user_input):
    response = client.get(f"/api/search?q={user_input}")
    # Should never return server error (SQL syntax error = injection possible)
    assert response.status_code != 500
    assert "SQL" not in response.text.upper()

CI/CD Integration

# GitHub Actions: Run fuzzing on every PR
name: Fuzz Testing
on: [pull_request]

jobs:
  fuzz:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Run fuzzer (5 minute budget)
        run: |
          python -m atheris \
            --fuzz_target=tests/fuzz_parser.py \
            --max_total_time=300 \
            --corpus_dir=corpus/ \
            --artifact_prefix=crashes/
      
      - name: Upload crashes
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: fuzz-crashes
          path: crashes/

Anti-Patterns

Anti-PatternConsequenceFix
Fuzzing only in pre-releaseMiss bugs during developmentContinuous fuzzing in CI
No seed corpusFuzzer starts from scratch each runMaintain corpus of interesting inputs
Catching all exceptionsHide real bugsOnly catch expected exceptions
No crash triageSame bug reported thousands of timesDeduplicate by stack trace
Fuzzing without sanitizersMiss memory bugsASan, MSan, UBSan with fuzzer

Fuzzing finds the bugs that code reviews, unit tests, and integration tests all miss. It is the safety net that catches what you never thought to test.

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →