5cbd3f29e3
Fuzz / Run fuzz harnesses (${{ github.event_name == 'schedule' && 'nightly' || 'smoke' }}) (push) Has been cancelled
Create Releases / call-mac (push) Has been cancelled
Create Releases / call-linux (push) Has been cancelled
Create Releases / call-sdist (push) Has been cancelled
Create Releases / call-win (push) Has been cancelled
Create Releases / call-pyodide (push) Has been cancelled
Windows_No_Exception_CI / build (x64, 3.10) (push) Has been cancelled
Check URLs / build (push) Has been cancelled
Create Releases / Attest CI build artifacts (push) Has been cancelled
Create Releases / Check for Publish release build to pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Check for Publish release build to test.pypi (rc-candidates) (push) Has been cancelled
Create Releases / Publish release build to test.pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish release build to pypi (push) Has been cancelled
Create Releases / test source distribution (push) Has been cancelled
clang-tidy / clang-tidy (push) Has been cancelled
Lint / Validate SBOM (push) Has been cancelled
Lint / Enforce style (push) Has been cancelled
CI / Test windows-2022, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=1, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=1, onnx_ml=1, autogen=1 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=0, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
Pixi CI / Install and lint (ubuntu-24.04-arm) (push) Has been cancelled
Pixi CI / Install and lint (windows-2022) (push) Has been cancelled
Pixi CI / Xcode generator build (push) Has been cancelled
Pixi CI / Install and test (macos-latest, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, default) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, default) (push) Has been cancelled
Pixi CI / Install and test (macos-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, oldies) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
Generate and publish ONNX docs / build (push) Has been cancelled
Generate and publish ONNX docs / deploy (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
190 lines
6.4 KiB
Python
190 lines
6.4 KiB
Python
# Copyright (c) ONNX Project Contributors
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
"""Atheris fuzz harness for onnx.shape_inference.
|
|
|
|
Two input paths are exercised per iteration, selected by a fuzzer-controlled
|
|
toggle byte read from the *tail* of the input (so the head remains a valid
|
|
candidate for the raw-bytes path):
|
|
|
|
* Raw bytes -> onnx.load_model_from_string -> infer_shapes
|
|
Catches protobuf parser bugs and bugs reachable only through crafted
|
|
serialized models the structured builder will not produce.
|
|
|
|
* Structured -> helper.make_model from FuzzedDataProvider -> infer_shapes
|
|
Constructs ModelProto objects whose graphs include subgraph-bearing
|
|
ops (If / Loop / Scan) so the recursive visitor inside shape_inference
|
|
is reached on most iterations rather than only when the parser happens
|
|
to accept a random byte string.
|
|
|
|
Both strict_mode values and both check_type values are sampled.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
|
|
import atheris
|
|
|
|
with atheris.instrument_imports():
|
|
import onnx
|
|
from onnx import TensorProto, helper, shape_inference
|
|
|
|
# Elementwise unary ops with trivial shape inference rules. Useful as
|
|
# filler nodes so generated graphs have non-trivial bodies that exercise
|
|
# the per-op inference dispatch table.
|
|
_UNARY = (
|
|
"Relu",
|
|
"Sigmoid",
|
|
"Tanh",
|
|
"Abs",
|
|
"Neg",
|
|
"Exp",
|
|
"Log",
|
|
"Sqrt",
|
|
"Identity",
|
|
"Floor",
|
|
"Ceil",
|
|
)
|
|
|
|
# Ops that carry one or more subgraph attributes. Each forces the recursive
|
|
# shape-inference visitor to descend, which is the path the known DoS lives
|
|
# on. Loop/Scan exercise different subgraph-context plumbing than If.
|
|
_SUBGRAPH_OPS = ("If", "Loop", "Scan")
|
|
|
|
|
|
def _const_bool(name, value=True):
|
|
tensor = helper.make_tensor(name, TensorProto.BOOL, [], [value])
|
|
return helper.make_node("Constant", [], [name], value=tensor)
|
|
|
|
|
|
def _build_branch(fdp, depth, max_depth):
|
|
"""Build a self-contained subgraph.
|
|
|
|
Self-contained means the subgraph produces its own starting tensor via
|
|
a Constant node, so the branch does not depend on outer-scope captures
|
|
we did not declare. With probability the branch nests one of
|
|
If/Loop/Scan, which is what drives the recursion inside shape_inference.
|
|
Loop and Scan body subgraphs are deliberately not signature-conformant;
|
|
the recursive visitor descends before signature checks run, so the
|
|
recursion path is still exercised even when inference ultimately fails.
|
|
"""
|
|
nodes = []
|
|
start = f"s_{depth}"
|
|
start_tensor = helper.make_tensor(start, TensorProto.FLOAT, [1], [0.0])
|
|
nodes.append(helper.make_node("Constant", [], [start], value=start_tensor))
|
|
|
|
if depth < max_depth and fdp.ConsumeBool():
|
|
sub_op = _SUBGRAPH_OPS[fdp.ConsumeIntInRange(0, len(_SUBGRAPH_OPS) - 1)]
|
|
out = f"sub_{depth}"
|
|
body = _build_branch(fdp, depth + 1, max_depth)
|
|
if sub_op == "If":
|
|
cond = f"c_{depth}"
|
|
nodes.append(_const_bool(cond))
|
|
else_body = _build_branch(fdp, depth + 1, max_depth)
|
|
nodes.append(
|
|
helper.make_node(
|
|
"If",
|
|
[cond],
|
|
[out],
|
|
then_branch=body,
|
|
else_branch=else_body,
|
|
)
|
|
)
|
|
elif sub_op == "Loop":
|
|
trip = f"M_{depth}"
|
|
trip_t = helper.make_tensor(trip, TensorProto.INT64, [], [1])
|
|
nodes.append(helper.make_node("Constant", [], [trip], value=trip_t))
|
|
cond = f"c_{depth}"
|
|
nodes.append(_const_bool(cond))
|
|
nodes.append(
|
|
helper.make_node(
|
|
"Loop",
|
|
[trip, cond],
|
|
[out],
|
|
body=body,
|
|
)
|
|
)
|
|
else: # Scan
|
|
nodes.append(
|
|
helper.make_node(
|
|
"Scan",
|
|
[start],
|
|
[out],
|
|
body=body,
|
|
num_scan_inputs=1,
|
|
)
|
|
)
|
|
last = out
|
|
else:
|
|
last = start
|
|
n_ops = fdp.ConsumeIntInRange(0, 4)
|
|
for i in range(n_ops):
|
|
op = _UNARY[fdp.ConsumeIntInRange(0, len(_UNARY) - 1)]
|
|
nxt = f"v_{depth}_{i}"
|
|
nodes.append(helper.make_node(op, [last], [nxt]))
|
|
last = nxt
|
|
|
|
return helper.make_graph(
|
|
nodes,
|
|
f"branch_{depth}",
|
|
inputs=[],
|
|
outputs=[helper.make_tensor_value_info(last, TensorProto.FLOAT, None)],
|
|
)
|
|
|
|
|
|
def _build_model(fdp):
|
|
# Top-level graph mirrors a branch but lives at depth 0 and chooses its
|
|
# own opset version so different shape-inference codepaths (per-opset
|
|
# schemas) are reached.
|
|
max_depth = fdp.ConsumeIntInRange(0, 80)
|
|
graph = _build_branch(fdp, depth=0, max_depth=max_depth)
|
|
opset = fdp.ConsumeIntInRange(7, 27)
|
|
return helper.make_model(
|
|
graph,
|
|
opset_imports=[helper.make_opsetid("", opset)],
|
|
)
|
|
|
|
|
|
def TestOneInput(data):
|
|
# Toggles live in the trailing byte. On the structured path we slice the
|
|
# byte off before handing the rest to FuzzedDataProvider. On the raw path
|
|
# we pass the full `data` to the protobuf parser unchanged: seed models
|
|
# are complete serialized ModelProtos, so slicing the tail would truncate
|
|
# every seed. The trailing toggle byte becomes part of the raw input,
|
|
# which libFuzzer mutates freely anyway.
|
|
if len(data) < 2:
|
|
return
|
|
toggles = data[-1]
|
|
strict = bool(toggles & 0x01)
|
|
check_type = bool(toggles & 0x02)
|
|
use_structured = bool(toggles & 0x04)
|
|
# bits 0x08..0x80 are reserved for future toggles; mutations against
|
|
# them are harmless until claimed.
|
|
|
|
try:
|
|
if use_structured:
|
|
fdp = atheris.FuzzedDataProvider(data[:-1])
|
|
model = _build_model(fdp)
|
|
else:
|
|
model = onnx.load_model_from_string(data)
|
|
shape_inference.infer_shapes(
|
|
model,
|
|
check_type=check_type,
|
|
strict_mode=strict,
|
|
)
|
|
except Exception:
|
|
# Malformed fuzz inputs raise a broad set of expected exceptions
|
|
# (ValidationError, InferenceError, DecodeError, ValueError, ...).
|
|
# Real bugs surface as crashes, hangs, or sanitizer reports.
|
|
return
|
|
|
|
|
|
def main():
|
|
atheris.instrument_all()
|
|
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
|
|
atheris.Fuzz()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|