Skip to content

Commit c15964f

Browse files
authored
[Kernels] Add validations to catch misformed edge cases (#23113)
## Summary Harden four kernels against malformed shapes that previously caused out-of-bounds writes or silent truncation: - `cat.out`: reject concatenations whose summed dim overflows 32-bit `SizesType` instead of truncating the output size. - `unfold_copy.out`: reject rank-16 input, which needs 17 shape entries for a 16-slot buffer (same guard the sibling `view_as_real` kernel uses). - `choose_qparams_per_token_asymmetric`: reject scalar input, which indexed `output_sizes[dim - 1]` at `-1`. - Optimized `grid_sampler_2d`: validate and resize `out` via the portable util before the NEON fast path, which sized its writes from `input`/`grid` alone. Each fix carries a regression test, and the grid_sampler suite now also runs against the optimized kernels. Commits are stacked one per fix in the order above. This PR was authored with Muse Code. ## Test Plan ``` cmake --build build --target portable_kernels_test quantized_kernels_test optimized_kernels_test ./build/kernels/test/portable_kernels_test ./build/kernels/test/quantized_kernels_test ./build/kernels/test/optimized_kernels_test ``` 1554 / 75 / 1564 tests pass with no failures (arm64, exercising the NEON fast path). Each new test was verified to fail with its fix reverted. `lintrunner` is clean on all touched files.
1 parent 57078b4 commit c15964f

16 files changed

Lines changed: 207 additions & 9 deletions

File tree

‎backends/cadence/fusion_g3/operators/op_cat.cpp‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,12 @@ Tensor& cat_out(
4848

4949
Tensor::SizesType expected_out_size[kTensorDimensionLimit];
5050
size_t expected_out_dim = 0;
51-
torch::executor::get_cat_out_target_size(
52-
tensors, dim, expected_out_size, &expected_out_dim);
51+
ET_KERNEL_CHECK(
52+
ctx,
53+
torch::executor::get_cat_out_target_size(
54+
tensors, dim, expected_out_size, &expected_out_dim),
55+
InvalidArgument,
56+
out);
5357

5458
ET_KERNEL_CHECK(
5559
ctx,

‎backends/cadence/hifi/operators/op_cat.cpp‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ Tensor& cat_out(
4040
Tensor::SizesType
4141
expected_out_size[executorch::runtime::kTensorDimensionLimit];
4242
size_t expected_out_dim = 0;
43-
get_cat_out_target_size(tensors, dim, expected_out_size, &expected_out_dim);
43+
ET_KERNEL_CHECK(
44+
ctx,
45+
get_cat_out_target_size(
46+
tensors, dim, expected_out_size, &expected_out_dim),
47+
InvalidArgument,
48+
out);
4449

4550
ET_KERNEL_CHECK(
4651
ctx,

‎kernels/optimized/cpu/op_grid_sampler_2d.cpp‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
// targets, and any unsupported interpolation/padding/layout combination,
2929
// delegate to the portable kernel.
3030

31+
#include <executorch/kernels/portable/cpu/util/grid_sampler_2d_util.h>
3132
#include <executorch/runtime/kernel/kernel_includes.h>
3233

3334
#ifdef __aarch64__
@@ -335,6 +336,15 @@ Tensor& opt_grid_sampler_2d_out(
335336
int64_t padding_mode,
336337
bool align_corners,
337338
Tensor& out) {
339+
// Validate args and resize out exactly like the portable kernel. The NEON
340+
// paths below size their writes from input/grid alone, so out must already
341+
// match [N, C, H_out, W_out] before the fast path is reached.
342+
ET_KERNEL_CHECK_MSG(
343+
ctx,
344+
check_grid_sampler_2d_args_and_resize_out(input, grid, out) == Error::Ok,
345+
InvalidArgument,
346+
out,
347+
"Failed to validate arguments and resize output tensor");
338348
// The NEON paths index input/grid/out directly assuming a contiguous NCHW
339349
// default-dim-order layout — no use of .strides() or .dim_order(). Fall
340350
// back to portable for anything else.

‎kernels/portable/cpu/op_cat.cpp‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ Tensor& cat_out(
3434

3535
Tensor::SizesType expected_out_size[kTensorDimensionLimit];
3636
size_t expected_out_dim = 0;
37-
get_cat_out_target_size(tensors, dim, expected_out_size, &expected_out_dim);
37+
ET_KERNEL_CHECK(
38+
ctx,
39+
get_cat_out_target_size(
40+
tensors, dim, expected_out_size, &expected_out_dim),
41+
InvalidArgument,
42+
out);
3843

3944
ET_KERNEL_CHECK(
4045
ctx,

‎kernels/portable/cpu/op_unfold_copy.cpp‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ Tensor& unfold_copy_out(
2525
if (dim < 0) {
2626
dim += nonzero_dim(self);
2727
}
28+
// Unfold adds one output dim; the target-size buffer holds
29+
// kTensorDimensionLimit entries.
30+
ET_KERNEL_CHECK_MSG(
31+
ctx,
32+
static_cast<size_t>(self.dim()) < kTensorDimensionLimit,
33+
InvalidArgument,
34+
out,
35+
"Output size buffer is too small. Expected at least %zd, got %zu",
36+
self.dim() + 1,
37+
kTensorDimensionLimit);
2838
// Calculate output size
2939
// @lint-ignore CLANGTIDY facebook-hte-CArray
3040
Tensor::SizesType expected_output_size[kTensorDimensionLimit];

‎kernels/portable/cpu/util/copy_ops_util.cpp‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <c10/util/irange.h>
1111
#include <cstring>
12+
#include <limits>
1213

1314
#include <executorch/kernels/portable/cpu/util/copy_ops_util.h>
1415
#include <executorch/runtime/core/exec_aten/util/dim_order_util.h>
@@ -127,7 +128,7 @@ bool check_cat_args(
127128
return true;
128129
}
129130

130-
void get_cat_out_target_size(
131+
bool get_cat_out_target_size(
131132
executorch::aten::ArrayRef<Tensor> tensors,
132133
int64_t dim,
133134
executorch::aten::SizesType* out_sizes,
@@ -139,6 +140,13 @@ void get_cat_out_target_size(
139140
size_t cat_dim_size = 0;
140141
for (const auto i : c10::irange(tensors.size())) {
141142
if (tensors[i].numel() > 0) {
143+
ET_CHECK_OR_RETURN_FALSE(
144+
static_cast<size_t>(tensors[i].size(dim)) <=
145+
static_cast<size_t>(
146+
std::numeric_limits<executorch::aten::SizesType>::max()) -
147+
cat_dim_size,
148+
"Concatenated size along dim %" PRId64 " overflows SizesType",
149+
dim);
142150
cat_dim_size += tensors[i].size(dim);
143151
}
144152
if (tensors[i].dim() != 1 || tensors[i].numel() != 0) {
@@ -152,9 +160,10 @@ void get_cat_out_target_size(
152160
if (static_cast<int64_t>(d) != dim) {
153161
out_sizes[d] = tensors[ref_i].size(d);
154162
} else {
155-
out_sizes[d] = cat_dim_size;
163+
out_sizes[d] = static_cast<executorch::aten::SizesType>(cat_dim_size);
156164
}
157165
}
166+
return true;
158167
}
159168
bool check_expand_copy_args(
160169
const Tensor& input,

‎kernels/portable/cpu/util/copy_ops_util.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ bool check_cat_args(
106106
int64_t dim,
107107
Tensor& out);
108108

109-
void get_cat_out_target_size(
109+
bool get_cat_out_target_size(
110110
executorch::aten::ArrayRef<Tensor> tensors,
111111
int64_t dim,
112112
executorch::aten::SizesType* out_sizes,

‎kernels/portable/cpu/util/targets.bzl‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ def define_common_targets():
412412
deps = [
413413
"//executorch/runtime/kernel:kernel_includes",
414414
],
415-
visibility = ["//executorch/kernels/portable/cpu/..."],
415+
visibility = ["//executorch/kernels/portable/cpu/...", "//executorch/kernels/optimized/cpu/..."],
416416
)
417417

418418
# Utility functions that can be used by operators that perform reduction

‎kernels/quantized/cpu/op_choose_qparams.cpp‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@ std::tuple<Tensor&, Tensor&> choose_qparams_per_token_asymmetric_out(
281281
ScalarType dtype,
282282
Tensor& scale_out,
283283
Tensor& zero_point_out) {
284+
ET_CHECK_MSG(
285+
input.dim() >= 1,
286+
"choose_qparams_per_token: input must have rank >= 1, got %zd",
287+
input.dim());
284288
int64_t quant_min = -128;
285289
int64_t quant_max = 127;
286290
executorch::aten::SizesType output_sizes[kTensorDimensionLimit];

‎kernels/quantized/test/op_choose_qparams_test.cpp‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,21 @@ TEST(
260260
EXPECT_EQ(zero_point_out.const_data_ptr<int64_t>()[i], nudged_zero_point);
261261
}
262262
}
263+
264+
TEST(OpChooseQparamsPerTokenAsymmetricTensorOutTest, ScalarInputDies) {
265+
et_pal_init();
266+
TensorFactory<ScalarType::Float> tf_float;
267+
TensorFactory<ScalarType::Double> tf_double;
268+
TensorFactory<ScalarType::Long> tf_long;
269+
270+
// Per-token scales index input.dim() - 1; a scalar input must be rejected
271+
// instead of indexing before the output-shape buffer.
272+
Tensor input = tf_float.make({}, {1.0});
273+
Tensor scale_out = tf_double.zeros({1});
274+
Tensor zero_point_out = tf_long.zeros({1});
275+
276+
ET_EXPECT_DEATH(
277+
choose_qparams_per_token_asymmetric_out(
278+
input, ScalarType::Float, scale_out, zero_point_out),
279+
"must have rank");
280+
}

0 commit comments

Comments
 (0)