CVE-2022-41898: Other (NVD-CWE-Other)
value: TensorFlow is an open source platform for machine learning. If `SparseFillEmptyRowsGrad` is given empty inputs, TensorFlow will crash. We have patched the issue in GitHub commit af4a6a3c8b95022c351edae94560acc61253a1b8. The fix will be included in TensorFlow 2.11. We will also cherrypick this commit on TensorFlow 2.10.1, 2.9.3, and TensorFlow 2.8.4, as these are also affected and still in supported range.
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
CVSS v3 base score: 7.5
Published: 2022-11-18T22:15Z
Repository: https://github.com/tensorflow/tensorflow
Language: Python
Files changed: tensorflow/core/kernels/sparse_fill_empty_rows_op_gpu.cu.cc, tensorflow/python/kernel_tests/sparse_ops/sparse_ops_test.py
Commit date: 2022-09-30 14:56:34 -0700
Commit message: Fix empty input crash for SparseFillEmptyRowsGrad.

PiperOrigin-RevId: 478085721

Diff: diff --git a/tensorflow/core/kernels/sparse_fill_empty_rows_op_gpu.cu.cc b/tensorflow/core/kernels/sparse_fill_empty_rows_op_gpu.cu.cc
index 37ce6c88e36be..62e9cea0f2311 100644
--- a/tensorflow/core/kernels/sparse_fill_empty_rows_op_gpu.cu.cc
+++ b/tensorflow/core/kernels/sparse_fill_empty_rows_op_gpu.cu.cc
@@ -295,13 +295,16 @@ struct SparseFillEmptyRows<GPUDevice, T, Tindex> {
       TF_RETURN_IF_ERROR(context->allocate_temp(
           DT_BOOL, TensorShape({dense_rows}), &empty_row_indicator_t));
       empty_row_indicator = empty_row_indicator_t.vec<bool>().data();
     }
 
-    TF_RETURN_IF_ERROR(wrap_kernel_call(ComputeEmptyRowIndicatorKernel<Tindex>,
-                                        /*device=*/device, /*size=*/dense_rows,
-                                        elements_per_row, empty_row_indicator));
+    if (dense_rows > 0) {
+      TF_RETURN_IF_ERROR(
+          wrap_kernel_call(ComputeEmptyRowIndicatorKernel<Tindex>,
+                           /*device=*/device, /*size=*/dense_rows,
+                           elements_per_row, empty_row_indicator));
+    }
 
     // For each row, the number of empty rows up to and including that row.
     Tensor num_empty_rows_through_t;
     TF_RETURN_IF_ERROR(context->allocate_temp(
         index_type, TensorShape({dense_rows}), &num_empty_rows_through_t));
@@ -403,18 +406,20 @@ struct SparseFillEmptyRows<GPUDevice, T, Tindex> {
                              num_empty_rows_through, output_indices,
                              output_values, reverse_index_map),
             done);
       }
 
-      OP_REQUIRES_OK_ASYNC(
-          context,
-          wrap_kernel_call(ScatterNewElementsKernel<T, Tindex>,
-                           /*device=*/device, /*size=*/dense_rows, rank,
-                           default_value, num_empty_rows_through,
-                           input_row_ends, empty_row_indicator, output_indices,
-                           output_values),
-          done);
+      if (dense_rows > 0) {
+        OP_REQUIRES_OK_ASYNC(
+            context,
+            wrap_kernel_call(ScatterNewElementsKernel<T, Tindex>,
+                             /*device=*/device, /*size=*/dense_rows, rank,
+                             default_value, num_empty_rows_through,
+                             input_row_ends, empty_row_indicator,
+                             output_indices, output_values),
+            done);
+      }
 
       done();
     };
 
     context->device()
@@ -459,13 +464,15 @@ struct SparseFillEmptyRows<GPUDevice, T, Tindex> {
     // Extract row indices into separate array for use as keys for sorting.
     Tensor row_indices_t;
     TF_RETURN_IF_ERROR(
         context->allocate_temp(index_type, TensorShape({N}), &row_indices_t));
     auto row_indices = row_indices_t.flat<Tindex>();
-    TF_RETURN_IF_ERROR(wrap_kernel_call(CopyRowIndicesKernel<Tindex>,
-                                        /*device=*/device, /*size=*/N, rank,
-                                        indices, row_indices));
+    if (N > 0) {
+      TF_RETURN_IF_ERROR(wrap_kernel_call(CopyRowIndicesKernel<Tindex>,
+                                          /*device=*/device, /*size=*/N, rank,
+                                          indices, row_indices));
+    }
     // Allocate input_index_map.
     TF_RETURN_IF_ERROR(context->allocate_temp(index_type, TensorShape({N}),
                                               input_index_map_t));
     Tindex* input_index_map = input_index_map_t->flat<Tindex>().data();
     return GpuRadixSort(context, /*size=*/N, /*keys_in=*/row_indices.data(),
@@ -526,13 +533,15 @@ struct SparseFillEmptyRowsGrad<GPUDevice, T, Tindex> {
     TF_RETURN_IF_ERROR(
         context->allocate_temp(DT_BOOL, TensorShape({N_full}), &visited_t));
     auto visited = visited_t.vec<bool>();
     visited.device(device) = visited.constant(false);
 
-    TF_RETURN_IF_ERROR(wrap_kernel_call(
-        GatherOriginalGradValuesKernel<T, Tindex>, /*device=*/device,
-        /*size=*/N, reverse_index_map, grad_values, d_values, visited));
+    if (N > 0) {
+      TF_RETURN_IF_ERROR(wrap_kernel_call(
+          GatherOriginalGradValuesKernel<T, Tindex>, /*device=*/device,
+          /*size=*/N, reverse_index_map, grad_values, d_values, visited));
+    }
 
     // Now we mask out the visited values and sum the remaining ones (which
     // correspond to the empty rows in the forward input) to compute
     // d_default_value.
 
diff --git a/tensorflow/python/kernel_tests/sparse_ops/sparse_ops_test.py b/tensorflow/python/kernel_tests/sparse_ops/sparse_ops_test.py
index 9900f0c6a511e..c5ec25ed1fdf5 100644
--- a/tensorflow/python/kernel_tests/sparse_ops/sparse_ops_test.py
+++ b/tensorflow/python/kernel_tests/sparse_ops/sparse_ops_test.py
@@ -512,10 +512,17 @@ class SparseFillEmptyRowsTest(test_util.TensorFlowTestCase):
         self.assertAllEqual(output.values, [0, 10, 13, 14, -1, 32, 33, -1])
         self.assertAllEqual(output.dense_shape, [5, 6])
         self.assertAllEqual(empty_row_indicator_out,
                             np.array([0, 0, 1, 0, 1]).astype(np.bool_))
 
+  def testSparseFillEmptyRowsGradEmpty(self):
+    with test_util.use_gpu():
+      grad, _ = self.evaluate(
+          sparse_ops.sparse_fill_empty_rows_grad(
+              reverse_index_map=[], grad_values=[]))
+      self.assertAllEqual(grad, [])
+
   @test_util.run_deprecated_v1
   def testFillFloat(self):
     with self.session():
       values = constant_op.constant(
           [0.0, 10.0, 13.0, 14.0, 32.0, 33.0], dtype=dtypes.float64)

Vulnerable code:     TF_RETURN_IF_ERROR(wrap_kernel_call(ComputeEmptyRowIndicatorKernel<Tindex>,
                                        /*device=*/device, /*size=*/dense_rows,
                                        elements_per_row, empty_row_indicator));
      OP_REQUIRES_OK_ASYNC(
          context,
          wrap_kernel_call(ScatterNewElementsKernel<T, Tindex>,
                           /*device=*/device, /*size=*/dense_rows, rank,
                           default_value, num_empty_rows_through,
                           input_row_ends, empty_row_indicator, output_indices,
                           output_values),
          done);
    TF_RETURN_IF_ERROR(wrap_kernel_call(CopyRowIndicesKernel<Tindex>,
                                        /*device=*/device, /*size=*/N, rank,
                                        indices, row_indices));
    TF_RETURN_IF_ERROR(wrap_kernel_call(
        GatherOriginalGradValuesKernel<T, Tindex>, /*device=*/device,
        /*size=*/N, reverse_index_map, grad_values, d_values, visited));
Fixed code:     if (dense_rows > 0) {
      TF_RETURN_IF_ERROR(
          wrap_kernel_call(ComputeEmptyRowIndicatorKernel<Tindex>,
                           /*device=*/device, /*size=*/dense_rows,
                           elements_per_row, empty_row_indicator));
    }
      if (dense_rows > 0) {
        OP_REQUIRES_OK_ASYNC(
            context,
            wrap_kernel_call(ScatterNewElementsKernel<T, Tindex>,
                             /*device=*/device, /*size=*/dense_rows, rank,
                             default_value, num_empty_rows_through,
                             input_row_ends, empty_row_indicator,
                             output_indices, output_values),
            done);
      }
    if (N > 0) {
      TF_RETURN_IF_ERROR(wrap_kernel_call(CopyRowIndicesKernel<Tindex>,
                                          /*device=*/device, /*size=*/N, rank,
                                          indices, row_indices));
    }
    if (N > 0) {
      TF_RETURN_IF_ERROR(wrap_kernel_call(
          GatherOriginalGradValuesKernel<T, Tindex>, /*device=*/device,
          /*size=*/N, reverse_index_map, grad_values, d_values, visited));
    }
  def testSparseFillEmptyRowsGradEmpty(self):
    with test_util.use_gpu():
      grad, _ = self.evaluate(
          sparse_ops.sparse_fill_empty_rows_grad(
              reverse_index_map=[], grad_values=[]))
      self.assertAllEqual(grad, [])