Skip to content

Commit f4aa2b4

Browse files
titaiwangmsCopilot
andauthored
Validate initializer data length and guard element-count computation in contrib Range shape inference (microsoft#29265)
### Summary The `com.microsoft` Range operator's shape-inference helper read a fixed number of bytes from an initializer's `raw_data` without first checking the buffer length, and the element-count computation could pass non-finite or out-of-range values to an `int64` cast. This change adds the missing validations and aligns the shape-inference and CPU kernel paths. ### Changes - `GetFirstElement` now checks `raw_data` length is at least the element size before reading, and reads via `std::memcpy` into an aligned local. - `CalcRangeDim` and the CPU kernel `ComputeRange` now reject non-finite computed counts, handle non-positive counts before the `int64` cast, and reject counts that are not representable as `int64` (`>= 2^63`). Both paths use identical messages and semantics. - The output dimension for empty/backward ranges is clamped to 0 in shape inference to match the kernel. ### Tests Added contrib Range model-load regression tests in `range_test.cc` covering: - truncated `raw_data` for `start`, `limit`, and `delta` (double, plus float and int64 element types), - zero delta, - a finite-but-too-large element count, - an exact-size success boundary, - backward-range zero-dimension inference. Tests assert on `Status` (safe for no-exception builds) and are guarded by `#ifndef DISABLE_CONTRIB_OPS` (throwing cases additionally by `!defined(ORT_NO_EXCEPTIONS)`). 21/21 `RangeTest` cases pass locally. ### Follow-up `int16` inputs are currently supported only via `raw_data` (there is no `get_data<int16_t>` specialization for the non-raw-data path); this is left as a separate follow-up. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 930b764 commit f4aa2b4

6 files changed

Lines changed: 437 additions & 23 deletions

File tree

onnxruntime/core/graph/contrib_ops/range_schema_defs.cc

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "core/graph/constants.h"
88
#include "core/graph/op.h"
99
#include <cmath>
10+
#include <cstring>
1011
#include <type_traits>
1112

1213
namespace onnxruntime {
@@ -42,6 +43,13 @@ int64_t get_data<int64_t>(const TensorProto* shapeInitializer) {
4243
fail_shape_inference("Can not get shape initializer data!");
4344
}
4445

46+
template <>
47+
int16_t get_data<int16_t>(const TensorProto* shapeInitializer) {
48+
// ONNX packs INT16 initializer values into the int32_data repeated field.
49+
if (shapeInitializer->int32_data_size() > 0) return static_cast<int16_t>(shapeInitializer->int32_data(0));
50+
fail_shape_inference("Can not get shape initializer data!");
51+
}
52+
4553
template <>
4654
float get_data<float>(const TensorProto* shapeInitializer) {
4755
if (shapeInitializer->float_data_size() > 0) return shapeInitializer->float_data(0);
@@ -60,7 +68,13 @@ static T GetFirstElement(const TensorProto* shapeInitializer) {
6068

6169
if (utils::HasRawData(*shapeInitializer)) {
6270
const std::string& bytes = shapeInitializer->raw_data();
63-
return *reinterpret_cast<const T*>(bytes.c_str());
71+
if (bytes.size() < sizeof(T)) {
72+
fail_shape_inference("Range: raw_data size is smaller than the element size for the given data type.");
73+
}
74+
// std::string data is only char-aligned, so copy the bytes into a properly aligned value.
75+
T value;
76+
std::memcpy(&value, bytes.data(), sizeof(T));
77+
return value;
6478
}
6579
return get_data<T>(shapeInitializer);
6680
}
@@ -75,7 +89,24 @@ static int64_t CalcRangeDim(const TensorProto* startShapeInitializer,
7589
if (delta == 0) {
7690
fail_shape_inference("delta in Range operator can not be zero!");
7791
}
78-
return static_cast<int64_t>(ceil((1.0 * (limit - start)) / delta));
92+
// Mirror the CPU kernel (core/providers/cpu/generator/range.cc ComputeRange) exactly so the
93+
// two paths stay byte-consistent: clamp empty or backward ranges to 0 and promote the
94+
// operands to double before the subtraction. Keep this expression identical to the kernel.
95+
double count = ceil((static_cast<double>(limit) - static_cast<double>(start)) / static_cast<double>(delta));
96+
if (!std::isfinite(count)) {
97+
fail_shape_inference("Range: the computed number of elements is not a finite value.");
98+
}
99+
// Empty or backward ranges clamp to 0; handle the non-positive case before the cast so a
100+
// large-magnitude negative count can never reach (and overflow) the int64 conversion below.
101+
if (count <= 0) {
102+
return 0;
103+
}
104+
// static_cast<double>(INT64_MAX) rounds up to 2^63 (9223372036854775808.0), which is not
105+
// representable as int64_t, so reject any count at or above that boundary before the cast.
106+
if (count >= 9223372036854775808.0) {
107+
fail_shape_inference("Range: the computed number of elements exceeds the supported range.");
108+
}
109+
return static_cast<int64_t>(count);
79110
}
80111

81112
static int64_t CalcResultDim(const TensorProto* startShapeInitializer,

onnxruntime/core/providers/cpu/generator/range.cc

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@
66
#include <cmath>
77

88
#include "core/providers/op_kernel_type_control.h"
9-
// TODO: fix the warnings
10-
#if defined(_MSC_VER) && !defined(__clang__)
11-
#pragma warning(disable : 26451)
12-
#endif
9+
1310
namespace onnxruntime {
1411

1512
namespace op_kernel_type_control {
@@ -80,9 +77,26 @@ static Status ComputeRange(
8077
if (delta == T{0}) {
8178
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "delta in Range operator can not be zero!");
8279
}
83-
int64_t n = static_cast<int64_t>(ceil((1.0 * (limit - start)) / delta));
84-
if (n <= 0)
85-
n = 0;
80+
// Compute the element count in double, mirroring the shape-inference path
81+
// (core/graph/contrib_ops/range_schema_defs.cc CalcRangeDim) exactly. The operands are
82+
// promoted to double before the subtraction so integral inputs cannot overflow in T.
83+
double count = ceil((static_cast<double>(limit) - static_cast<double>(start)) / static_cast<double>(delta));
84+
if (!std::isfinite(count)) {
85+
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
86+
"Range: the computed number of elements is not a finite value.");
87+
}
88+
// Empty or backward ranges clamp to 0; handle the non-positive case before the cast so a
89+
// large-magnitude negative count can never reach (and overflow) the int64 conversion.
90+
int64_t n = 0;
91+
if (count > 0) {
92+
// static_cast<double>(INT64_MAX) rounds up to 2^63 (9223372036854775808.0), which is not
93+
// representable as int64_t, so reject any count at or above that boundary before the cast.
94+
if (count >= 9223372036854775808.0) {
95+
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
96+
"Range: the computed number of elements exceeds the supported range.");
97+
}
98+
n = static_cast<int64_t>(count);
99+
}
86100
TensorShape shape = {n};
87101
T* y = ctx->Output(0, shape)->MutableData<T>();
88102
for (int64_t i = 0; i < n; ++i) {

onnxruntime/core/providers/cuda/generator/range.cc

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
#include <cmath>
5+
46
#include "core/providers/shared_library/provider_api.h"
57
#include "core/providers/cuda/cuda_common.h"
68
#include "range.h"
@@ -87,11 +89,28 @@ static Status ComputeRange(cudaStream_t stream, OpKernelContext* ctx) {
8789
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "delta in Range operator can not be zero!");
8890
}
8991

90-
double num = (static_cast<double>(limit) - static_cast<double>(start)) / static_cast<double>(delta);
91-
int count = static_cast<int>(ceil(num));
92-
if (count <= 0)
93-
count = 0;
94-
TensorShape shape = {static_cast<int64_t>(count)};
92+
// Compute the element count in double, mirroring the CPU kernel's guard structure and error
93+
// messages (core/providers/cpu/generator/range.cc ComputeRange) and the shape-inference path
94+
// (core/graph/contrib_ops/range_schema_defs.cc CalcRangeDim). The operands are
95+
// promoted to double before the subtraction so integral inputs cannot overflow in T.
96+
double num = ceil((static_cast<double>(limit) - static_cast<double>(start)) / static_cast<double>(delta));
97+
if (!std::isfinite(num)) {
98+
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
99+
"Range: the computed number of elements is not a finite value.");
100+
}
101+
// Empty or backward ranges clamp to 0; handle the non-positive case before the cast so a
102+
// large-magnitude negative count can never reach (and overflow) the int64 conversion.
103+
int64_t count = 0;
104+
if (num > 0) {
105+
// static_cast<double>(INT64_MAX) rounds up to 2^63 (9223372036854775808.0), which is not
106+
// representable as int64_t, so reject any count at or above that boundary before the cast.
107+
if (num >= 9223372036854775808.0) {
108+
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
109+
"Range: the computed number of elements exceeds the supported range.");
110+
}
111+
count = static_cast<int64_t>(num);
112+
}
113+
TensorShape shape = {count};
95114
T* y = ctx->Output(0, shape)->MutableData<T>();
96115

97116
if (count > 0) {

onnxruntime/core/providers/cuda/generator/range_impl.cu

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,34 @@ namespace onnxruntime {
1313
namespace cuda {
1414

1515
template <typename T>
16-
__global__ void RangeKernel(const T start, const T delta, const int count, T* output) {
17-
int index = blockIdx.x * blockDim.x + threadIdx.x;
18-
if (index < count) {
19-
output[index] = start + delta * index;
16+
__global__ void RangeKernel(const T start, const T delta, const int64_t count, T* output) {
17+
// Use a 64-bit grid-stride loop so counts larger than the launch grid (and larger than
18+
// INT_MAX) are handled correctly without index truncation or grid-dimension overflow.
19+
int64_t index = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
20+
int64_t stride = static_cast<int64_t>(gridDim.x) * blockDim.x;
21+
for (int64_t i = index; i < count; i += stride) {
22+
output[i] = start + delta * static_cast<T>(i);
2023
}
2124
}
2225

2326
template <typename T>
24-
Status RangeImpl(cudaStream_t stream, const T start, const T delta, const int count, T* output) {
25-
constexpr int block_size = 256;
26-
int grid_size = (count + block_size - 1) / block_size;
27+
Status RangeImpl(cudaStream_t stream, const T start, const T delta, const int64_t count, T* output) {
28+
if (count <= 0) {
29+
return Status::OK();
30+
}
31+
constexpr int block_size = GridDim::maxThreadsPerBlock;
32+
int64_t num_blocks = (count + block_size - 1) / block_size;
33+
// CUDA limits the x-dimension of the launch grid to 2^31 - 1 blocks. Cap the grid to that
34+
// maximum; the grid-stride loop in RangeKernel covers any remaining elements when the count
35+
// requires more blocks than can be launched at once.
36+
constexpr int64_t kMaxGridDimX = 2147483647;
37+
int grid_size = static_cast<int>(num_blocks < kMaxGridDimX ? num_blocks : kMaxGridDimX);
2738
RangeKernel<T><<<grid_size, block_size, 0, stream>>>(start, delta, count, output);
2839
return CUDA_CALL(cudaGetLastError());
2940
}
3041

3142
#define SPECIALIZED_IMPL(T) \
32-
template Status RangeImpl<T>(cudaStream_t stream, const T start, const T delta, const int count, T* output);
43+
template Status RangeImpl<T>(cudaStream_t stream, const T start, const T delta, const int64_t count, T* output);
3344

3445
SPECIALIZED_IMPL(int16_t)
3546
SPECIALIZED_IMPL(int32_t)

onnxruntime/core/providers/cuda/generator/range_impl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace onnxruntime {
88
namespace cuda {
99

1010
template <typename T>
11-
Status RangeImpl(cudaStream_t stream, const T start, const T delta, const int count, T* output);
11+
Status RangeImpl(cudaStream_t stream, const T start, const T delta, const int64_t count, T* output);
1212

1313
} // namespace cuda
1414
} // namespace onnxruntime

0 commit comments

Comments
 (0)