Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses a heap out-of-bounds read in MatMulInteger by adding missing shape validation for the 1D×1D (vector dot-product) case, plus additional validation changes across Slice/Pad/Split implementations in native and JS Web backends.
Changes:
- Add a missing K-dimension compatibility check for 1D×1D MatMul in
MatMulComputeHelper. - Add regression tests for MatMulInteger 1D dimension mismatch and a valid 1D dot product.
- Add/adjust validation in WebGPU/CUDA/CPU (and JS Web) implementations for Slice/Pad/Split inputs and computed shapes.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| onnxruntime/test/providers/cpu/math/matmul_integer_test.cc | Adds regression tests for 1D MatMulInteger mismatch and valid dot product. |
| onnxruntime/core/providers/cpu/math/matmul_helper.h | Adds missing K-dimension check for 1D×1D MatMul. |
| onnxruntime/core/providers/webgpu/tensor/slice.cc | Adds axis range validation and new output-dimension validation. |
| onnxruntime/core/providers/webgpu/tensor/pad.cc | Adds new output-dimension validation. |
| onnxruntime/core/providers/cuda/tensor/split.cc | Adds validation to reject negative values in split input. |
| onnxruntime/core/providers/cuda/tensor/pad.cc | Adds new output-dimension validation. |
| onnxruntime/core/providers/cpu/tensor/split.h | Adds validation to reject negative values in split input. |
| onnxruntime/core/providers/cpu/tensor/pad.cc | Adds new output-dimension validation in reshaped and final output dims. |
| js/web/lib/wasm/jsep/webgpu/ops/slice.ts | Adds JS WebGPU Slice output-dimension validation. |
| js/web/lib/wasm/jsep/util.ts | Fixes normalizeAxis() condition and adds padShape output-dimension validation. |
| js/web/lib/onnxjs/util.ts | Fixes normalizeAxis() condition and adds padShape output-dimension validation. |
| js/web/lib/onnxjs/backends/webgl/ops/slice.ts | Adds WebGL Slice output-dimension validation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ORT_RETURN_IF(output_dims[axes[i]] <= 0, | ||
| "Output dimension ", axes[i], " is non-positive (", output_dims[axes[i]], |
There was a problem hiding this comment.
The new validation rejects output dimensions of 0 (empty slices) by checking <= 0. In ONNX, Slice can legally produce empty outputs (e.g., start == end, or a valid slice on an input with a 0 dimension), and the code already has an output_size == 0 fast-return for that case. Consider changing this check to only reject negative dimensions (e.g., < 0) or removing it, so valid empty outputs don’t error.
| ORT_RETURN_IF(output_dims[axes[i]] <= 0, | |
| "Output dimension ", axes[i], " is non-positive (", output_dims[axes[i]], | |
| ORT_RETURN_IF(output_dims[axes[i]] < 0, | |
| "Output dimension ", axes[i], " is negative (", output_dims[axes[i]], |
| ORT_RETURN_IF(output_dims[i] <= 0, | ||
| "Output dimension ", i, " is non-positive (", output_dims[i], |
There was a problem hiding this comment.
This check treats output dimension 0 as an error (<= 0). Pad should allow 0-sized dimensions (empty tensors) in ONNX; only negative dimensions are invalid. Consider changing the condition to only reject negative dimensions, otherwise valid padding/slicing that results in an empty output will fail unexpectedly.
| ORT_RETURN_IF(output_dims[i] <= 0, | |
| "Output dimension ", i, " is non-positive (", output_dims[i], | |
| ORT_RETURN_IF(output_dims[i] < 0, | |
| "Output dimension ", i, " is negative (", output_dims[i], |
| ORT_RETURN_IF(output_dims[i] <= 0, | ||
| "Output dimension ", i, " is non-positive (", output_dims[i], |
There was a problem hiding this comment.
The validation rejects output dims equal to 0 by checking <= 0. ONNX shapes can contain 0 (empty tensors), and Pad can legitimately produce empty outputs; only negative output dimensions should be rejected. Consider changing the condition to only check for < 0 to avoid breaking valid models.
| ORT_RETURN_IF(output_dims[i] <= 0, | |
| "Output dimension ", i, " is non-positive (", output_dims[i], | |
| ORT_RETURN_IF(output_dims[i] < 0, | |
| "Output dimension ", i, " is negative (", output_dims[i], |
| ORT_RETURN_IF(reshaped_output_dims[i] <= 0, | ||
| "Output dimension ", i, " is non-positive (", reshaped_output_dims[i], | ||
| "). This may occur from excessive negative padding/slicing."); | ||
| } | ||
|
|
||
| // Compute true output dimensions | ||
| for (size_t i = 0; i < data_rank; i++) { | ||
| output_dims[i] += SafeInt<int64_t>(pads[i]) + pads[i + data_rank] + slices[i] + slices[i + data_rank]; | ||
| ORT_RETURN_IF(output_dims[i] <= 0, | ||
| "Output dimension ", i, " is non-positive (", output_dims[i], |
There was a problem hiding this comment.
This new check makes output dimensions of 0 an error (<= 0). CPU Pad historically supports 0-sized dimensions (empty outputs) and uses Size()==0 handling paths later; only negative dimensions are invalid. Consider changing this validation to reject only negative dimensions so valid empty outputs don’t fail.
| ORT_RETURN_IF(reshaped_output_dims[i] <= 0, | |
| "Output dimension ", i, " is non-positive (", reshaped_output_dims[i], | |
| "). This may occur from excessive negative padding/slicing."); | |
| } | |
| // Compute true output dimensions | |
| for (size_t i = 0; i < data_rank; i++) { | |
| output_dims[i] += SafeInt<int64_t>(pads[i]) + pads[i + data_rank] + slices[i] + slices[i + data_rank]; | |
| ORT_RETURN_IF(output_dims[i] <= 0, | |
| "Output dimension ", i, " is non-positive (", output_dims[i], | |
| ORT_RETURN_IF(reshaped_output_dims[i] < 0, | |
| "Output dimension ", i, " is negative (", reshaped_output_dims[i], | |
| "). This may occur from excessive negative padding/slicing."); | |
| } | |
| // Compute true output dimensions | |
| for (size_t i = 0; i < data_rank; i++) { | |
| output_dims[i] += SafeInt<int64_t>(pads[i]) + pads[i + data_rank] + slices[i] + slices[i + data_rank]; | |
| ORT_RETURN_IF(output_dims[i] < 0, | |
| "Output dimension ", i, " is negative (", output_dims[i], |
| ORT_RETURN_IF(reshaped_output_dims[i] <= 0, | ||
| "Output dimension ", i, " is non-positive (", reshaped_output_dims[i], | ||
| "). This may occur from excessive negative padding/slicing."); | ||
| } | ||
|
|
||
| // Compute true output dimensions | ||
| for (size_t i = 0; i < data_rank; i++) { | ||
| output_dims[i] += SafeInt<int64_t>(pads[i]) + pads[i + data_rank] + slices[i] + slices[i + data_rank]; | ||
| ORT_RETURN_IF(output_dims[i] <= 0, | ||
| "Output dimension ", i, " is non-positive (", output_dims[i], |
There was a problem hiding this comment.
Same issue as above: this validation rejects output dims that become 0 (<= 0), but 0 is a valid ONNX dimension (empty tensor). Consider changing to only reject negative dimensions to avoid breaking valid padding/slicing cases that legitimately produce empty outputs.
| ORT_RETURN_IF(reshaped_output_dims[i] <= 0, | |
| "Output dimension ", i, " is non-positive (", reshaped_output_dims[i], | |
| "). This may occur from excessive negative padding/slicing."); | |
| } | |
| // Compute true output dimensions | |
| for (size_t i = 0; i < data_rank; i++) { | |
| output_dims[i] += SafeInt<int64_t>(pads[i]) + pads[i + data_rank] + slices[i] + slices[i + data_rank]; | |
| ORT_RETURN_IF(output_dims[i] <= 0, | |
| "Output dimension ", i, " is non-positive (", output_dims[i], | |
| ORT_RETURN_IF(reshaped_output_dims[i] < 0, | |
| "Output dimension ", i, " is negative (", reshaped_output_dims[i], | |
| "). This may occur from excessive negative padding/slicing."); | |
| } | |
| // Compute true output dimensions | |
| for (size_t i = 0; i < data_rank; i++) { | |
| output_dims[i] += SafeInt<int64_t>(pads[i]) + pads[i + data_rank] + slices[i] + slices[i + data_rank]; | |
| ORT_RETURN_IF(output_dims[i] < 0, | |
| "Output dimension ", i, " is negative (", output_dims[i], |
| if (outputShape[axis] <= 0) { | ||
| throw new Error( | ||
| `Output dimension ${axis} is non-positive (${outputShape[axis]}). This may occur from excessive negative slicing.`, |
There was a problem hiding this comment.
This throws when a computed Slice output dimension is 0 (<= 0). Slice can validly produce empty outputs (e.g., start === end), and ORT generally allows 0-sized dimensions. Consider only rejecting negative dimensions so empty slices don’t throw unexpectedly.
| if (outputShape[axis] <= 0) { | |
| throw new Error( | |
| `Output dimension ${axis} is non-positive (${outputShape[axis]}). This may occur from excessive negative slicing.`, | |
| if (outputShape[axis] < 0) { | |
| throw new Error( | |
| `Output dimension ${axis} is negative (${outputShape[axis]}). This may occur from excessive negative slicing.`, |
| if (outputShape[normalizedAxes[i]] <= 0) { | ||
| throw new Error( | ||
| `Output dimension ${normalizedAxes[i]} is non-positive (${outputShape[normalizedAxes[i]]}). This may occur from excessive negative slicing.`, |
There was a problem hiding this comment.
This new validation throws when the Slice output dimension is 0 (<= 0). In ONNX it’s valid for Slice to return an empty tensor (0-sized dimension). Consider changing the check to only reject negative dimensions so valid empty slices don’t error.
| if (outputShape[normalizedAxes[i]] <= 0) { | |
| throw new Error( | |
| `Output dimension ${normalizedAxes[i]} is non-positive (${outputShape[normalizedAxes[i]]}). This may occur from excessive negative slicing.`, | |
| if (outputShape[normalizedAxes[i]] < 0) { | |
| throw new Error( | |
| `Output dimension ${normalizedAxes[i]} is negative (${outputShape[normalizedAxes[i]]}). This may occur from excessive negative slicing.`, |
| if (nextDim <= 0) { | ||
| throw new Error( | ||
| `Output dimension ${i} is non-positive (${nextDim}). This may occur from excessive negative padding.`, |
There was a problem hiding this comment.
padShape() now throws if the computed dimension is 0 (<= 0). ORT/ONNX shapes can legally contain 0 (empty tensors), and padding can legitimately result in an empty dimension. Consider only rejecting negative dimensions so valid empty outputs don’t throw.
| if (nextDim <= 0) { | |
| throw new Error( | |
| `Output dimension ${i} is non-positive (${nextDim}). This may occur from excessive negative padding.`, | |
| if (nextDim < 0) { | |
| throw new Error( | |
| `Output dimension ${i} is negative (${nextDim}). This may occur from excessive negative padding.`, |
| return dims.map((v, i) => { | ||
| const nextDim = v + pad[i] + pad[i + rank]; | ||
| if (nextDim <= 0) { | ||
| throw new Error( | ||
| `Output dimension ${i} is non-positive (${nextDim}). This may occur from excessive negative padding.`, | ||
| ); | ||
| } | ||
| return nextDim; | ||
| }); |
There was a problem hiding this comment.
padShape() now throws when nextDim is 0 (<= 0). ONNX allows 0-sized dimensions, and Pad may legitimately produce empty outputs. Consider changing the check to only reject negative dimensions to avoid breaking valid models that include empty tensors.
The condition 'axis < -tensorRank && axis >= tensorRank' is always false (a number cannot simultaneously be less than -r and >= r). Changed to '||' so out-of-range axes are properly rejected. Affects both onnxjs and jsep utility implementations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After normalizing negative axes, validate that each axis is within [0, input_rank). Without this check, out-of-range axes could cause out-of-bounds access on the input shape array. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When split sizes come from a runtime tensor input (not an attribute), they were not validated for non-negative values. Crafted negative split sizes that sum to the correct total could pass the sum check and create tensors with negative dimensions. The attribute path already had this validation. Added non-negative check in both CPU PrepareForCompute and CUDA PrepareForComputeLocal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add checks that output dimensions are positive after computing them from user-supplied pads/slices. Without these checks, excessive negative padding or slicing could produce tensors with non-positive dimensions, leading to buffer underflows or undefined behavior. Affected operators and providers: - CPU Pad: both reshaped and true output dimensions - CUDA Pad: output dimensions in compute loop - WebGPU Pad: output dimensions in compute loop - WebGPU Slice: output dimensions after ceil computation - JS onnxjs Slice (WebGL): output shape after ends - starts - JS onnxjs padShape utility - JS jsep padShape utility - JS jsep Slice (WebGPU): output shape after ceil computation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MatMulComputeHelper::Compute skipped K-dimension validation when both inputs were 1D vectors (the vector x vector dot-product case). This allowed mismatched shapes like A=[5] and B=[1] to reach the MLAS GEMM backend, which assumed B had K elements and read past its allocation. Add the missing ORT_RETURN_IF_NOT check in the num_output_dims==0 branch to reject mismatched K dimensions before dispatch. This closes the heap-buffer-overflow (CVE-class) in MlasGemmQuantCopyPackB triggered via MatMulInteger with malformed 1D quantized inputs. Files changed: - onnxruntime/core/providers/cpu/math/matmul_helper.h - onnxruntime/test/providers/cpu/math/matmul_integer_test.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The 1D input tests were running on all EPs including CUDA, which does not support 1D quantized inputs the same way. Restrict to CPU-only using the same pattern as other EP-specific tests in this file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
e0ffdbb to
f656138
Compare
Description
Motivation and Context