Skip to content

Fix begin/size validation in slice to prevent OOB reads / heap OOB writes#115462

Open
mohammadmseet-hue wants to merge 1 commit intotensorflow:masterfrom
mohammadmseet-hue:fix-slice-bounds
Open

Fix begin/size validation in slice to prevent OOB reads / heap OOB writes#115462
mohammadmseet-hue wants to merge 1 commit intotensorflow:masterfrom
mohammadmseet-hue:fix-slice-bounds

Conversation

@mohammadmseet-hue
Copy link
Copy Markdown

Summary

slice::CalculateOutputShapeVector<T> reads begin and size values from attacker-controlled int32 / int64 input tensors. The existing validation has three independent gaps:

Gap 1 — begin[idx] is never validated

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 the input buffer. With a begin > input_dim the same computation underflows or overshoots.

Gap 2 — begin + size overflow bypasses the bound check

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.

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 into output_shape_vector and on into ResizeTensor, producing an undersized buffer that the kernel later overruns. Result: heap-buffer-overflow write.

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_t 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.
   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

File Lines
tensorflow/lite/kernels/slice.cc +31 / -3

Test plan

  • No public API change.
  • No new dependencies.
  • Existing slice_test tests pass against the patched kernel.
  • Happy to add regression tests for the negative begin / int64 wrap / int64 narrowing cases on request.

…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.
@google-ml-butler google-ml-butler bot added the size:S CL Change Size: Small label Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S CL Change Size: Small

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants