CVE-2022-21733: Integer Overflow or Wraparound (CWE-190)
value: Tensorflow is an Open Source Machine Learning Framework. The implementation of `StringNGrams` can be used to trigger a denial of service attack by causing an out of memory condition after an integer overflow. We are missing a validation on `pad_witdh` and that result in computing a negative value for `ngram_width` which is later used to allocate parts of the output. The fix will be included in TensorFlow 2.8.0. We will also cherrypick this commit on TensorFlow 2.7.1, TensorFlow 2.6.3, and TensorFlow 2.5.3, as these are also affected and still in supported range.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 6.5
Published: 2022-02-03T12:15Z
Repository: https://github.com/tensorflow/tensorflow
Language: Python
Files changed: tensorflow/core/kernels/string_ngrams_op.cc, tensorflow/python/ops/raw_ops_test.py
Commit date: 2021-11-30 16:57:47 -0800
Commit message: Add a check for pad width to be a positive value.

PiperOrigin-RevId: 413275853
Change-Id: I261a8db9dabf5ce48a806a9e58129080c9fac619

Diff: diff --git a/tensorflow/core/kernels/string_ngrams_op.cc b/tensorflow/core/kernels/string_ngrams_op.cc
index 6acf846f95812..07904fa04019f 100644
--- a/tensorflow/core/kernels/string_ngrams_op.cc
+++ b/tensorflow/core/kernels/string_ngrams_op.cc
@@ -150,10 +150,20 @@ class StringNGramsOp : public tensorflow::OpKernel {
           continue;
         }
         // We don't have to worry about dynamic padding sizes here: if padding
         // was dynamic, every sequence would have had sufficient padding to
         // generate at least one ngram.
+
+        // If reached here, pad_width should be > 0, pad_width_ = -1,
+        // which indicates max(ngram_widths) - 1 cannot be used here since
+        // ngram_width is not known.
+        OP_REQUIRES(
+            context, pad_width_ >= 0,
+            errors::InvalidArgument("Pad width should be >= 0 when "
+                                    "preserve_short_sequences is True and "
+                                    "ngram_widths are not provided, got ",
+                                    pad_width_));
         int ngram_width = data_length + 2 * pad_width_;
         auto output_start = &ngrams_data[output_start_idx];
         int num_ngrams = 1;
         CreateNgrams(data_start, output_start, num_ngrams, ngram_width);
       }
diff --git a/tensorflow/python/ops/raw_ops_test.py b/tensorflow/python/ops/raw_ops_test.py
index 953ab570f7d10..5097800d9ea13 100644
--- a/tensorflow/python/ops/raw_ops_test.py
+++ b/tensorflow/python/ops/raw_ops_test.py
@@ -26,11 +26,10 @@ from tensorflow.python.ops import gen_math_ops
 from tensorflow.python.ops import gen_string_ops
 from tensorflow.python.platform import test
 
 
 @test_util.run_all_in_graph_and_eager_modes
-@test_util.disable_tfrt
 class RawOpsTest(test.TestCase, parameterized.TestCase):
 
   def testSimple(self):
     x = constant_op.constant(1)
     self.assertEqual([2], self.evaluate(gen_math_ops.Add(x=x, y=x)))
@@ -61,12 +60,13 @@ class RawOpsTest(test.TestCase, parameterized.TestCase):
         gen_math_ops.Any(input=x, axis=0, keep_dims=False))
 
   @parameterized.parameters([[0, 8]], [[-1, 6]])
   def testStringNGramsBadDataSplits(self, splits):
     data = ["aa", "bb", "cc", "dd", "ee", "ff"]
-    with self.assertRaisesRegex(errors.InvalidArgumentError,
-                                "Invalid split value"):
+    with self.assertRaisesRegex(
+        errors.InvalidArgumentError,
+        r"Invalid split value|First split value must be 0"):
       self.evaluate(
           gen_string_ops.string_n_grams(
               data=data,
               data_splits=splits,
               separator="",
@@ -74,10 +74,29 @@ class RawOpsTest(test.TestCase, parameterized.TestCase):
               left_pad="",
               right_pad="",
               pad_width=0,
               preserve_short_sequences=False))
 
+  def testStringSplit(self):
+    data = ["123456"]
+    data_splits = [0, 1]
+    separator = "a" * 15
+    ngram_widths = []
+    pad_width = -5
+    left_pad = right_pad = ""
+    with self.assertRaisesRegex(errors.InvalidArgumentError,
+                                "Pad width should be >= 0"):
+      self.evaluate(gen_string_ops.string_n_grams(
+          data=data,
+          data_splits=data_splits,
+          separator=separator,
+          ngram_widths=ngram_widths,
+          left_pad=left_pad,
+          right_pad=right_pad,
+          pad_width=pad_width,
+          preserve_short_sequences=True))
+
   def testGetSessionHandle(self):
     if context.executing_eagerly():
       with self.assertRaisesRegex(
           errors.FailedPreconditionError,
           "GetSessionHandle called on null session state"):

Vulnerable code: @test_util.disable_tfrt
    with self.assertRaisesRegex(errors.InvalidArgumentError,
                                "Invalid split value"):
Fixed code: 
        // If reached here, pad_width should be > 0, pad_width_ = -1,
        // which indicates max(ngram_widths) - 1 cannot be used here since
        // ngram_width is not known.
        OP_REQUIRES(
            context, pad_width_ >= 0,
            errors::InvalidArgument("Pad width should be >= 0 when "
                                    "preserve_short_sequences is True and "
                                    "ngram_widths are not provided, got ",
                                    pad_width_));
    with self.assertRaisesRegex(
        errors.InvalidArgumentError,
        r"Invalid split value|First split value must be 0"):
  def testStringSplit(self):
    data = ["123456"]
    data_splits = [0, 1]
    separator = "a" * 15
    ngram_widths = []
    pad_width = -5
    left_pad = right_pad = ""
    with self.assertRaisesRegex(errors.InvalidArgumentError,
                                "Pad width should be >= 0"):
      self.evaluate(gen_string_ops.string_n_grams(
          data=data,
          data_splits=data_splits,
          separator=separator,
          ngram_widths=ngram_widths,
          left_pad=left_pad,
          right_pad=right_pad,
          pad_width=pad_width,
          preserve_short_sequences=True))