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-476 \u2014 NULL Pointer Dereference
The product dereferences a pointer that it expects to be valid but is NULL.
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)