From f0b3c20c24f34d88c98a7d48b6fb654f9fb2874f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adil=20Burak=20=C5=9Een?= <56400880+adilburaksen@users.noreply.github.com> Date: Fri, 19 Jun 2026 03:04:27 +0300 Subject: [PATCH] Validate STACKER init parameters against overflow and tensor sizes StackerInit reads num_channels, stacker_left_context, stacker_right_context and stacker_step from the init flexbuffer and computes buffer_size = num_channels * (left + right + 1) and step_size = num_channels * stacker_step. StackerPrepare validated only tensor ranks and types, never these parameters. Negative or overflowing values yield a zero/undersized circular buffer that StackerEval then writes and reads out of bounds via CircularBufferWrite/CircularBufferExtend (num_channels values are copied per frame and buffer_size values are read into the output). In release builds the bounds assert in CircularBufferWrite is compiled out, so this is an out-of-bounds access; confirmed with AddressSanitizer. Validate num_channels > 0, the contexts >= 0, stacker_step > 0, num_channels <= input size, that the buffer_size product does not overflow, and that the output is large enough, plus a regression test. --- signal/micro/kernels/stacker.cc | 27 +++++++++++++++++++++++++++ signal/micro/kernels/stacker_test.cc | 21 +++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/signal/micro/kernels/stacker.cc b/signal/micro/kernels/stacker.cc index fc1a4a3769d..c9a4a91f6dd 100644 --- a/signal/micro/kernels/stacker.cc +++ b/signal/micro/kernels/stacker.cc @@ -112,9 +112,36 @@ TfLiteStatus StackerPrepare(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE_TYPES_EQ(context, output->type, kTfLiteInt16); TF_LITE_ENSURE_TYPES_EQ(context, output_valid->type, kTfLiteBool); + const int input_size = ElementCount(*input->dims); + const int output_size = ElementCount(*output->dims); + micro_context->DeallocateTempTfLiteTensor(input); micro_context->DeallocateTempTfLiteTensor(output); micro_context->DeallocateTempTfLiteTensor(output_valid); + + // Validate the init-flexbuffer parameters before they are used to size the + // circular buffer and to copy data. Without this, negative or overflowing + // values produce a zero/undersized buffer that StackerEval then writes and + // reads out of bounds (CircularBufferWrite copies num_channels values per + // frame and the output receives buffer_size values). + auto* params = reinterpret_cast(node->user_data); + TF_LITE_ENSURE(context, params != nullptr); + TF_LITE_ENSURE(context, params->num_channels > 0); + TF_LITE_ENSURE(context, params->stacker_left_context >= 0); + TF_LITE_ENSURE(context, params->stacker_right_context >= 0); + TF_LITE_ENSURE(context, params->stacker_step > 0); + TF_LITE_ENSURE(context, params->num_channels <= input_size); + + // Recompute buffer_size in 64-bit to confirm the products did not overflow, + // then require the output to be large enough to receive it. + const int64_t frames = static_cast(params->stacker_left_context) + + params->stacker_right_context + 1; + const int64_t buffer_size = + static_cast(params->num_channels) * frames; + TF_LITE_ENSURE(context, + buffer_size == static_cast(params->buffer_size)); + TF_LITE_ENSURE(context, buffer_size <= output_size); + return kTfLiteOk; } diff --git a/signal/micro/kernels/stacker_test.cc b/signal/micro/kernels/stacker_test.cc index bd004a9241a..61869d4b5b0 100644 --- a/signal/micro/kernels/stacker_test.cc +++ b/signal/micro/kernels/stacker_test.cc @@ -238,4 +238,25 @@ TEST(StackerTest, StackerTestReset10ChannelStep2_2ndTest) { g_gen_data_size_stacker_10_channels_step_2); } + +// Regression test: a crafted init flexbuffer with stacker_right_context = -1 makes +// buffer_size = num_channels * (left + right + 1) = 0. Before the fix this produced a +// zero-capacity circular buffer that StackerEval wrote/read out of bounds; now +// StackerPrepare rejects the invalid parameters. +TEST(StackerTest, StackerInvalidParamsRejected) { + int input_shape[] = {1, 8}; + int output_shape[] = {1, 8}; + int output_ready_shape[] = {0}; + const int16_t input[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + int16_t output[8]; + bool output_ready = false; + const unsigned char evil_flex[] = {110,117,109,95,99,104,97,110,110,101,108,115,0,115,116,97,99,107,101,114,95,108,101,102,116,95,99,111,110,116,101,120,116,0,115,116,97,99,107,101,114,95,114,105,103,104,116,95,99,111,110,116,101,120,116,0,115,116,97,99,107,101,114,95,115,116,101,112,0,4,70,58,38,17,4,1,4,8,0,255,1,4,4,4,4,8,36,1}; + tflite::StackerKernelRunner stacker_runner(input_shape, input, output_shape, + output, output_ready_shape, + &output_ready); + EXPECT_EQ(kTfLiteError, + stacker_runner.kernel_runner()->InitAndPrepare( + reinterpret_cast(evil_flex), sizeof(evil_flex))); +} + TF_LITE_MICRO_TESTS_MAIN