You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

ONNX core parser: NULL-pointer dereference in FunctionProto shape inference (DoS)

Status: gated PoC, access restricted for coordinated disclosure via huntr.com (Model File Vulnerability program). Contact protectai-bot / huntr maintainers for access.

Summary

onnx/onnx (the core protobuf model parser/checker library that ships with every pip install onnx, and that ONNX Runtime, model-surgery tools, and MLOps pipelines call directly on untrusted, externally-sourced .onnx files) crashes with a NULL-pointer dereference (SIGSEGV) when shape inference is run over a model-local FunctionProto whose call site omits a trailing optional input, if that same (unbound) formal parameter name is also used as one of the function's own output names.

This is reachable via:

  • onnx.checker.check_model(model, full_check=True) β€” the officially documented, commonly recommended way to fully validate an untrusted ONNX model before use.
  • onnx.shape_inference.infer_shapes(model) β€” used directly by many downstream tools (ONNX Runtime tooling, onnx-graphsurgeon, model optimizers/converters, MLOps ingestion pipelines) to propagate shapes over third-party models.

Both entry points crash the host Python process itself (native SIGSEGV, not a catchable Python exception) on a 159-byte crafted .onnx file β€” i.e. any service that loads and validates/shape-infers an attacker-supplied ONNX file goes down.

Root cause

onnx/shape_inference/implementation.cc, ShapeInferenceImplBase::Process(const FunctionProto&, InferenceContext&):

for (int i = 0; i < num_func_inputs; ++i) {
  const auto& parameter_name = func_proto.input().Get(i);
  const auto* const type_ptr = (i < num_actual_inputs) ? ctx.getInputType(i) : nullptr;
  // nullptr is valid, and indicates a missing optional input
  if (type_ptr != nullptr) {
    types_cache[i] = *type_ptr;
    value_types_by_name[parameter_name] = &types_cache[i];
  } else {
    value_types_by_name[parameter_name] = nullptr;   // <-- intentional null, by design
  }
}

When a function call site provides fewer actual inputs than the function's formal parameter list (a legal, documented way to pass "missing optional inputs" to a function), the missing parameter's name is registered in value_types_by_name with an explicit nullptr value. This is intentional and, by itself, correct.

The bug is that two consumers of value_types_by_name later dereference that stored pointer without checking it for null, whenever the same name is looked up again:

  1. ShapeInferenceImplBase::UpdateType() (line ~367) β†’ mergeShapesAndTypes(*inferred_type, iter->second) β†’ checkShapesAndTypes(inferred_type, *existing_type) (line 198, 112) dereferences existing_type directly β†’ TypeProto::value_case() on a null this β†’ SEGV. Triggered when any node inside the function body reuses the missing parameter's name as its own output name (so UpdateType is invoked again for that name).

  2. ShapeInferenceImplBase::Process(const FunctionProto&, InferenceContext&) (line 670), the function-output copy-back loop:

    for (int i = 0; i < func_proto.output_size(); ++i) {
      auto iter = value_types_by_name.find(output_name);
      if (iter != value_types_by_name.cend()) {
        ctx.getOutputType(i)->CopyFrom(*(iter->second));   // <-- null deref if iter->second==nullptr
      }
    }
    

    Triggered directly when the function declares the missing parameter's name as one of its own outputs (a pure pass-through, no processing node required at all β€” this is the PoC below).

Both are two call sites of the same underlying oversight (introduced in onnx/onnx#5066, merged April 2023, "Fix an issue in function type/shape inference to support optional inputs" β€” the fix added the intentional-null storage but never audited/guarded its later readers). The bug has been present, unfixed, for 3+ years and reproduces on current main (964b886d46d337fa775c6e4012a90534b28cd3ca, 2026-07-07).

PoC

poc_minimal.onnx (159 bytes) β€” built with make_min_poc.py (included) using the standard onnx.helper API:

  • One model-local function custom.domain::PassThroughY, formal inputs ["x", "y"], formal outputs ["y"], zero nodes in its body (pure name pass-through).
  • One call node in the main graph invokes it with only one actual input (["fx"]), leaving formal parameter "y" unbound.
import onnx
m = onnx.load("poc_minimal.onnx")
onnx.checker.check_model(m, full_check=True)   # SIGSEGV
# or:
onnx.shape_inference.infer_shapes(m)           # SIGSEGV

Reproduces on a stock pip install onnx (tested against onnx 1.22.0, PyPI wheel, no custom build) β€” see repro_pip_onnx.txt.

ASan stack trace (from-source ASan+AFL build of onnx/onnx main, matching line numbers)

See asan_trace.txt. Top of stack:

AddressSanitizer: SEGV on unknown address 0x000000000010
    #0 onnx::TypeProto::MergeImpl(...)                          onnx-ml.pb.cc:11593
    #1 ShapeInferenceImplBase::Process(FunctionProto const&, InferenceContext&)  implementation.cc:670
    #2 InferShapeForFunctionNodeInternal(...)                    implementation.cc:879
    #3 ShapeInferenceImplBase::ProcessCall(...)                  implementation.cc:896
    #4 ShapeInferenceImplBase::Process(NodeProto&)                implementation.cc:489
    #5 ShapeInferenceImplBase::Process(GraphProto&)                implementation.cc:599
    #6 InferShapesImpl(...)                                       implementation.cc:789
    #7 onnx::shape_inference::InferShapes(ModelProto&, ...)        implementation.cc:824
    #8 onnx::checker::check_model(ModelProto const&, ...)          checker.cc:1187

How this was found

Coverage-guided AFL++ fuzzing (afl-clang-fast++ + AFL_USE_ASAN=1) directly against the unmodified onnx/onnx C++ core (checker::check_model + shape_inference::InferShapes), built from source against a matching protobuf 6.31.1/v31.1 (Abseil-backed) static build, and seeded with a small hand-written corpus of structurally valid ONNX models covering Add/Conv/ If/Loop/local-function/Sequence graphs. AFL found 13 raw crashing inputs across a ~25-minute, single-machine (4-core) campaign; all 13 were manually triaged by true root cause (not just distinct stack hashes) down to one underlying bug at two call sites, and then hand-minimized to the 159-byte PoC above (no nodes, no tensors, no shapes β€” just the function-signature/call-arity mismatch that triggers the null store).

Dedup / prior-art check

  • Github-issue-searched onnx/onnx for prior reports referencing checkShapesAndTypes, mergeShapesAndTypes, value_types_by_name, ProcessCall, InferShapeForFunctionNodeInternal, and "function optional input segfault" β€” no matching open or closed issue found.
  • Confirmed distinct from the closest-sounding prior reports: onnx/onnx#5219 (closed; a different function β€” the shape data-propagation helper, not general type/shape-inference merge), #5989 (schema-level empty input/output mismatch, not function-call arity), #5212 (segfault specific to data_prop=True on nested functions).
  • The introducing PR (#5066, April 2023) is old and merged; the bug has had 3+ years of exposure (including onnx/onnx's own OSS-Fuzz atheris harnesses, live since ~mid-June 2026) without being caught, most likely because triggering it requires a specific, non-trivial FunctionProto/optional-input/name-reuse structure that raw-byte-mutation fuzzing rarely reaches without a domain-aware seed β€” exactly the gap a seeded, coverage-guided native build closes.

Impact

Any process that runs onnx.checker.check_model(untrusted_model, full_check=True) or onnx.shape_inference.infer_shapes(untrusted_model) over a file it does not fully trust β€” model marketplaces, CI/CD model-validation gates, MLOps ingestion services, ONNX converters β€” can be crashed (denial of service) by a 159-byte file with no special privileges. This is a native SIGSEGV, not a Python exception, so it cannot be caught by a try/except around the call; it takes down the whole process (and, in naive multi-tenant setups, everything else running in it).

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support