CVE-2020-15205: Out-of-bounds Write (CWE-787)
value: In Tensorflow before versions 1.15.4, 2.0.3, 2.1.2, 2.2.1 and 2.3.1, the `data_splits` argument of `tf.raw_ops.StringNGrams` lacks validation. This allows a user to pass values that can cause heap overflow errors and even leak contents of memory In the linked code snippet, all the binary strings after `ee ff` are contents from the memory stack. Since these can contain return addresses, this data leak can be used to defeat ASLR. The issue is patched in commit 0462de5b544ed4731aa2fb23946ac22c01856b80, and is released in TensorFlow versions 1.15.4, 2.0.3, 2.1.2, 2.2.1, or 2.3.1.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 9.8
Published: 2020-09-25T19: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: 2020-09-18 16:00:29 -0700
Commit message: Validate `data_splits` for `tf.StringNGrams`.

Without validation, we can cause a heap buffer overflow which results in data leakage and/or segfaults.

PiperOrigin-RevId: 332543478
Change-Id: Iee5bda24497a195d09d122355502480830b1b317

Diff: diff --git a/tensorflow/core/kernels/string_ngrams_op.cc b/tensorflow/core/kernels/string_ngrams_op.cc
index 97b32c4242c..8aed2b3831a 100644
--- a/tensorflow/core/kernels/string_ngrams_op.cc
+++ b/tensorflow/core/kernels/string_ngrams_op.cc
@@ -17,10 +17,11 @@ limitations under the License.
 #include <string>
 
 #include "absl/strings/ascii.h"
 #include "absl/strings/str_cat.h"
 #include "tensorflow/core/framework/op_kernel.h"
+#include "tensorflow/core/platform/errors.h"
 
 namespace tensorflow {
 namespace text {
 
 namespace {
@@ -58,10 +59,22 @@ class StringNGramsOp : public tensorflow::OpKernel {
 
     const tensorflow::Tensor* splits;
     OP_REQUIRES_OK(context, context->input("data_splits", &splits));
     const auto& splits_vec = splits->flat<SPLITS_TYPE>();
 
+    // Validate that the splits are valid indices into data
+    const int input_data_size = data->flat<tstring>().size();
+    const int splits_vec_size = splits_vec.size();
+    for (int i = 0; i < splits_vec_size; ++i) {
+      bool valid_splits = splits_vec(i) >= 0;
+      valid_splits = valid_splits && (splits_vec(i) <= input_data_size);
+      OP_REQUIRES(
+          context, valid_splits,
+          errors::InvalidArgument("Invalid split value ", splits_vec(i),
+                                  ", must be in [0,", input_data_size, "]"));
+    }
+
     int num_batch_items = splits_vec.size() - 1;
     tensorflow::Tensor* ngrams_splits;
     OP_REQUIRES_OK(
         context, context->allocate_output(1, splits->shape(), &ngrams_splits));
     auto ngrams_splits_data = ngrams_splits->flat<SPLITS_TYPE>().data();
diff --git a/tensorflow/python/ops/raw_ops_test.py b/tensorflow/python/ops/raw_ops_test.py
index 850e96bb9ed..ee20d58d2f0 100644
--- a/tensorflow/python/ops/raw_ops_test.py
+++ b/tensorflow/python/ops/raw_ops_test.py
@@ -16,20 +16,25 @@
 
 from __future__ import absolute_import
 from __future__ import division
 from __future__ import print_function
 
+from absl.testing import parameterized
+
 from tensorflow.python.eager import context
 from tensorflow.python.framework import constant_op
+from tensorflow.python.framework import errors
 from tensorflow.python.framework import ops
 from tensorflow.python.framework import test_util
 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
-class RawOpsTest(test.TestCase):
+@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)))
 
@@ -56,9 +61,25 @@ class RawOpsTest(test.TestCase):
     x = constant_op.constant([[True]])
     self.assertAllClose(
         gen_math_ops.Any(input=x, axis=0),
         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"):
+      self.evaluate(
+          gen_string_ops.string_n_grams(
+              data=data,
+              data_splits=splits,
+              separator="",
+              ngram_widths=[2],
+              left_pad="",
+              right_pad="",
+              pad_width=0,
+              preserve_short_sequences=False))
+
 
 if __name__ == "__main__":
   ops.enable_eager_execution()
   test.main()

Vulnerable code: class RawOpsTest(test.TestCase):
Fixed code: #include "tensorflow/core/platform/errors.h"
    // Validate that the splits are valid indices into data
    const int input_data_size = data->flat<tstring>().size();
    const int splits_vec_size = splits_vec.size();
    for (int i = 0; i < splits_vec_size; ++i) {
      bool valid_splits = splits_vec(i) >= 0;
      valid_splits = valid_splits && (splits_vec(i) <= input_data_size);
      OP_REQUIRES(
          context, valid_splits,
          errors::InvalidArgument("Invalid split value ", splits_vec(i),
                                  ", must be in [0,", input_data_size, "]"));
    }

from absl.testing import parameterized

from tensorflow.python.framework import errors
from tensorflow.python.ops import gen_string_ops
@test_util.disable_tfrt
class RawOpsTest(test.TestCase, parameterized.TestCase):
  @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"):
      self.evaluate(
          gen_string_ops.string_n_grams(
              data=data,
              data_splits=splits,
              separator="",
              ngram_widths=[2],
              left_pad="",
              right_pad="",
              pad_width=0,
              preserve_short_sequences=False))