ONNX OSS-Fuzz Harnesses
This directory contains atheris-based Python fuzz targets used by OSS-Fuzz to continuously test ONNX for crashes, hangs, and sanitizer violations.
Harnesses
| File | Entry point fuzzed | Input path |
|---|---|---|
fuzz_checker.py |
checker.check_model |
Raw bytes → protobuf parser |
fuzz_compose.py |
compose.merge_models |
Raw (length-prefixed model pair) and structured (toggle byte) |
fuzz_model_loader.py |
load_model_from_string + checker.check_model |
Raw bytes → protobuf parser |
fuzz_parser.py |
parser.parse_model |
UTF-8 text (ONNX text format) |
fuzz_shape_inference.py |
shape_inference.infer_shapes |
Raw bytes and structured model (toggle byte) |
fuzz_version_converter.py |
version_converter.convert_version |
Raw bytes → protobuf parser |
make_seed_corpus.py |
(seed generator, not a fuzzer) | Produces seed zips for OSS-Fuzz |
CI regression check
.github/workflows/fuzz.yml runs each harness in this
repo's own CI (a short smoke run on PRs/pushes that touch onnx/, a longer nightly run) as
a regression check — e.g. to catch a harness failing to import or a documented harness file
going missing. This is separate from, and much shallower than, OSS-Fuzz's own continuous
fuzzing campaigns; OSS-Fuzz findings are not surfaced in this repo's CI.
How OSS-Fuzz uses these files
The companion OSS-Fuzz project (google/oss-fuzz#15382)
clones this repository and runs each fuzz_*.py file via compile_python_fuzzer.
The build.sh in that repo references these files from $SRC/onnx/fuzz/ so that the
harnesses are version-controlled here alongside the code they test.
Running a harness locally
Atheris requires a libFuzzer-instrumented Python build; the easiest way is via the
OSS-Fuzz Docker image. For quick local smoke-tests you can run without fuzzing by
passing -runs=0 (just loads the harness):
pip install atheris
python onnx/fuzz/fuzz_checker.py -runs=1000
python onnx/fuzz/fuzz_compose.py -runs=1000
python onnx/fuzz/fuzz_parser.py -runs=1000
python onnx/fuzz/fuzz_shape_inference.py -runs=1000
python onnx/fuzz/fuzz_version_converter.py -runs=1000
To generate the seed corpora that OSS-Fuzz uses as starting inputs. The 5th
argument (compose_seeds.zip) is optional, so existing 4-zip invocations keep
working unchanged:
python onnx/fuzz/make_seed_corpus.py \
/tmp/vc_seeds.zip /tmp/parser_seeds.zip /tmp/checker_seeds.zip \
/tmp/shape_inference_seeds.zip [/tmp/compose_seeds.zip] # 5th arg optional
Design notes
Why except Exception: return?
Fuzz targets must never crash on expected errors — only on unexpected ones
(memory corruption, hangs, sanitizer reports). All protobuf parse failures,
ValidationError, InferenceError, DecodeError, etc. are expected when the
fuzzer feeds random bytes. Swallowing them lets libFuzzer keep searching for
inputs that cause real bugs.
Why TestOneInput?
TestOneInput is the required entry-point name
for atheris harnesses. Ruff's N802 (lowercase function names) is suppressed for
onnx/fuzz/** in pyproject.toml for this reason.
fuzz_shape_inference.py toggle byte
The shape inference harness exercises two code paths per iteration, selected by the last byte of the input:
| Bit | Meaning |
|---|---|
0x01 |
strict_mode=True |
0x02 |
check_type=True |
0x04 |
Use structured model builder (If/Loop/Scan subgraphs) instead of raw bytes |
This lets a single harness cover both the protobuf-parser path and the recursive subgraph visitor without needing separate fuzzers.
fuzz_compose.py toggle byte
The compose harness drives two ModelProtos plus an io_map into
compose.merge_models. The last byte selects the input strategy:
| Bit | Meaning |
|---|---|
0x01 |
pass prefix1/prefix2 (collision-resolution path) |
0x04 |
structured: build both models from FuzzedDataProvider (else raw: a 4-byte length prefix splits the remaining bytes into m1 | m2) |
0x08 |
random io_map (else derive it from m1's output names and m2's input names) |
merge_models transitively exercises merge_graphs, check_overlapping_names,
the recursive connect_io subgraph rewrite, add_prefix, and a final
checker.check_model on the merged result.
Adding a new harness
- Create
onnx/fuzz/fuzz_<name>.pyfollowing the pattern of an existing harness. - If the fuzzer benefits from seed inputs, add them to
make_seed_corpus.pyand wire up the output zip in the OSS-Fuzzbuild.sh. - Open a PR here; once merged, update the OSS-Fuzz
build.shif a new seed zip was added.