VYPR
Moderate severityNVD Advisory· Published May 20, 2022· Updated Apr 22, 2025

Missing validation in `QuantizedConv2D` results in undefined behavior in TensorFlow

CVE-2022-29201

Description

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.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
tensorflowPyPI
< 2.6.42.6.4
tensorflowPyPI
>= 2.7.0, < 2.7.22.7.2
tensorflowPyPI
>= 2.8.0, < 2.8.12.8.1
tensorflow-cpuPyPI
< 2.6.42.6.4
tensorflow-cpuPyPI
>= 2.7.0, < 2.7.22.7.2
tensorflow-cpuPyPI
>= 2.8.0, < 2.8.12.8.1
tensorflow-gpuPyPI
< 2.6.42.6.4
tensorflow-gpuPyPI
>= 2.7.0, < 2.7.22.7.2
tensorflow-gpuPyPI
>= 2.8.0, < 2.8.12.8.1

Affected products

1

Patches

1
0f0b080ecde4

Fix undefined behavior in QuantizedConv2D

https://github.com/tensorflow/tensorflowAntonio SanchezApr 29, 2022via ghsa
3 files changed · +107 26
  • tensorflow/core/kernels/quantized_conv_ops.cc+20 6 modified
    @@ -18,8 +18,6 @@ limitations under the License.
     #include <algorithm>
     #include <vector>
     
    -#include "tensorflow/core/platform/errors.h"
    -
     #define EIGEN_USE_THREADS
     
     #define GEMMLOWP_ALLOW_SLOW_SCALAR_FALLBACK
    @@ -32,6 +30,7 @@ limitations under the License.
     #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 {
    @@ -499,11 +498,26 @@ class QuantizedConv2DOp : public OpKernel {
     
         // 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);
    
  • tensorflow/core/kernels/quantized_conv_ops_test.cc+20 20 modified
    @@ -91,10 +91,10 @@ TEST_F(QuantizedConv2DTest, Small) {
                                 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
    @@ -158,10 +158,10 @@ TEST_F(QuantizedConv2DTest, Small32Bit) {
       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;
    @@ -201,10 +201,10 @@ TEST_F(QuantizedConv2DTest, OddPadding) {
       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;
    @@ -244,10 +244,10 @@ TEST_F(QuantizedConv2DTest, OddPaddingBatch) {
       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;
    @@ -302,10 +302,10 @@ TEST_F(QuantizedConv2DTest, SmallWithNoZero) {
                                 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;
    
  • tensorflow/python/ops/quantized_conv_ops_test.py+67 0 modified
    @@ -18,6 +18,8 @@
     
     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
     
    @@ -196,6 +198,71 @@ def testConv2D2x2FilterStride2Same(self):
             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()
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.