CVE-2022-29201 (C++, 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.QuantizedConv2D` does not fully validate the input arguments. In this case, references get bound to `nullptr` for each argument that is empty. 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: NVD-CWE-Other \u2014 Other
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.
Repository: https://github.com/tensorflow/tensorflow
Files changed:
tensorflow/core/kernels/quantized_conv_ops.cc
tensorflow/core/kernels/quantized_conv_ops_test.cc
tensorflow/python/ops/quantized_conv_ops_test.py
Commit message: Fix undefined behavior in QuantizedConv2D

Added more input validation and tests.  Prior to this, we could get
`nullptr` exceptions when attempting to access 0th elements of 0-sized
inputs, leading to security vulnerability bugs.

Also needed to modify `quantized_conv_ops_test.cc` for consistency.
Previously the CPU kernel did technically support passing tensors
of rank larger than 0 for min/max values.  However, the XLA kernels do not.

PiperOrigin-RevId: 445518507


Vulnerable code:
#include "tensorflow/core/platform/errors.h"

                errors::InvalidArgument("input must be 4-dimensional",
                                        input.shape().DebugString()));
                errors::InvalidArgument("filter must be 4-dimensional: ",
                                        filter.shape().DebugString()));
  AddInputFromArray<float>(TensorShape({1}), {image_min});
  AddInputFromArray<float>(TensorShape({1}), {image_max});
  AddInputFromArray<float>(TensorShape({1}), {filter_min});
  AddInputFromArray<float>(TensorShape({1}), {filter_max});
  AddInputFromArray<float>(TensorShape({1}), {0});
  AddInputFromArray<float>(TensorShape({1}), {255.0f});
  AddInputFromArray<float>(TensorShape({1}), {0});
  AddInputFromArray<float>(TensorShape({1}), {255.0f});
  AddInputFromArray<float>(TensorShape({1}), {0});
  AddInputFromArray<float>(TensorShape({1}), {255.0f});
  AddInputFromArray<float>(TensorShape({1}), {0});
  AddInputFromArray<float>(TensorShape({1}), {255.0f});
  AddInputFromArray<float>(TensorShape({1}), {0});
  AddInputFromArray<float>(TensorShape({1}), {255.0f});
  AddInputFromArray<float>(TensorShape({1}), {0});
  AddInputFromArray<float>(TensorShape({1}), {255.0f});
  AddInputFromArray<float>(TensorShape({1}), {image_min});
  AddInputFromArray<float>(TensorShape({1}), {image_max});
  AddInputFromArray<float>(TensorShape({1}), {filter_min});
  AddInputFromArray<float>(TensorShape({1}), {filter_max});

Fixed code:
#include "tensorflow/core/platform/errors.h"
                errors::InvalidArgument("input must be rank 4 but is rank ",
                                        input.shape().dims()));
                errors::InvalidArgument("filter must be rank 4 but is rank ",
                                        filter.shape().dims()));

    OP_REQUIRES(context, TensorShapeUtils::IsScalar(context->input(2).shape()),
                errors::InvalidArgument("min_input must be rank 0 but is rank ",
                                        context->input(2).shape().dims()));
    OP_REQUIRES(context, TensorShapeUtils::IsScalar(context->input(3).shape()),
                errors::InvalidArgument("max_input must be rank 0 but is rank ",
                                        context->input(3).shape().dims()));
    OP_REQUIRES(
        context, TensorShapeUtils::IsScalar(context->input(4).shape()),
        errors::InvalidArgument("min_filter must be rank 0 but is rank ",
                                context->input(4).shape().dims()));
    OP_REQUIRES(
        context, TensorShapeUtils::IsScalar(context->input(5).shape()),
        errors::InvalidArgument("max_filter must be rank 0 but is rank ",
                                context->input(5).shape().dims()));
  AddInputFromArray<float>(TensorShape({}), {image_min});
  AddInputFromArray<float>(TensorShape({}), {image_max});
  AddInputFromArray<float>(TensorShape({}), {filter_min});
  AddInputFromArray<float>(TensorShape({}), {filter_max});
  AddInputFromArray<float>(TensorShape({}), {0});
  AddInputFromArray<float>(TensorShape({}), {255.0f});
  AddInputFromArray<float>(TensorShape({}), {0});
  AddInputFromArray<float>(TensorShape({}), {255.0f});
  AddInputFromArray<float>(TensorShape({}), {0});
  AddInputFromArray<float>(TensorShape({}), {255.0f});
  AddInputFromArray<float>(TensorShape({}), {0});
  AddInputFromArray<float>(TensorShape({}), {255.0f});
  AddInputFromArray<float>(TensorShape({}), {0});
  AddInputFromArray<float>(TensorShape({}), {255.0f});
  AddInputFromArray<float>(TensorShape({}), {0});
  AddInputFromArray<float>(TensorShape({}), {255.0f});
  AddInputFromArray<float>(TensorShape({}), {image_min});
  AddInputFromArray<float>(TensorShape({}), {image_max});
  AddInputFromArray<float>(TensorShape({}), {filter_min});
  AddInputFromArray<float>(TensorShape({}), {filter_max});
from tensorflow.python.framework import errors
from tensorflow.python.ops import math_ops
  def _testBadInputSize(self,
                        tin=None,
                        tfilter=None,
                        min_input=None,
                        max_input=None,
                        min_filter=None,
                        max_filter=None,
                        error_regex=""):
    strides = [1, 1, 1, 1]
    padding = "SAME"
    if tin is None:
      tin = math_ops.cast(
          constant_op.constant(1, shape=[1, 2, 3, 3]), dtype=dtypes.quint8)

    if tfilter is None:
      tfilter = math_ops.cast(
          constant_op.constant(1, shape=[1, 2, 3, 3]), dtype=dtypes.quint8)

    if min_input is None:
      min_input = constant_op.constant(0, shape=[], dtype=dtypes.float32)

    if max_input is None:
      max_input = constant_op.constant(0, shape=[], dtype=dtypes.float32)

    if min_filter is None:
      min_filter = constant_op.constant(0, shape=[], dtype=dtypes.float32)

    if max_filter is None:
      max_filter = constant_op.constant(0, shape=[], dtype=dtypes.float32)

    with self.assertRaisesRegex((ValueError, errors.InvalidArgumentError),
                                error_regex):
      self.evaluate(
          nn_ops.quantized_conv2d(
              tin,
              tfilter,
              out_type=dtypes.qint32,
              strides=strides,
              padding=padding,
              min_input=min_input,
              max_input=max_input,
              min_filter=min_filter,
              max_filter=max_filter))

  def testBadInputSizes(self):
    self._testBadInputSize(
        tin=math_ops.cast(
            constant_op.constant(1, shape=[1, 2]), dtype=dtypes.quint8),
        error_regex="must be rank 4")
    self._testBadInputSize(
        tfilter=math_ops.cast(
            constant_op.constant(1, shape=[1, 2]), dtype=dtypes.quint8),
        error_regex="must be rank 4")
    self._testBadInputSize(
        min_input=constant_op.constant(0, shape=[1], dtype=dtypes.float32),
        error_regex="must be rank 0")
    self._testBadInputSize(
        max_input=constant_op.constant(0, shape=[1], dtype=dtypes.float32),
        error_regex="must be rank 0")
    self._testBadInputSize(
        min_filter=constant_op.constant(0, shape=[1], dtype=dtypes.float32),
        error_regex="must be rank 0")
    self._testBadInputSize(
        max_filter=constant_op.constant(0, shape=[1], dtype=dtypes.float32),
        error_regex="must be rank 0")