CVE-2022-35938 (published 2022-09-16T20:15Z)

TensorFlow is an open source platform for machine learning. The `GatherNd` function takes arguments that determine the sizes of inputs and outputs. If the inputs given are greater than or equal to the sizes of the outputs, an out-of-bounds memory read or a crash is triggered. This issue has been patched in GitHub commit 4142e47e9e31db481781b955ed3ff807a781b494. The fix will be included in TensorFlow 2.10.0. We will also cherrypick this commit on TensorFlow 2.9.1, TensorFlow 2.8.1, and TensorFlow 2.7.2, as these are also affected and still in supported range. There are no known workarounds for this issue.

CVSS v3 base score: 9.1, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H)

Weakness information:
cwe_id: CWE-125
cwe_name: Out-of-bounds Read
description: The product reads data past the end, or before the beginning, of the intended buffer.
extended_description: 
            
            
             
			 
         
         

Fix information:
repo_url: https://github.com/tensorflow/tflite-micro
commit_details: repo_url: https://github.com/tensorflow/tflite-micro
author: alankelly
author_date: 2022-07-26 21:54:47+02:00
msg: GatherNd verifies that an index is valid before reading. (#1286)
repository: repo_url: https://github.com/tensorflow/tflite-micro
repo_name: tensorflow/tflite-micro
description: Infrastructure to enable deployment of ML models to low-power resource-constrained embedded targets (including microcontrollers and digital signal processors).
repo_language: C++
owner: tensorflow
file_changes: filename: gather_nd.cc
change_type: ModificationType.MODIFY
diff: @@ -131,7 +131,8 @@ TfLiteStatus GatherNd(const TfLiteEvalTensor* params,
     slice_size *= params->dims->data[i];
   }
 
-  int remain_flat_size = ElementCount(*params->dims);
+  int params_flat_size = ElementCount(*params->dims);
+  int remain_flat_size = params_flat_size;
 
   // Number of elements per dimension
   int dims_to_count[MAX_INDICES_ND];
@@ -147,6 +148,9 @@ TfLiteStatus GatherNd(const TfLiteEvalTensor* params,
       IndicesT index = index_data[offset];
       from_pos += index * dims_to_count[j];
     }
+    if (from_pos < 0 || from_pos + slice_size > params_flat_size) {
+      return kTfLiteError;
+    }
     std::memcpy(output_data + i * slice_size, param_data + from_pos,
                 sizeof(ParamsT) * slice_size);
   }
@@ -158,12 +162,13 @@ TfLiteStatus EvalGatherNd(TfLiteContext* context,
                           const TfLiteEvalTensor* params,
                           const TfLiteEvalTensor* indices,
                           TfLiteEvalTensor* output) {
+  TfLiteStatus status = kTfLiteError;
   switch (params->type) {
     case kTfLiteFloat32:
-      return GatherNd<float, IndicesT>(params, indices, output);
+      status = GatherNd<float, IndicesT>(params, indices, output);
       break;
     case kTfLiteInt8:
-      return GatherNd<int8_t, IndicesT>(params, indices, output);
+      status = GatherNd<int8_t, IndicesT>(params, indices, output);
       break;
     default:
       TF_LITE_KERNEL_LOG(context,
@@ -171,6 +176,10 @@ TfLiteStatus EvalGatherNd(TfLiteContext* context,
                          TfLiteTypeGetName(params->type));
       return kTfLiteError;
   }
+  if (status != kTfLiteOk) {
+    TF_LITE_KERNEL_LOG(context, "gather_nd index out of bounds");
+  }
+  return status;
 }
 
 TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {

programming_language: C++
filename: gather_nd_test.cc
change_type: ModificationType.MODIFY
diff: @@ -26,8 +26,8 @@ namespace {
 template <typename ParamType, typename IndexType>
 void TestGatherNd(int* param_dims, const ParamType* param_data, int* index_dims,
                   const IndexType* index_data, int* output_dims,
-                  ParamType* output_data,
-                  const ParamType* expected_output_data) {
+                  ParamType* output_data, const ParamType* expected_output_data,
+                  const TfLiteStatus expected_status = kTfLiteOk) {
   TfLiteIntArray* pdims = IntArrayFromInts(param_dims);
   TfLiteIntArray* idims = IntArrayFromInts(index_dims);
   TfLiteIntArray* odims = IntArrayFromInts(output_dims);
@@ -49,14 +49,16 @@ void TestGatherNd(int* param_dims, const ParamType* param_data, int* index_dims,
   micro::KernelRunner runner(registration, tensors, tensors_size, inputs_array,
                              outputs_array, /*builtin_data=*/nullptr);
   TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, runner.InitAndPrepare());
-  TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, runner.Invoke());
+  TF_LITE_MICRO_EXPECT_EQ(expected_status, runner.Invoke());
 
-  // The output tensor's data and shape have been updated by the kernel.
-  TfLiteTensor* actual_output_tensor = &tensors[2];
-  TfLiteIntArray* actual_output_dims = actual_output_tensor->dims;
-  const int output_size = ElementCount(*actual_output_dims);
-  for (int i = 0; i < output_size; ++i) {
-    TF_LITE_MICRO_EXPECT_EQ(expected_output_data[i], output_data[i]);
+  if (expected_status == kTfLiteOk) {
+    // The output tensor's data and shape have been updated by the kernel.
+    TfLiteTensor* actual_output_tensor = &tensors[2];
+    TfLiteIntArray* actual_output_dims = actual_output_tensor->dims;
+    const int output_size = ElementCount(*actual_output_dims);
+    for (int i = 0; i < output_size; ++i) {
+      TF_LITE_MICRO_EXPECT_EQ(expected_output_data[i], output_data[i]);
+    }
   }
 }
 
@@ -298,4 +300,32 @@ TF_LITE_MICRO_TEST(GatherNd_Int8Int32) {
       golden_data);
 }
 
+TF_LITE_MICRO_TEST(GatherNd_ReadOOB) {
+  // For input_dims[], index_dims[], or output_dims[], element 0 is the
+  // number of dimensions in that array, not the actual dimension data.
+  int input_dims[] = {2, 2, 2};
+  int index_dims[] = {2, 2, 2};
+  const int32_t index_data[] = {0, 1, 2, 0};
+  const int8_t input_data[] = {1, -1, 1, -2};
+  int8_t output_data;
+  int output_dims[] = {1, 0, 0};
+  tflite::testing::TestGatherNd<int8_t, int32_t>(
+      input_dims, input_data, index_dims, index_data, output_dims, &output_data,
+      nullptr, kTfLiteError);
+}
+
+TF_LITE_MICRO_TEST(GatherNd_ReadOOBNegative) {
+  // For input_dims[], index_dims[], or output_dims[], element 0 is the
+  // number of dimensions in that array, not the actual dimension data.
+  int input_dims[] = {2, 2, 2};
+  int index_dims[] = {2, 2, 2};
+  const int32_t index_data[] = {0, -1, 1, 0};
+  const int8_t input_data[] = {1, -1, 1, -2};
+  int8_t output_data;
+  int output_dims[] = {1, 0, 0};
+  tflite::testing::TestGatherNd<int8_t, int32_t>(
+      input_dims, input_data, index_dims, index_data, output_dims, &output_data,
+      nullptr, kTfLiteError);
+}
+
 TF_LITE_MICRO_TESTS_END

programming_language: C