Coverage-guided fuzzing across every mainstream engine - libFuzzer (C/C++ in-process), AFL++ (out-of-process, QEMU mode for closed-source binaries), cargo-fuzz (Rust), Go native fuzzing (go test -fuzz), Atheris (Python), and Jazzer (JVM, @FuzzTest). Body covers choosing the right fuzzer for the language and build type (the routing tree) plus the engine-generic workflow: writing a small deterministic fuzz target, seed-corpus + dictionary construction, sanitizer selection (ASan + UBSan default, compatibility matrix), corpus minimisation, crash-artifact handling, and CI smoke-fuzz wiring with a cached corpus. Per-engine depth (flags, harness syntax, CI jobs) lives in references, as do the corpus-management and sanitizer-integration catalogs. Use when a project needs fuzz coverage and no fuzzer is chosen yet, or when authoring / running / maintaining a fuzz campaign with any of these engines. For triaging the resulting crashes see crash-triage-reference.
70
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Per-engine reference for coverage-guided-fuzzing; the shared workflow
and fuzzer-choice routing live in ../SKILL.md.
Atheris (per github.com/google/atheris) supports both pure-Python and native-extension targets (CPython C extensions).
For sanitiser pairing on native extensions, see sanitizer-integration.md; for corpus discipline see corpus-management.md.
pip install atherisPer the Atheris README, prebuilt wheels include libFuzzer for pure-Python fuzzing. Native-extension fuzzing may require building from source so the Clang and libFuzzer versions match.
# fuzz_parser.py
import sys
import atheris
with atheris.instrument_imports():
from my_library import parser
def TestOneInput(data):
parser.parse(data)
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()Per Atheris README:
TestOneInput(data: bytes) is the fuzz callback - invoked
with mutated input bytes each iteration.atheris.Setup(sys.argv, TestOneInput) initialises the fuzzer
with libFuzzer flags from sys.argv.atheris.Fuzz() starts the fuzz loop (doesn't return until
the campaign ends).Atheris needs to instrument the modules under test:
with atheris.instrument_imports():
from my_library import parser, decoderThe instrument_imports() context manager monkey-patches the
import system so subsequent imports are coverage-instrumented.
Module-level imports above this context manager are NOT
instrumented - the fuzzer is blind to their code.
Alternative: per-function instrumentation:
import my_library
my_library.parser.parse = atheris.instrument_func(my_library.parser.parse)Or instrument-all (heavyweight):
atheris.instrument_all()For structured input, use the Python equivalent of libFuzzer's helper:
def TestOneInput(data):
fdp = atheris.FuzzedDataProvider(data)
port = fdp.ConsumeInt(4) # signed 4-byte int
is_https = fdp.ConsumeBool()
host = fdp.ConsumeUnicode(64) # up to 64 chars
body_size = fdp.ConsumeIntInRange(0, 1024)
body = fdp.ConsumeBytes(body_size)
parser.parse_request(host, port, is_https, body)Per Atheris README, the provider exposes ConsumeInt,
ConsumeUnicode, ConsumeFloat, ConsumeBool, PickValueInList,
and related methods.
python fuzz_parser.pyAtheris by default runs indefinitely. Pass libFuzzer-style flags:
python fuzz_parser.py -max_total_time=300 corpus/The trailing directory is the corpus (read + write). Subsequent directories are read-only seeds.
Per Atheris README, all libFuzzer flags pass through:
| Flag | Effect |
|---|---|
-max_total_time=N | Stop after N seconds |
-atheris_runs=N | Run N iterations then stop (also enables coverage report) |
-dict=path | Use dictionary file |
-seed=N | Random seed |
-runs=N | libFuzzer runs (use -atheris_runs for Atheris-specific) |
python fuzz_parser.py -atheris_runs=100000 corpus/
# At end: prints coverage statisticspython fuzz_parser.py crash-<sha1>
# Same crash with full tracebackPython tracebacks instead of sanitiser reports (unless instrumenting a CPython extension built with ASan):
[+] Loading binary contents from crash-abc123
=== Uncaught Python exception: ===
ValueError: invalid syntax
Traceback (most recent call last):
File "fuzz_parser.py", line 12, in TestOneInput
parser.parse(data)
File "/path/my_library/parser.py", line 47, in parse
return json.loads(text)
...Map the traceback to a bug spec via the from-CI-failure workflow in bug-report-template
(qa-bug-repro plugin).
- uses: actions/setup-python@v6
with: { python-version: '3.12' }
- run: pip install atheris
- name: Smoke fuzz (3 min)
run: timeout 180 python fuzz_parser.py -max_total_time=180 corpus/ || true
- uses: actions/upload-artifact@v4
with:
name: atheris-crashes
path: crash-*| Anti-pattern | Why it fails | Fix |
|---|---|---|
Module imports above instrument_imports() | Coverage signal absent for those modules | Always import via with atheris.instrument_imports(): ... |
No exception handling in TestOneInput | Expected exceptions (ValueError on bad input) count as crashes | Catch expected exceptions; only let unexpected ones propagate |
| Pure-Python target without instrumentation | Coverage is blind; fuzzer flailing | Always instrument |
Missing atheris.Fuzz() call | Fuzz loop never starts | Always end with atheris.Fuzz() |
| Treating every traceback as a bug | Many tracebacks are spec-compliant (raising ValueError on invalid input is correct) | Use assert for invariants; let spec-defined exceptions through |
| Native extension without ASan | C bugs silent (segfault crashes Python interpreter) | Build CPython + extension with ASan for native fuzzing |
-jobs (and libFuzzer's job
flag works imperfectly with Python).hypothesis-testing skill in the
qa-property-based plugin) for property-based-style structured input.hypothesis-testing - different methodology (hypothesis-driven vs coverage-guided).