This repository provides the source code for SnakeCharmer: a prototype framework for automated generation of fuzzing harnesses for pure- and hybrid-Python libraries (i.e., libraries with compiled C/C++ extension modules).
This work is presented in our paper SnakeCharmer: Automatic Fuzzing Harness Generation for Pure and Hybrid Python Libraries, appearing in the 2026 Conference on the Foundations of Software Engineering (FSE '26).
- Setup
- Quickstart
- Fuzzing
- Integrating a Target Library
- Generating Harnesses
- Python Interpreter Module Fuzzing
- Trophy Case
| Citing this repository: |
@article{snakecharmer, author = {Sherman, Gabriel and Nagy, Stefan}, title = {SnakeCharmer: Automatic Fuzzing Harness Generation for Pure and Hybrid Python Libraries}, year = {2026}, issue_date = {July 2026}, publisher = {Association for Computing Machinery}, address = {New York, NY, USA}, volume = {3}, number = {FSE}, journal = {Proc. ACM Softw. Eng.}}
|
| Developer: | Gabriel Sherman (gabe.sherman@utah.edu) |
| License: | MIT License |
| Disclaimer: | This software is provided as-is with no warranty. This is a research prototype under active development. |
SnakeCharmer harnesses a target library in two phases:
- Preprocessing (
snakecharmer.preprocess) statically analyzes the library's Python modules, classes, and functions — and, for hybrid libraries, mines native (C/C++) source for exported functions and error call sites — and serializes the result to a single JSON artifact file. - Harness generation (
snakecharmer.cli) consumes that artifact and iteratively synthesizes candidate Atheris fuzz harnesses by mutating API call chains and argument combinations, tracking Python and native code coverage to rank candidates, and minimizing down to a final harness corpus.
Run extras/install_deps.sh to create the snakecharmer_work virtualenv, install SnakeCharmer and its dependencies via uv, and build Atheris (Google's Python fuzzing engine used by generated harnesses). Activate it with extras/activate_venv.sh.
llvm-profdata and llvm-cov are also required
Libraries bundled with the CPython interpreter itself (e.g. ast, email) instead need the coverage-instrumented Python binary from extras/install_instrumented_cpython.sh and its instrumented_interpreter_venv; see Python Interpreter Module Fuzzing.
libs/hiredis-py and libs/autoflake ship prebuilt minimized.json artifacts, so you can try harness generation immediately without running preprocessing first:
source ./extras/activate_venv.sh
cd libs/hiredis-py
make lib
python -m snakecharmer.cli -i $PWD -o $PWD/out --artifact $PWD/minimized.json -n 4 -m hiredis -r b -d -f
cd ../autoflake
make lib
python -m snakecharmer.cli -i $PWD -o $PWD/out -c $PWD/config.yaml --artifact $PWD/minimized.json -n 4 -m autoflake -r b -d -fEach run produces and stores candidate harnesses into out/final-harnesses/. Examples of tool usage, including preprocessing, are included in each library's run_sc.sh script.
At the end of a harnessing campaign, harnesses are written to <out_dir>/final-harnesses/fuzzing/. Run one directly:
python3 out/final-harnesses/fuzzing/harness1.py -rss_limit_mb=0(In hiredis-py, this should quickly expose a segmentation fault in its native code.)
-rss_limit_mb=0 disables Atheris/libFuzzer's memory-limit abort, which otherwise tends to trigger against memory-hungry Python libraries.
To replay coverage after fuzzing, use the harness's counterpart under <out_dir>/final-harnesses/coverage/:
- Python: set
PYCOV_OUTPUTto control where line-coverage data is written; view a report withcoverage report --data-file=<name>. - Native (hybrid libraries): Set
LLVM_PROFILE_FILEto control wherellvm-covdata is written.
Every harness routes exceptions through snakecharmer.fuzzing.triage, which filters out non-builtin exceptions, intentional raise/assert sites (including Cython), duplicates (same type + raise-site file/function/line), and known intentional native (PyErr_Format/PyErr_SetString) messages. What's left is reported as a crash file under <ARTIFACT_PREFIX>/crashes/ (defaults to /tmp/crashes/).
Final (fuzzing) harnesses additionally persist the first occurrence of every stored exception to <ARTIFACT_PREFIX>/<module>_exceptions/seen_exceptions.jsonl so that later occurrences of the same exception are recognized as duplicates and skipped instead of being re-reported. Each line is one JSON object:
{"exception_type": "ValueError", "frame": {"source_file": "autoflake.py", "qualified_function": "autoflake.fix_code", "line": 412}, "message": "invalid literal for int()", "crash_path": "/tmp/crashes/crash-1730000000-ab12cd"}A target library only needs a directory under libs/<library_name>/ containing:
-
Makefile— installs (and, for hybrid libraries, compiles) the library into thesnakecharmer_workvirtualenv. Must define alibtarget that clones/builds the library, and ashowmaptarget that runs a candidate harness against a seed during ranking. Hybrid libraries must build native code with source-based coverage and sanitizer instrumentation, e.g.libs/hiredis-py/Makefile:NATIVE_FLAGS = CC="clang" CFLAGS="-g -fsanitize=address,undefined,fuzzer-no-link -fprofile-instr-generate -fcoverage-mapping" \ CXX="clang++" CXXFLAGS="-g -fsanitize=address,fuzzer-no-link -fprofile-instr-generate -fcoverage-mapping" \ LDSHARED="clang -shared" lib: git clone --recurse-submodules https://github.com/redis/hiredis-py.git lib cd lib && git checkout 4113c717d17b7bd30243254d8b673fdcc2e270b3 @source ../../extras/activate_venv.sh && \ pip uninstall lib && \ $(NATIVE_FLAGS) python3 -m pip install ./lib && \ deactivate
See
libs/for more examples: pure-Python (libs/autoflake,libs/PyYAML) and hybrid (libs/hiredis-py,libs/lxml,libs/h5py). -
seeds_valid/andseeds_invalid/— corpora of accepted/rejected inputs. A varied corpus spanning a range of sizes and shapes works best. -
config.yaml(optional) — currently supportsexcluded_functions: function/method names to exclude from harnessing (seelibs/autoflake/config.yaml).
python -m snakecharmer.preprocess <module> [<module> ...] --output <in_dir>/artifact.json [options]Analyzes the given top-level module name(s) and writes a LibraryAnalysisArtifact JSON file consumed by harness generation.
| Argument | Description |
|---|---|
modules |
Target module name(s) to analyze (positional, one or more). |
--output |
Destination path for the JSON artifact. Required. |
--src-path |
Path to the library's native (.c/.cpp) source. Required for hybrid libraries; if omitted, native API analysis and error call-site mining are skipped. |
--excluded-functions |
Function/method names to exclude from discovery. |
--mutate-optional |
Mutate optional arguments too. |
python -m snakecharmer.cli --input <work_dir> --output <out_dir> --artifact <work_dir>/artifact.json \
--numfuncs <n> --modules <module> --readhow <b|f> [options]| Argument | Short | Description |
|---|---|---|
--input |
-i |
Directory housing the library's Makefile and seeds_valid/seeds_invalid. Required. |
--output |
-o |
Output directory for generated artifacts. Required. |
--artifact |
Path to the LibraryAnalysisArtifact JSON from snakecharmer.preprocess. Required. |
|
--numfuncs |
-n |
Maximum functions to call per harness following a "data entrypoint" routine. Required. |
--modules |
-m |
Module name(s) to target. Required. |
--readhow |
-r |
How harnesses read fuzzer-generated data: b/buf (buffer, e.g. foo(data: bytes)) or f/file (file path). Required. |
--config |
-c |
Path to the optional config.yaml described above. |
--c_weight |
-cw |
Weight given to native (C) coverage edges when ranking harnesses. |
--py_weight |
-pw |
Weight given to Python coverage edges when ranking harnesses. |
--fast_mode |
-f |
Skip exhaustive argument search; keep the first successful resolution. |
--allow_stderr |
-as |
Keep harnesses that emit stderr, if that's valid API behavior for the target. |
--allow_lincov |
-al |
Keep harnesses with linear (input-size-proportional) coverage deltas. |
--debug |
-d |
Write failed/succeeded harness info, inferred function dependencies, and campaign statistics to <out_dir>/debug-info/. |
Fuzzing CPython's own standard-library modules (ast, email, string, etc.) requires a Python interpreter built with coverage/libfuzzer instrumentation, rather than instrumenting the module alone.
extras/install_instrumented_cpython.sh builds the interpreter, and extras/activate_instrumented_interpreter.sh activates it in place of activate_venv.sh (see libs/ast/Makefile's showmap target for an example).
If harnessing appears to be failing or under-performing, inspect the campaign's <out_dir>/debug-info/ directory, which includes:
log_failed.txt/log_successful.txt: which candidate harnesses were discarded or kept, and why.log_harnessed_funcs.txt: functions that were and were not successfully harnessed.log_setup_routines.txt: inferred setup/dependency sequences.log_stats: campaign-wide statistics.
| API | Reported Bugs |
|---|---|
| Astroid | pylint-dev/astroid#2734 |
| AutoFlake | PyCQA/autoflake#324, PyCQA/autoflake#325 |
| Babel | python-babel/babel#1209, python-babel/babel#1134 |
| DateUtil | dateutil/dateutil#1402 |
| Email (CPython 3.9) | python/cpython#134151, python/cpython#134152, python/cpython#134155 |
| Hiredis-Py | redis/hiredis-py#205, redis/hiredis-py#206 |
| Jinja2 | pallets/jinja#2092 |
| PikePDF | pikepdf/pikepdf#652 |
| Pillow | python-pillow/Pillow#8597, python-pillow/Pillow#8956 |
| PyJSON5 | dpranke/pyjson5#90 |
If you find bugs using SnakeCharmer, please let us know!
This material is based upon work supported by the National Science Foundation (NSF) under Award No. 2419798, and by the Defense Advanced Research Projects Agency (DARPA) under Award No. FA8750-24-2-0002, Subaward No. GR105409-SUB00001384.
