CVE-2022-29201: Other (NVD-CWE-Other)
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.
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: 5.5
Published: 2022-05-20T23:15Z
Repository: https://github.com/tensorflow/tensorflow
Language: C++
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 date: 2022-04-29 15:32:23 -0700
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

Diff: diff --git a/tensorflow/core/kernels/quantized_conv_ops.cc b/tensorflow/core/kernels/quantized_conv_ops.cc
index 39824ab2546d3..0519256b613be 100644
--- a/tensorflow/core/kernels/quantized_conv_ops.cc
+++ b/tensorflow/core/kernels/quantized_conv_ops.cc
@@ -16,12 +16,10 @@ limitations under the License.
 // Implements quantized eight-bit versions of the convolution operations.
 
 #include <algorithm>
 #include <vector>
 
-#include "tensorflow/core/platform/errors.h"
-
 #define EIGEN_USE_THREADS
 
 #define GEMMLOWP_ALLOW_SLOW_SCALAR_FALLBACK
 #include "public/gemmlowp.h"
 #include "tensorflow/core/framework/kernel_shape_util.h"
@@ -30,10 +28,11 @@ limitations under the License.
 #include "tensorflow/core/kernels/conv_ops.h"
 #include "tensorflow/core/kernels/meta_support.h"
 #include "tensorflow/core/kernels/quantization_utils.h"
 #include "tensorflow/core/kernels/reference_gemm.h"
 #include "tensorflow/core/lib/core/errors.h"
+#include "tensorflow/core/platform/errors.h"
 #include "tensorflow/core/util/padding.h"
 
 namespace tensorflow {
 
 // This functor implements the convolution operation in as simple a form as
@@ -497,15 +496,30 @@ class QuantizedConv2DOp : public OpKernel {
     // [ filter_rows, filter_cols, in_depth, out_depth]
     const Tensor& filter = context->input(1);
 
     // For 2D convolution, there should be 4 dimensions.
     OP_REQUIRES(context, input.dims() == 4,
-                errors::InvalidArgument("input must be 4-dimensional",
-                                        input.shape().DebugString()));
+                errors::InvalidArgument("input must be rank 4 but is rank ",
+                                        input.shape().dims()));
     OP_REQUIRES(context, filter.dims() == 4,
-                errors::InvalidArgument("filter must be 4-dimensional: ",
-                                        filter.shape().DebugString()));
+                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()));
 
     const float min_input = context->input(2).flat<float>()(0);
     const float max_input = context->input(3).flat<float>()(0);
     const float min_filter = context->input(4).flat<float>()(0);
     const float max_filter = context->input(5).flat<float>()(0);
diff --git a/tensorflow/core/kernels/quantized_conv_ops_test.cc b/tensorflow/core/kernels/quantized_conv_ops_test.cc
index 4226378bb6468..dd0878a36df7b 100644
--- a/tensorflow/core/kernels/quantized_conv_ops_test.cc
+++ b/tensorflow/core/kernels/quantized_conv_ops_test.cc
@@ -89,14 +89,14 @@ TEST_F(QuantizedConv2DTest, Small) {
 
   AddInputFromArray<quint8>(image_quantized.shape(),
                             image_quantized.flat<quint8>());
   AddInputFromArray<quint8>(filter_quantized.shape(),
                             filter_quantized.flat<quint8>());
-  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({}), {image_min});
+  AddInputFromArray<float>(TensorShape({}), {image_max});
+  AddInputFromArray<float>(TensorShape({}), {filter_min});
+  AddInputFromArray<float>(TensorShape({}), {filter_max});
   TF_ASSERT_OK(RunOpKernel());
 
   // We're sliding the 3x3 filter across the 3x4 image, with accesses outside
   // the input set to zero because we're using the 'SAME' padding mode.
   // The calculations behind the expected output are:
@@ -156,14 +156,14 @@ TEST_F(QuantizedConv2DTest, Small32Bit) {
   const int filter_size = 3;
   const int filter_count = 1;
   AddInputFromArray<quint8>(
       TensorShape({filter_size, filter_size, depth, filter_count}),
       {10, 40, 70, 20, 50, 80, 30, 60, 90});
-  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({}), {0});
+  AddInputFromArray<float>(TensorShape({}), {255.0f});
+  AddInputFromArray<float>(TensorShape({}), {0});
+  AddInputFromArray<float>(TensorShape({}), {255.0f});
 
   TF_ASSERT_OK(RunOpKernel());
   const int expected_width = image_width;
   const int expected_height = image_height * filter_count;
   Tensor expected(DT_QINT32, TensorShape({image_batch_count, expected_height,
@@ -199,14 +199,14 @@ TEST_F(QuantizedConv2DTest, OddPadding) {
   const int filter_size = 3;
   const int filter_count = 1;
   AddInputFromArray<quint8>(
       TensorShape({filter_size, filter_size, depth, filter_count}),
       {1, 2, 3, 4, 5, 6, 7, 8, 9});
-  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({}), {0});
+  AddInputFromArray<float>(TensorShape({}), {255.0f});
+  AddInputFromArray<float>(TensorShape({}), {0});
+  AddInputFromArray<float>(TensorShape({}), {255.0f});
 
   TF_ASSERT_OK(RunOpKernel());
   const int expected_width = image_width / stride;
   const int expected_height = (image_height * filter_count) / stride;
   Tensor expected(DT_QINT32, TensorShape({image_batch_count, expected_height,
@@ -242,14 +242,14 @@ TEST_F(QuantizedConv2DTest, OddPaddingBatch) {
   const int filter_size = 3;
   const int filter_count = 1;
   AddInputFromArray<quint8>(
       TensorShape({filter_size, filter_size, depth, filter_count}),
       {1, 2, 3, 4, 5, 6, 7, 8, 9});
-  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({}), {0});
+  AddInputFromArray<float>(TensorShape({}), {255.0f});
+  AddInputFromArray<float>(TensorShape({}), {0});
+  AddInputFromArray<float>(TensorShape({}), {255.0f});
 
   TF_ASSERT_OK(RunOpKernel());
   const int expected_width = image_width / stride;
   const int expected_height = (image_height * filter_count) / stride;
   Tensor expected(DT_QINT32, TensorShape({image_batch_count, expected_height,
@@ -300,14 +300,14 @@ TEST_F(QuantizedConv2DTest, SmallWithNoZero) {
       FloatTensorToQuantized<quint8>(filter_float, filter_min, filter_max);
   AddInputFromArray<quint8>(image_quantized.shape(),
                             image_quantized.flat<quint8>());
   AddInputFromArray<quint8>(filter_quantized.shape(),
                             filter_quantized.flat<quint8>());
-  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({}), {image_min});
+  AddInputFromArray<float>(TensorShape({}), {image_max});
+  AddInputFromArray<float>(TensorShape({}), {filter_min});
+  AddInputFromArray<float>(TensorShape({}), {filter_max});
   TF_ASSERT_OK(RunOpKernel());
   const int expected_width = image_width;
   const int expected_height = image_height * filter_count;
   Tensor expected_float(
       DT_FLOAT, TensorShape({image_batch_count, expected_height, expected_width,
diff --git a/tensorflow/python/ops/quantized_conv_ops_test.py b/tensorflow/python/ops/quantized_conv_ops_test.py
index 2d485d0ecb256..971abf339f75b 100644
--- a/tensorflow/python/ops/quantized_conv_ops_test.py
+++ b/tensorflow/python/ops/quantized_conv_ops_test.py
@@ -16,10 +16,12 @@
 
 import numpy as np
 
 from tensorflow.python.framework import constant_op
 from tensorflow.python.framework import dtypes
+from tensorflow.python.framework import errors
+from tensorflow.python.ops import math_ops
 from tensorflow.python.ops import nn_ops
 from tensorflow.python.platform import test
 
 
 class Conv2DTest(test.TestCase):
@@ -194,8 +196,73 @@ class Conv2DTest(test.TestCase):
         filter_in_sizes=[2, 2, 3, 3],
         stride=2,
         padding="SAME",
         expected=expected_output)
 
+  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")
 
 if __name__ == "__main__":
   test.main()

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")