From d06d60d6d752e432a3a1424dc5197d366300775b Mon Sep 17 00:00:00 2001 From: NEX Date: Sat, 5 Sep 2026 13:12:18 +0800 Subject: [PATCH] Fix out-of-bounds memory access in CIRCULAR_BUFFER with zero slot count CircularBufferEval computes the buffer shift size as (num_slots - 1) * depth, where num_slots is output->dims->data[1]. CircularBufferPrepare did not validate this dimension; with num_slots == 0 the size becomes negative and wraps to a huge size_t when passed to memmove, causing an out-of-bounds read/write on the output tensor. All dimensions come from the model file, so a crafted model can trigger this. Reject output slot counts < 1 in Prepare and add a regression test that fails without the fix and passes with it. BUG=tensorflow/tflite-micro#3695 --- .../micro/kernels/circular_buffer_common.cc | 6 +++++ .../micro/kernels/circular_buffer_test.cc | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/tensorflow/lite/micro/kernels/circular_buffer_common.cc b/tensorflow/lite/micro/kernels/circular_buffer_common.cc index bf45c06f61c..afe28938086 100644 --- a/tensorflow/lite/micro/kernels/circular_buffer_common.cc +++ b/tensorflow/lite/micro/kernels/circular_buffer_common.cc @@ -53,6 +53,12 @@ TfLiteStatus CircularBufferPrepare(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE_EQ(context, 1, input->dims->data[1]); TF_LITE_ENSURE_EQ(context, input->dims->data[2], output->dims->data[2]); TF_LITE_ENSURE_EQ(context, output->dims->data[3], input->dims->data[3]); + // The number of slots in the circular buffer (output->dims->data[1]) must be + // at least 1. CircularBufferEval computes the shift size as + // (num_slots - 1) * depth and passes it to memmove; a num_slots of 0 makes + // that size negative, which wraps to a huge size_t and causes an + // out-of-bounds read/write on the output tensor. + TF_LITE_ENSURE(context, output->dims->data[1] >= 1); TF_LITE_ENSURE_TYPES_EQ(context, input->type, output->type); diff --git a/tensorflow/lite/micro/kernels/circular_buffer_test.cc b/tensorflow/lite/micro/kernels/circular_buffer_test.cc index 1b657357003..48b1ab9e1fe 100644 --- a/tensorflow/lite/micro/kernels/circular_buffer_test.cc +++ b/tensorflow/lite/micro/kernels/circular_buffer_test.cc @@ -256,4 +256,27 @@ TEST(CircularBufferTest, Reset) { EXPECT_EQ(kTfLiteOk, runner.Invoke()); } +TEST(CircularBufferTest, ZeroNumSlotsRejectedInPrepare) { + // With num_slots == 0, CircularBufferEval computes the buffer shift size as + // (num_slots - 1) * depth, which is negative and wraps to a huge size_t when + // passed to memmove, causing an out-of-bounds read/write on the output + // tensor. Since all dimensions come from the model, Prepare must reject a + // zero slot count. + int8_t in = 0, out = 0; + int in_dims[] = {4, 1, 1, 1, 1}, out_dims[] = {4, 1, 0, 1, 1}; + TfLiteTensor tensors[] = { + tflite::testing::CreateQuantizedTensor( + &in, tflite::testing::IntArrayFromInts(in_dims), 1, 0), + tflite::testing::CreateQuantizedTensor( + &out, tflite::testing::IntArrayFromInts(out_dims), 1, 0), + }; + int ins[] = {1, 0}, outs[] = {1, 1}; + tflite::micro::KernelRunner runner( + *tflite::Register_CIRCULAR_BUFFER(), tensors, 2, + tflite::testing::IntArrayFromInts(ins), + tflite::testing::IntArrayFromInts(outs), nullptr); + + EXPECT_NE(kTfLiteOk, runner.InitAndPrepare()); +} + TF_LITE_MICRO_TESTS_MAIN