CVE-2022-29206 (Python, published 2022-05-20T23:15Z)
Description: value: TensorFlow is an open source platform for machine learning. Prior to versions 2.9.0, 2.8.1, 2.7.2, and 2.6.4, the implementation of `tf.raw_ops.SparseTensorDenseAdd` does not fully validate the input arguments. In this case, a reference gets bound to a `nullptr` during kernel execution. This is undefined behavior. Versions 2.9.0, 2.8.1, 2.7.2, and 2.6.4 contain a patch for this issue.
Severity: LOW (CVSS v3 base score: 5.5)
Weakness: CWE-20 \u2014 Improper Input Validation
The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Repository: https://github.com/tensorflow/tensorflow
Files changed:
tensorflow/core/kernels/sparse_tensor_dense_add_op.cc
tensorflow/python/kernel_tests/sparse_ops/sparse_add_op_test.py
tensorflow/python/kernel_tests/sparse_ops/sparse_ops_test.py
Commit message: Fix UB in SparseTensorDenseAdd

Added more input validation to avoid nullptr dereferencing and array index
out of bounds issues.

PiperOrigin-RevId: 446192704


Vulnerable code:
  @test_util.run_deprecated_v1
        s = sparse_ops.sparse_add(sparse, dense)

        with self.assertRaisesRegex(errors_impl.InvalidArgumentError,
                                    "invalid index"):
    indices = constant_op.constant([[1]], dtype=dtypes.int64)

Fixed code:
#include "tensorflow/core/framework/op_requires.h"
  int64_t nnz = a_indices->dim_size(0);
  int64_t ndims = a_indices->dim_size(1);
  if (a_values->dim_size(0) != nnz) {
    return errors::InvalidArgument("Dimensions ", nnz, " and ",
                                   a_values->dim_size(0),
                                   " are not compatible");
  }
  if (a_shape->dim_size(0) != ndims) {
    return errors::InvalidArgument("Dimensions ", ndims, " and ",
                                   a_shape->dim_size(0), " are not compatible");
  }

  // Check for invalid indices.
  const auto a_indices_mat = a_indices->flat_inner_dims<Index>();

  for (int64_t zidx = 0; zidx < nnz; ++zidx) {
    for (int64_t didx = 0; didx < ndims; ++didx) {
      const Index idx = a_indices_mat(zidx, didx);
      if (idx < 0 || idx >= a_shape_flat(didx)) {
        return errors::InvalidArgument(
            "Sparse tensor has an invalid index on dimension ", didx,
            ": "
            "a_indices(",
            zidx, ",", didx, ") = ", idx,
            ", dense tensor shape: ", a_shape_flat);
      }
    }
  }

        with self.assertRaisesRegex(
            (ValueError, errors_impl.InvalidArgumentError), "invalid index"):
          s = sparse_ops.sparse_add(sparse, dense)
  def _testSparseDenseInvalidInputs(self,
                                    a_indices,
                                    a_values,
                                    a_shape,
                                    b,
                                    expected_error=""):
    # Public API call to sparse-dense add.
    with self.assertRaisesRegex((ValueError, errors_impl.InvalidArgumentError),
                                expected_error):
      a = sparse_tensor.SparseTensor(a_indices, a_values, a_shape)
      self.evaluate(sparse_ops.sparse_add(a, b))
    # Directly call generated kernel, by-passing SparseTensor validation.
    with self.assertRaisesRegex((ValueError, errors_impl.InvalidArgumentError),
                                expected_error):
      self.evaluate(
          sparse_ops.gen_sparse_ops.sparse_tensor_dense_add(
              a_indices, a_values, a_shape, b))

  def testSparseDenseInvalidInputs(self):
    self._testSparseDenseInvalidInputs(
        a_indices=constant_op.constant(0, shape=[17, 2], dtype=dtypes.int64),
        a_values=constant_op.constant(0, shape=[5], dtype=dtypes.float32),
        a_shape=constant_op.constant([3, 4], dtype=dtypes.int64),
        b=constant_op.constant(1, shape=[3, 4], dtype=dtypes.float32),
        expected_error="Dimensions 17 and 5 are not compatible")
    self._testSparseDenseInvalidInputs(
        a_indices=constant_op.constant(0, shape=[17, 4], dtype=dtypes.int64),
        a_values=constant_op.constant(0, shape=[17], dtype=dtypes.float32),
        a_shape=constant_op.constant([3, 4], dtype=dtypes.int64),
        b=constant_op.constant(1, shape=[3, 4], dtype=dtypes.float32),
        expected_error="Dimensions 4 and 2 are not compatible")
    self._testSparseDenseInvalidInputs(
        a_indices=constant_op.constant(7, shape=[17, 2], dtype=dtypes.int64),
        a_values=constant_op.constant(0, shape=[17], dtype=dtypes.float32),
        a_shape=constant_op.constant([3, 4], dtype=dtypes.int64),
        b=constant_op.constant(1, shape=[3, 4], dtype=dtypes.float32),
        expected_error="invalid index")

    indices = constant_op.constant([[0]], dtype=dtypes.int64)