CVE-2023-25665: NULL Pointer Dereference (CWE-476)
value: TensorFlow is an open source platform for machine learning. Prior to versions 2.12.0 and 2.11.1, when `SparseSparseMaximum` is given invalid sparse tensors as inputs, it can give a null pointer error. A fix is included in TensorFlow version 2.12 and version 2.11.1.
CWE description: The product dereferences a pointer that it expects to be valid but is NULL.
CVSS v3 base score: 7.5
Published: 2023-03-25T00:15Z
Repository: https://github.com/tensorflow/tensorflow
Language: Python
Files changed: tensorflow/core/kernels/sparse_sparse_binary_op_shared.cc, tensorflow/python/kernel_tests/sparse_ops/sparse_ops_test.py
Commit date: 2023-01-20 12:46:01 -0800
Commit message: Add validation checks in sparse binary ops.

Fixes a nullptr security vulnerability.

PiperOrigin-RevId: 503501621

Diff: diff --git a/tensorflow/core/kernels/sparse_sparse_binary_op_shared.cc b/tensorflow/core/kernels/sparse_sparse_binary_op_shared.cc
index 8d0642eb1d413..0863e5e37949b 100644
--- a/tensorflow/core/kernels/sparse_sparse_binary_op_shared.cc
+++ b/tensorflow/core/kernels/sparse_sparse_binary_op_shared.cc
@@ -39,10 +39,11 @@ limitations under the License.
 #include "tensorflow/core/framework/tensor.h"
 #include "tensorflow/core/framework/tensor_util.h"
 #include "tensorflow/core/framework/types.h"
 #include "tensorflow/core/kernels/cwise_ops.h"
 #include "tensorflow/core/kernels/cwise_ops_common.h"
+#include "tensorflow/core/kernels/sparse_utils.h"
 #include "tensorflow/core/util/sparse/sparse_tensor.h"
 
 namespace tensorflow {
 
 typedef Eigen::ThreadPoolDevice CPUDevice;
@@ -129,52 +130,24 @@ class SparseSparseBinaryOpShared : public OpKernel {
     OP_REQUIRES_OK(ctx, ctx->input("b_indices", &b_indices_t));
     OP_REQUIRES_OK(ctx, ctx->input("b_values", &b_values_t));
     OP_REQUIRES_OK(ctx, ctx->input("b_shape", &b_shape_t));
 
     // Validations.
-    OP_REQUIRES(
-        ctx,
-        TensorShapeUtils::IsMatrix(a_indices_t->shape()) &&
-            TensorShapeUtils::IsMatrix(b_indices_t->shape()),
-        errors::InvalidArgument("Inputs a_indices and b_indices should be "
-                                "matrices but received shapes: ",
-                                a_indices_t->shape().DebugString(), ", ",
-                                b_indices_t->shape().DebugString()));
-    OP_REQUIRES(ctx,
-                TensorShapeUtils::IsVector(a_values_t->shape()) &&
-                    TensorShapeUtils::IsVector(b_values_t->shape()),
-                errors::InvalidArgument(
-                    "Inputs a_values and b_values should be vectors "
-                    "but received shapes: ",
-                    a_values_t->shape().DebugString(), " and ",
-                    b_values_t->shape().DebugString()));
+    OP_REQUIRES_OK(ctx, sparse_utils::ValidateSparseTensor<int64_t>(
+                            *a_indices_t, *a_values_t, *a_shape_t,
+                            sparse_utils::IndexValidation::kUnordered));
+    OP_REQUIRES_OK(ctx, sparse_utils::ValidateSparseTensor<int64_t>(
+                            *b_indices_t, *b_values_t, *b_shape_t,
+                            sparse_utils::IndexValidation::kUnordered));
 
     const int64_t a_nnz = a_indices_t->dim_size(0);
     const int64_t b_nnz = b_indices_t->dim_size(0);
 
     const auto a_values = a_values_t->vec<T>();
     const auto b_values = b_values_t->vec<T>();
 
-    OP_REQUIRES(
-        ctx, a_values.size() == a_nnz && b_values.size() == b_nnz,
-        errors::InvalidArgument("Expected ", a_nnz, " and ", b_nnz,
-                                " non-empty input values, got ",
-                                a_values.size(), " and ", b_values.size()));
-
-    OP_REQUIRES(ctx,
-                TensorShapeUtils::IsVector(a_shape_t->shape()) &&
-                    TensorShapeUtils::IsVector(b_shape_t->shape()),
-                errors::InvalidArgument(
-                    "Input shapes should be a vector but received shapes ",
-                    a_shape_t->shape().DebugString(), " and ",
-                    b_shape_t->shape().DebugString()));
     const int num_dims = a_indices_t->dim_size(1);
-    OP_REQUIRES(
-        ctx, a_shape_t->NumElements() == num_dims,
-        errors::InvalidArgument("Second dimension of a_indices and length of "
-                                "a_shape must match, got ",
-                                num_dims, " and ", a_shape_t->NumElements()));
     OP_REQUIRES(ctx, num_dims > 0,
                 errors::InvalidArgument("Tensors must not be empty"));
     OP_REQUIRES(ctx, a_shape_t->IsSameSize(*b_shape_t),
                 errors::InvalidArgument(
                     "Operands do not have the same ranks; got shapes: ",
@@ -190,11 +163,11 @@ class SparseSparseBinaryOpShared : public OpKernel {
     }
 
     const auto a_indices_mat = a_indices_t->matrix<int64_t>();
     const auto b_indices_mat = b_indices_t->matrix<int64_t>();
     std::vector<T> a_augmented_values, b_augmented_values;
-    std::vector<std::pair<bool, int64>> entries_to_copy;  // from_a?, idx
+    std::vector<std::pair<bool, int64_t>> entries_to_copy;  // from_a?, idx
     UnionSparseIndicesAndValues(a_indices_mat, a_values, a_nnz, b_indices_mat,
                                 b_values, b_nnz, num_dims, &a_augmented_values,
                                 &b_augmented_values, &entries_to_copy);
 
     // Allocates and fills output tensors.
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 60f61cf4f1436..ee46404aedf1a 100644
--- a/tensorflow/python/kernel_tests/sparse_ops/sparse_ops_test.py
+++ b/tensorflow/python/kernel_tests/sparse_ops/sparse_ops_test.py
@@ -24,10 +24,11 @@ from tensorflow.python.framework import ops
 from tensorflow.python.framework import sparse_tensor
 from tensorflow.python.framework import tensor_shape
 from tensorflow.python.framework import tensor_spec
 from tensorflow.python.framework import test_util
 from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import gen_sparse_ops
 from tensorflow.python.ops import gradient_checker
 from tensorflow.python.ops import nn_ops
 from tensorflow.python.ops import sparse_ops
 from tensorflow.python.ops import variables
 import tensorflow.python.ops.sparse_grad  # pylint: disable=unused-import
@@ -1173,10 +1174,22 @@ class SparseMinimumMaximumTest(test_util.TensorFlowTestCase):
       max_tf = sparse_ops.sparse_maximum(sp_zero, sp_zero_2)
       min_tf = sparse_ops.sparse_minimum(sp_zero, sp_zero_2)
       self._assertSparseTensorValueEqual(expected, max_tf)
       self._assertSparseTensorValueEqual(expected, min_tf)
 
+  def testInvalidSparseInputs(self):
+    with test_util.force_cpu():
+      with self.assertRaisesRegex(
+          (ValueError, errors.InvalidArgumentError),
+          ".*Index rank .* and shape rank .* do not match.*",
+      ):
+        self.evaluate(
+            gen_sparse_ops.sparse_sparse_maximum(
+                [[1]], [0], [2], [[]], [1], [2]
+            )
+        )
+
   @test_util.run_deprecated_v1
   def testRandom(self):
     np.random.seed(1618)
     shapes = [(13,), (6, 8), (1, 7, 1)]
     for shape in shapes:

Vulnerable code:     OP_REQUIRES(
        ctx,
        TensorShapeUtils::IsMatrix(a_indices_t->shape()) &&
            TensorShapeUtils::IsMatrix(b_indices_t->shape()),
        errors::InvalidArgument("Inputs a_indices and b_indices should be "
                                "matrices but received shapes: ",
                                a_indices_t->shape().DebugString(), ", ",
                                b_indices_t->shape().DebugString()));
    OP_REQUIRES(ctx,
                TensorShapeUtils::IsVector(a_values_t->shape()) &&
                    TensorShapeUtils::IsVector(b_values_t->shape()),
                errors::InvalidArgument(
                    "Inputs a_values and b_values should be vectors "
                    "but received shapes: ",
                    a_values_t->shape().DebugString(), " and ",
                    b_values_t->shape().DebugString()));
    OP_REQUIRES(
        ctx, a_values.size() == a_nnz && b_values.size() == b_nnz,
        errors::InvalidArgument("Expected ", a_nnz, " and ", b_nnz,
                                " non-empty input values, got ",
                                a_values.size(), " and ", b_values.size()));

    OP_REQUIRES(ctx,
                TensorShapeUtils::IsVector(a_shape_t->shape()) &&
                    TensorShapeUtils::IsVector(b_shape_t->shape()),
                errors::InvalidArgument(
                    "Input shapes should be a vector but received shapes ",
                    a_shape_t->shape().DebugString(), " and ",
                    b_shape_t->shape().DebugString()));
    OP_REQUIRES(
        ctx, a_shape_t->NumElements() == num_dims,
        errors::InvalidArgument("Second dimension of a_indices and length of "
                                "a_shape must match, got ",
                                num_dims, " and ", a_shape_t->NumElements()));
    std::vector<std::pair<bool, int64>> entries_to_copy;  // from_a?, idx
Fixed code: #include "tensorflow/core/kernels/sparse_utils.h"
    OP_REQUIRES_OK(ctx, sparse_utils::ValidateSparseTensor<int64_t>(
                            *a_indices_t, *a_values_t, *a_shape_t,
                            sparse_utils::IndexValidation::kUnordered));
    OP_REQUIRES_OK(ctx, sparse_utils::ValidateSparseTensor<int64_t>(
                            *b_indices_t, *b_values_t, *b_shape_t,
                            sparse_utils::IndexValidation::kUnordered));
    std::vector<std::pair<bool, int64_t>> entries_to_copy;  // from_a?, idx
from tensorflow.python.ops import gen_sparse_ops
  def testInvalidSparseInputs(self):
    with test_util.force_cpu():
      with self.assertRaisesRegex(
          (ValueError, errors.InvalidArgumentError),
          ".*Index rank .* and shape rank .* do not match.*",
      ):
        self.evaluate(
            gen_sparse_ops.sparse_sparse_maximum(
                [[1]], [0], [2], [[]], [1], [2]
            )
        )