Fix begin/size validation in slice to prevent OOB reads / heap OOB writes#115462
Open
mohammadmseet-hue wants to merge 1 commit intotensorflow:masterfrom
Open
Fix begin/size validation in slice to prevent OOB reads / heap OOB writes#115462mohammadmseet-hue wants to merge 1 commit intotensorflow:masterfrom
mohammadmseet-hue wants to merge 1 commit intotensorflow:masterfrom
Conversation
…ites
slice::CalculateOutputShapeVector<T> reads `begin` and `size` values
from attacker-controlled int32 / int64 input tensors. The existing
validation has three independent gaps:
1. begin[idx] is never validated to be >= 0 or <= input_dim. With a
negative begin and size_value == -1, the code computes
`size_value = input_dim - begin` which is larger than input_dim,
producing an output shape that asks the kernel Eval path to read
past the end of input. With a begin > input_dim the same
computation underflows or overshoots.
2. The `else` branch checks `input_dim < begin + size` in template-T
arithmetic, where T can be int64. With begin = INT64_MAX and
size = 1, the addition signed-overflows to INT64_MIN and the
check `input_dim < INT64_MIN` is false → bypass. The unchecked
begin then propagates to GetBeginAndSizeVectors and into
reference_ops::Slice as `op_params.begin[i]`, where it is used
to compute `input_offset + begin * stride` for the read pointer.
Result: OOB read on the input buffer.
3. The final `static_cast<int>(size_value)` silently truncates int64
to int. A large positive int64 size value becomes a small or
negative int written into the output_shape_vector and on into
ResizeTensor, producing an undersized buffer that the kernel
later overruns.
A malicious .tflite with crafted begin / size constant tensors can
therefore drive any of these into a heap-buffer-overflow read or
write, depending on which branch is taken.
Fix
---
* Validate begin in [0, input_dim] before either branch.
* Compute `begin + size` in int64 in the else branch so the
comparison cannot wrap.
* Bounds-check the final size_value against the int range used by
output_shape_vector before the static_cast.
Drop <stdint.h> in favor of <cstdint> per the style review on
PR tensorflow#115031.
This is the same family of fix as PRs tensorflow#115031, tensorflow#115452, tensorflow#115453,
tensorflow#115455, tensorflow#115456, tensorflow#115457, tensorflow#115458, tensorflow#115459, tensorflow#115460. Twelve tflite
kernels in the CheckedInt incomplete-pattern hunt now share the same
bug class.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
slice::CalculateOutputShapeVector<T>readsbeginandsizevalues from attacker-controlled int32 / int64 input tensors. The existing validation has three independent gaps:Gap 1 —
begin[idx]is never validatedbegin[idx]is never validated to be>= 0or<= input_dim. With a negativebeginandsize_value == -1, the code computessize_value = input_dim - beginwhich is larger thaninput_dim, producing an output shape that asks the kernelEvalpath to read past the end of the input buffer. With abegin > input_dimthe same computation underflows or overshoots.Gap 2 —
begin + sizeoverflow bypasses the bound checkThe
elsebranch checksinput_dim < begin + sizein template-Tarithmetic, whereTcan beint64. Withbegin = INT64_MAXandsize = 1, the addition signed-overflows toINT64_MINand the checkinput_dim < INT64_MINis false → bypass. The uncheckedbeginthen propagates toGetBeginAndSizeVectorsand intoreference_ops::Sliceasop_params.begin[i], where it is used to computeinput_offset + begin * stridefor the read pointer. Result: OOB read on the input buffer.Gap 3 — int64 → int truncation in the final cast
The final
static_cast<int>(size_value)silently truncates int64 to int. A large positive int64 size value becomes a small or negative int written intooutput_shape_vectorand on intoResizeTensor, producing an undersized buffer that the kernel later overruns. Result: heap-buffer-overflow write.A malicious
.tflitewith craftedbegin/sizeconstant tensors can therefore drive any of these into a heap-buffer-overflow read or write, depending on which branch is taken.Fix
beginin[0, input_dim]before either branch.begin + sizeinint64_tin theelsebranch so the comparison cannot wrap.size_valueagainst theintrange used byoutput_shape_vectorbefore thestatic_cast.for (int idx = 0; idx < NumDimensions(input); ++idx) { + const int input_dim = SizeOfDimension(input, idx); + const T begin_value_t = GetTensorData<T>(begin)[idx]; T size_value = GetTensorData<T>(size)[idx]; + + if (begin_value_t < 0 || begin_value_t > input_dim) { + TF_LITE_KERNEL_LOG(context, \"Slice: begin value out of range at dim %d\", idx); + return kTfLiteError; + } + const int begin_value = static_cast<int>(begin_value_t); + if (size_value < 0) { if (size_value != -1) { ... return kTfLiteError; } - size_value = SizeOfDimension(input, idx) - GetTensorData<T>(begin)[idx]; + size_value = input_dim - begin_value; } else { - if (SizeOfDimension(input, idx) < GetTensorData<T>(begin)[idx] + size_value) { + const int64_t end = + static_cast<int64_t>(begin_value) + static_cast<int64_t>(size_value); + if (end < 0 || end > input_dim) { TF_LITE_KERNEL_LOG(context, \"Invalid begin and size.\"); return kTfLiteError; } } + if (size_value < 0 || + static_cast<int64_t>(size_value) > std::numeric_limits<int>::max()) { + TF_LITE_KERNEL_LOG(context, \"Slice: size value out of int range at dim %d\", idx); + return kTfLiteError; + } output_shape_vector->push_back(static_cast<int>(size_value)); }Relationship to other PRs in this series
Same family of fix as PRs #115031, #115452, #115453, #115455, #115456, #115457, #115458, #115459, #115460. Twelve tflite kernels in the CheckedInt incomplete-pattern hunt now share the same bug class.
Files changed
tensorflow/lite/kernels/slice.ccTest plan
slice_testtests pass against the patched kernel.