Skip to content

Repository files navigation

SnakeCharmer: Automatic Python Harness Generation

SnakeCharmer logo

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).

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.

How It Works

SnakeCharmer harnesses a target library in two phases:

  1. 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.
  2. 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.

Setup

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.

Quickstart

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 -f

Each 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.

Fuzzing

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_OUTPUT to control where line-coverage data is written; view a report with coverage report --data-file=<name>.
  • Native (hybrid libraries): Set LLVM_PROFILE_FILE to control where llvm-cov data is written.

Exception Triage

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"}

Integrating a Target Library

A target library only needs a directory under libs/<library_name>/ containing:

  • Makefile — installs (and, for hybrid libraries, compiles) the library into the snakecharmer_work virtualenv. Must define a lib target that clones/builds the library, and a showmap target 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/ and seeds_invalid/ — corpora of accepted/rejected inputs. A varied corpus spanning a range of sizes and shapes works best.

  • config.yaml (optional) — currently supports excluded_functions: function/method names to exclude from harnessing (see libs/autoflake/config.yaml).

Generating Harnesses

Step 1: Preprocess the library

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.

Step 2: Generate harnesses

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/.

Python Interpreter Module Fuzzing

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).

Debugging

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.

Trophy Case

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!

Acknowledgement

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.

About

SnakeCharmer: Automatic Fuzzing Harness Generation for Pure and Hybrid Python Libraries

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages