Fix missing bounds checks in subgraph API functions#9845
Fix missing bounds checks in subgraph API functions#9845mohammadmseet-hue wants to merge 2 commits intogoogle:masterfrom
Conversation
Add bounds validation for user-controlled parameters that were used as loop bounds or array indices without checking: - xnn_define_static_expand_dims: num_new_axes was used to write into int32_t ynn_axes[XNN_MAX_TENSOR_DIMS] without bounds check. Add check that num_new_axes <= XNN_MAX_TENSOR_DIMS. - xnn_define_static_reduce: num_reduction_axes was used to write into int64_t signed_reduction_axes[XNN_MAX_TENSOR_DIMS] without bounds check. Add check that num_reduction_axes <= XNN_MAX_TENSOR_DIMS. - ynn_define_broadcast: axes were passed to axis_to_slinky_dim without validate_axis, producing out-of-range bitset indices. Add validate_axis call. - ynn_define_broadcast_like: same missing validate_axis. Add it. - ynn_define_reduce: same missing validate_axis. Add it. - ynn_define_fuse_dims: axes[i]+1 was passed to axis_to_slinky_dim but only axes[i] was validated. When axes[i]=rank-1, axes[i]+1=rank produces an invalid index. Add validate_axis for axes[i]+1.
| YNN_RETURN_IF_ERROR( | ||
| validate_axis("broadcast_like", "input", input.rank(), axes[i])); | ||
| const int axis = axis_to_slinky_dim(input.rank(), axes[i]); | ||
| if (axis < template_value.rank()) { |
There was a problem hiding this comment.
Isn't this validated right here?
In general we should ignore "no-op" dimensions, not error out on them. In the case of broadcasting, broadcasting a dimension past the last dimension is a no-op, not an error.
I think this is handled here, but not in the other case you identified.
|
|
||
| ynn::axes_set axes_set; | ||
| for (size_t i = 0; i < num_axes; ++i) { | ||
| YNN_RETURN_IF_ERROR( |
There was a problem hiding this comment.
It should be a no-op if the axis is out of bounds, not an error:
const int axis = axis_to_slinky_dim(input.rank(), axes[i]);
if (axis < input.rank()) {
axes_set[axis_to_slinky_dim(input.rank(), axes[i])] = true;
}
| YNN_RETURN_IF_ERROR( | ||
| validate_axis("reduce", "input_a", a.rank(), axes[i])); | ||
| const int axis = axis_to_slinky_dim(a.rank(), axes[i]); | ||
| if (axis < a.rank()) { |
There was a problem hiding this comment.
This looks like it is already validated here.
| uint32_t input_id, uint32_t output_id, | ||
| uint32_t flags) { | ||
| if (num_new_axes > XNN_MAX_TENSOR_DIMS) { | ||
| return xnn_status_invalid_parameter; |
| uint32_t input_id, uint32_t output_id, | ||
| uint32_t flags) { | ||
| if (num_reduction_axes > XNN_MAX_TENSOR_DIMS) { | ||
| return xnn_status_invalid_parameter; |
There was a problem hiding this comment.
This should report an error:
YNN_LOG_ERROR() << ...
| // for (size_t i = 0; i < num_new_axes; ++i) { | ||
| // ynn_axes[i] = new_axes[i]; // OOB when num_new_axes > 6 | ||
| // } | ||
| TEST(OobWrite, StaticExpandDimsStackOverflow) { |
There was a problem hiding this comment.
Please remove these tests before submitting, we will cover this kind of issue with testing random graphs, and I think that it doesn't make sense to only cover random small subsets of error handling codepaths.
…remove PoC tests - broadcast.cc: Replace validate_axis with no-op guard for out-of-bounds axes - broadcast_like.cc: Remove redundant validate_axis (already guarded) - reduce.cc: Remove redundant validate_axis (already guarded) - subgraph.cc: Add YNN_LOG_ERROR() messages for bounds check failures - Remove oob_write_poc.cc test and BUILD entry per reviewer request Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Multiple public API functions in ynnpack accept user-controlled count/axis parameters
that are used as loop bounds or array indices without bounds checking, causing stack
buffer overflows:
xnn_define_static_expand_dims(subgraph.cc):num_new_axesused as loop bound to write intoint32_t ynn_axes[XNN_MAX_TENSOR_DIMS](size 6). ASAN-confirmed stack-buffer-overflow withnum_new_axes > 6.xnn_define_static_reduce(subgraph.cc):num_reduction_axesused as loop bound to write intoint64_t signed_reduction_axes[XNN_MAX_TENSOR_DIMS](size 6). Same pattern.ynn_define_broadcast(broadcast.cc): Missingvalidate_axiscall. Out-of-range axes passed directly toaxis_to_slinky_dim, producing negative results used asbitset<10>indices.ynn_define_broadcast_like(broadcast_like.cc): Same missing validation.ynn_define_reduce(reduce.cc): Same missing validation.ynn_define_fuse_dims(copy.cc): Off-by-one —axes[i]is validated butaxes[i]+1is passed toaxis_to_slinky_dim. Whenaxes[i] = rank-1,axes[i]+1 = rankproduces index -1.Fixes
num_new_axes > XNN_MAX_TENSOR_DIMS/num_reduction_axes > XNN_MAX_TENSOR_DIMSbounds checks before stack array writes.validate_axiscalls beforeaxis_to_slinky_dimin broadcast, broadcast_like, and reduce.validate_axisforaxes[i]+1in fuse_dims.ASAN trace (xnn_define_static_expand_dims, before fix)
Test plan
bazel test //ynnpack/subgraph/test:oob_write_poc --copt=-fsanitize=address --linkopt=-fsanitize=addresspasses