CVE-2022-35989: Reachable Assertion (CWE-617)
value: TensorFlow is an open source platform for machine learning. When `MaxPool` receives a window size input array `ksize` with dimensions greater than its input tensor `input`, the GPU kernel gives a `CHECK` fail that can be used to trigger a denial of service attack. We have patched the issue in GitHub commit 32d7bd3defd134f21a4e344c8dfd40099aaf6b18. The fix will be included in TensorFlow 2.10.0. We will also cherrypick this commit on TensorFlow 2.9.1, TensorFlow 2.8.1, and TensorFlow 2.7.2, as these are also affected and still in supported range. There are no known workarounds for this issue.
CWE description: The product contains an assert() or similar statement that can be triggered by an attacker, which leads to an application exit or other behavior that is more severe than necessary.
CVSS v3 base score: 7.5
Published: 2022-09-16T22:15Z
Repository: https://github.com/tensorflow/tensorflow
Language: Python
Files changed: tensorflow/core/kernels/maxpooling_op.cc, tensorflow/python/kernel_tests/nn_ops/pooling_ops_test.py
Commit date: 2022-07-22 13:03:45 -0700
Commit message: Fix MaxPool crash on GPU for invalid filter size.

If the filter size exceeds the input size by one for `VALID` padding,
return an empty tensor. This is consistent with XLA.

PiperOrigin-RevId: 462684864

Diff: diff --git a/tensorflow/core/kernels/maxpooling_op.cc b/tensorflow/core/kernels/maxpooling_op.cc
index 9edd5cf6a6d52..4fb198690d5ad 100644
--- a/tensorflow/core/kernels/maxpooling_op.cc
+++ b/tensorflow/core/kernels/maxpooling_op.cc
@@ -1266,10 +1266,17 @@ class MaxPoolingNoMaskOp<GPUDevice, T> : public OpKernel {
 
     TensorShape out_shape =
         ShapeFromFormat(data_format_, params.tensor_in_batch, params.out_height,
                         params.out_width, params.depth);
 
+    // Degenerate pooling output should return an empty tensor.
+    if (out_shape.num_elements() == 0) {
+      Tensor* output = nullptr;
+      OP_REQUIRES_OK(context, context->allocate_output(0, out_shape, &output));
+      return;
+    }
+
     // Assuming qint8 <--> NCHW_VECT_C (int8x4) here.
     constexpr bool is_int8x4 = std::is_same<T, qint8>::value;
     OP_REQUIRES(context, (is_int8x4 == (data_format_ == FORMAT_NCHW_VECT_C)),
                 errors::InvalidArgument(
                     "qint8 should be used with data_format NCHW_VECT_C."));
diff --git a/tensorflow/python/kernel_tests/nn_ops/pooling_ops_test.py b/tensorflow/python/kernel_tests/nn_ops/pooling_ops_test.py
index 2eabfbe3957b5..e279f6e0027da 100644
--- a/tensorflow/python/kernel_tests/nn_ops/pooling_ops_test.py
+++ b/tensorflow/python/kernel_tests/nn_ops/pooling_ops_test.py
@@ -770,10 +770,22 @@ class PoolingTest(test.TestCase, parameterized.TestCase):
         strides=[1, 2, 2, 1],
         padding="SAME",
         expected=[],
         **kwargs)
 
+  @parameterized.parameters(
+      GetTestConfigsDicts(nn_ops.max_pool, gen_nn_ops.max_pool_v2))
+  @test_util.run_deprecated_v1
+  def testMaxPoolInvalidFilterSize(self, **kwargs):
+    with self.cached_session(use_gpu=test.is_gpu_available()):
+      t = constant_op.constant(1.0, shape=[1, 1, 1, 1])
+      with self.assertRaisesRegex(
+          (errors_impl.InvalidArgumentError, ValueError),
+          "Negative dimension size"):
+        t = self.evaluate(
+            nn_ops.max_pool(t, ksize=[1, 1, 2, 1], strides=1, padding="VALID"))
+
   # Tests for DepthwiseMaxPooling on CPU only.
   @parameterized.parameters(
       GetTestConfigsDicts(
           nn_ops.max_pool, gen_nn_ops.max_pool_v2, allow_gpu=False))
   @test_util.run_deprecated_v1

Fixed code:     // Degenerate pooling output should return an empty tensor.
    if (out_shape.num_elements() == 0) {
      Tensor* output = nullptr;
      OP_REQUIRES_OK(context, context->allocate_output(0, out_shape, &output));
      return;
    }

  @parameterized.parameters(
      GetTestConfigsDicts(nn_ops.max_pool, gen_nn_ops.max_pool_v2))
  @test_util.run_deprecated_v1
  def testMaxPoolInvalidFilterSize(self, **kwargs):
    with self.cached_session(use_gpu=test.is_gpu_available()):
      t = constant_op.constant(1.0, shape=[1, 1, 1, 1])
      with self.assertRaisesRegex(
          (errors_impl.InvalidArgumentError, ValueError),
          "Negative dimension size"):
        t = self.evaluate(
            nn_ops.max_pool(t, ksize=[1, 1, 2, 1], strides=1, padding="VALID"))