Skip to content

Validate runtime tensor ranks in CPU Conv, ConvInteger, QLinearConv, CropAndResize, Einsum, and AffineGrid kernels - #33517

Open
m00dy (professor-moody) wants to merge 1 commit into
microsoft:mainfrom
professor-moody:fix/cpu-conv-crop-einsum-affinegrid-rank-checks
Open

m00dy (professor-moody) wants to merge 1 commit into
microsoft:mainfrom
professor-moody:fix/cpu-conv-crop-einsum-affinegrid-rank-checks

Conversation

@professor-moody

@professor-moody m00dy (professor-moody) commented Oct 21, 2026 •

Copy link
Copy Markdown

Summary

When input tensor shapes are dynamic or omitted from ValueInfoProto at model load time, ONNX schema shape inference does not reject rank mismatches before kernel execution. This PR adds runtime rank checks (and reorders shape validation before dimension indexing) across six CPU and contrib operator paths so invalid runtime inputs return a clean INVALID_ARGUMENT / FAIL Status instead of triggering gsl::span contract-violation std::terminate() aborts (or reading past empty buffers when trailing dimensions are zero):

  1. ConvAttributes::ValidateInputShape (onnxruntime/core/providers/cpu/nn/conv_attributes.h):

    • Checks input_shape.NumDimensions() >= 3 (minimum rank for 1D convolution [N, C, D1]) after verifying input_shape.NumDimensions() == weight_shape.NumDimensions() and before indexing weight_shape[0], input_shape[1], and weight_shape[1]. Previously, when both X and W had rank 0 or rank 1 at runtime (e.g., X={3}, W={3}, group=3), input_shape.NumDimensions() != weight_shape.NumDimensions() passed and input_shape[1] / weight_shape[1] indexed past the 1-element gsl::span; when both had rank 2 (e.g., X={1, 1}, W={1, 1}), ValidateInputShape succeeded with zero spatial dimensions (kernel_rank == 0).
  2. ConvInteger::Compute (onnxruntime/core/providers/cpu/quantization/conv_integer.cc):

    • Moves ORT_RETURN_IF_ERROR(conv_attrs_.ValidateInputShape(X, W)); before indexing X->Shape()[0], X->Shape()[1], and W->Shape()[0]. Previously, rank-0 or rank-1 X or rank-0 W indexed Shape()[0..1] out of bounds before ValidateInputShape was reached.
  3. QLinearConv<ActType>::Compute (onnxruntime/core/providers/cpu/quantization/qlinearconv.cc):

    • Moves ORT_RETURN_IF_ERROR(conv_attrs_.ValidateInputShape(X->Shape(), W_shape, channels_last_)); before indexing X->Shape()[0] and W_shape[0].
  4. contrib::CropAndResize<T>::Compute (onnxruntime/contrib_ops/cpu/crop_and_resize.cc):

    • Checks x_dims.NumDimensions() == 4, rois_dims.NumDimensions() == 2, and batch_indices_dims.NumDimensions() == 1 before indexing x_dims[1], batch_indices_dims[0], and rois_dims[1] (which previously ran before CheckROIAlignValidInput).
    • In addition to preventing low-rank gsl::span aborts, enforcing exact ranks before indexing ensures that Shape().Size() equals the product of the validated dimensions (x_dims[0..3], rois_dims[0..1], and batch_indices_dims[0]), keeping rois_data, batch_indices_data, and X_data reads in range even when dynamic runtime inputs have a trailing zero dimension (such as rois with shape {1, 4, 0} or batch_indices with shape {1, 0}).
  5. EinsumComputePreprocessor::ProcessSubscripts (onnxruntime/core/providers/cpu/math/einsum_utils/einsum_compute_preprocessor.h):

    • Moves the excess subscript-label check (if (dim_counter >= rank)) ahead of auto dim_value = dims[dim_counter];. Previously, if (++dim_counter > rank) ran at the end of the character loop body after dims[dim_counter] had already been indexed out of bounds.
  6. AffineGrid<T>::Compute (onnxruntime/core/providers/cpu/tensor/affine_grid.cc):

    • Checks size_shape.NumDimensions() == 1 before indexing size_shape.GetDims()[0] and reading size_data[0..3] / size_data[0..4]. Without this check, a rank-0 size tensor aborts on an empty GetDims() span, and enforcing rank 1 ensures size_shape.GetDims()[0] equals the element count of size_data.

Tests

  • Added regression unit tests pinned to DefaultCpuExecutionProvider() with test.AddShapeToTensorData(false) so dynamic runtime shapes reach the CPU kernels:
    • ConvTest.Rank1InputAndWeightRejected and ConvTest.Rank2InputAndWeightRejected (conv_op_test.cc)
    • ConvIntegerTest.Rank1InputRejected and ConvIntegerTest.Rank2InputAndWeightRejected (conv_integer_test.cc)
    • QLinearConvTest.Rank0InputRejected (qlinearconv_op_test.cc)
    • CropAndResizeTest.InvalidInputRanksRejected (crop_and_resize_op_test.cc)
    • Einsum.SubscriptLabelsExceedInputRankRejected (einsum_test.cc)
    • AffineGridTest.size_input_is_rank_0 and AffineGridTest.size_input_rank_2_trailing_zero_rejected (affine_grid_test.cc)
  • Verified in onnxruntime_test_all (--gtest_filter="ConvTest.*:ConvIntegerTest.*:QLinearConvTest.*:CropAndResizeTest.*:Einsum.*:AffineGridTest.*"): all 9 new regression tests fail or abort before the fix and pass (9/9) with the fix applied, and the full 302-test filter passes with zero regressions (299 PASSED, 3 SKIPPED).

Note: This patch was prepared with AI assistance and verified by human review and local test execution.

…CropAndResize, Einsum, and AffineGrid kernels
Copilot AI balanced review requested due to automatic review settings October 21, 2026 16:50
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The targeted guards precede unsafe accesses, preserve valid-input behavior, and have focused regression coverage with no identified blocking issues.

No open issues 🎉

What changed in this PR

This PR hardens six ONNX Runtime CPU operator paths so invalid runtime tensor ranks return errors instead of causing unsafe accesses.

Changes:

  • Add rank checks and move validation before dimension indexing.
  • Reject excess Einsum labels before accessing input dimensions.
  • Add CPU-only regression tests with dynamic shapes.

No actionable findings remain.

File Description
onnxruntime/​test/​providers/​cpu/​tensor/​affine_grid_test.cc Tests scalar and trailing-zero size inputs.
onnxruntime/​test/​providers/​cpu/​nn/​qlinearconv_op_test.cc Tests scalar input rejection.
onnxruntime/​test/​providers/​cpu/​nn/​conv_op_test.cc Tests rank-1 and rank-2 rejection.
onnxruntime/​test/​providers/​cpu/​nn/​conv_integer_test.cc Tests invalid convolution ranks.
onnxruntime/​test/​providers/​cpu/​math/​einsum_test.cc Tests excess subscript labels.
onnxruntime/​test/​contrib_ops/​crop_and_resize_op_test.cc Tests invalid input ranks.
onnxruntime/​core/​providers/​cpu/​tensor/​affine_grid.cc Requires rank-1 size input.
onnxruntime/​core/​providers/​cpu/​quantization/​qlinearconv.cc Validates shapes before indexing.
onnxruntime/​core/​providers/​cpu/​quantization/​conv_integer.cc Validates shapes before indexing.
onnxruntime/​core/​providers/​cpu/​nn/​conv_attributes.h Requires convolution rank of at least three.
onnxruntime/​core/​providers/​cpu/​math/​einsum_utils/​einsum_compute_preprocessor.h Checks label bounds before dimension access.
onnxruntime/​contrib_ops/​cpu/​crop_and_resize.cc Enforces ranks before dimension access.

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants