CVE-2022-41889: NULL Pointer Dereference (CWE-476)
value: TensorFlow is an open source platform for machine learning. If a list of quantized tensors is assigned to an attribute, the pywrap code fails to parse the tensor and returns a `nullptr`, which is not caught. An example can be seen in `tf.compat.v1.extract_volume_patches` by passing in quantized tensors as input `ksizes`. We have patched the issue in GitHub commit e9e95553e5411834d215e6770c81a83a3d0866ce. The fix will be included in TensorFlow 2.11. We will also cherrypick this commit on TensorFlow 2.10.1, 2.9.3, and TensorFlow 2.8.4, as these are also affected and still in supported range.
CWE description: The product dereferences a pointer that it expects to be valid but is NULL.
CVSS v3 base score: 7.5
Published: 2022-11-18T22:15Z
Repository: https://github.com/tensorflow/tensorflow
Language: Python
Files changed: tensorflow/python/eager/pywrap_tfe_src.cc, tensorflow/python/kernel_tests/image_ops/extract_image_patches_op_test.py
Commit date: 2022-09-26 14:03:34 -0700
Commit message: Fix pywrap attribute read security vulnerability.

If a list of quantized tensors is assigned to an attribute, the pywrap code was failing to
parse the tensor and returning a `nullptr`, which wasn't caught.  Here we check the return
value and set an appropriate error status.

PiperOrigin-RevId: 476981029

Diff: diff --git a/tensorflow/python/eager/pywrap_tfe_src.cc b/tensorflow/python/eager/pywrap_tfe_src.cc
index 83f1565f15e28..7b3a8048c154b 100644
--- a/tensorflow/python/eager/pywrap_tfe_src.cc
+++ b/tensorflow/python/eager/pywrap_tfe_src.cc
@@ -395,15 +395,24 @@ bool SetOpAttrList(TFE_Context* ctx, TFE_Op* op, const char* key,
     return false;
   }
   const int num_values = PySequence_Size(py_list);
   if (attr_list_sizes != nullptr) (*attr_list_sizes)[key] = num_values;
 
-#define PARSE_LIST(c_type, parse_fn)                                      \
-  std::unique_ptr<c_type[]> values(new c_type[num_values]);               \
-  for (int i = 0; i < num_values; ++i) {                                  \
-    tensorflow::Safe_PyObjectPtr py_value(PySequence_ITEM(py_list, i));   \
-    if (!parse_fn(key, py_value.get(), status, &values[i])) return false; \
+#define PARSE_LIST(c_type, parse_fn)                                       \
+  std::unique_ptr<c_type[]> values(new c_type[num_values]);                \
+  for (int i = 0; i < num_values; ++i) {                                   \
+    tensorflow::Safe_PyObjectPtr py_value(PySequence_ITEM(py_list, i));    \
+    if (py_value == nullptr) {                                             \
+      TF_SetStatus(status, TF_INVALID_ARGUMENT,                            \
+                   tensorflow::strings::StrCat(                            \
+                       "Expecting sequence of " #c_type " for attr ", key, \
+                       ", got ", py_list->ob_type->tp_name)                \
+                       .c_str());                                          \
+      return false;                                                        \
+    } else if (!parse_fn(key, py_value.get(), status, &values[i])) {       \
+      return false;                                                        \
+    }                                                                      \
   }
 
   if (type == TF_ATTR_STRING) {
     std::unique_ptr<const void*[]> values(new const void*[num_values]);
     std::unique_ptr<size_t[]> lengths(new size_t[num_values]);
diff --git a/tensorflow/python/kernel_tests/image_ops/extract_image_patches_op_test.py b/tensorflow/python/kernel_tests/image_ops/extract_image_patches_op_test.py
index 9d9b7bf7248d4..3247fbb428adb 100644
--- a/tensorflow/python/kernel_tests/image_ops/extract_image_patches_op_test.py
+++ b/tensorflow/python/kernel_tests/image_ops/extract_image_patches_op_test.py
@@ -15,11 +15,13 @@
 """Functional tests for ExtractImagePatches op."""
 
 import numpy as np
 
 from tensorflow.python.framework import constant_op
+from tensorflow.python.framework import dtypes
 from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import math_ops
 from tensorflow.python.platform import test
 
 
 class ExtractImagePatches(test.TestCase):
   """Functional tests for ExtractImagePatches op."""
@@ -137,8 +139,19 @@ class ExtractImagePatches(test.TestCase):
             strides=[1, 1],
             rates=[1, 1],
             padding=padding,
             patches=patches)
 
+  def testInvalidAttributes(self):
+    """Test for passing weird things into ksizes."""
+    with self.assertRaisesRegex(TypeError, "Expected list"):
+      image = constant_op.constant([0.0])
+      ksizes = math_ops.cast(
+          constant_op.constant(dtype=dtypes.int16, value=[[1, 4], [5, 2]]),
+          dtype=dtypes.qint16)
+      strides = [1, 1, 1, 1]
+      self.evaluate(
+          array_ops.extract_image_patches(
+              image, ksizes=ksizes, strides=strides, padding="SAME"))
 
 if __name__ == "__main__":
   test.main()

Vulnerable code: #define PARSE_LIST(c_type, parse_fn)                                      \
  std::unique_ptr<c_type[]> values(new c_type[num_values]);               \
  for (int i = 0; i < num_values; ++i) {                                  \
    tensorflow::Safe_PyObjectPtr py_value(PySequence_ITEM(py_list, i));   \
    if (!parse_fn(key, py_value.get(), status, &values[i])) return false; \
Fixed code: #define PARSE_LIST(c_type, parse_fn)                                       \
  std::unique_ptr<c_type[]> values(new c_type[num_values]);                \
  for (int i = 0; i < num_values; ++i) {                                   \
    tensorflow::Safe_PyObjectPtr py_value(PySequence_ITEM(py_list, i));    \
    if (py_value == nullptr) {                                             \
      TF_SetStatus(status, TF_INVALID_ARGUMENT,                            \
                   tensorflow::strings::StrCat(                            \
                       "Expecting sequence of " #c_type " for attr ", key, \
                       ", got ", py_list->ob_type->tp_name)                \
                       .c_str());                                          \
      return false;                                                        \
    } else if (!parse_fn(key, py_value.get(), status, &values[i])) {       \
      return false;                                                        \
    }                                                                      \
from tensorflow.python.framework import dtypes
from tensorflow.python.ops import math_ops
  def testInvalidAttributes(self):
    """Test for passing weird things into ksizes."""
    with self.assertRaisesRegex(TypeError, "Expected list"):
      image = constant_op.constant([0.0])
      ksizes = math_ops.cast(
          constant_op.constant(dtype=dtypes.int16, value=[[1, 4], [5, 2]]),
          dtype=dtypes.qint16)
      strides = [1, 1, 1, 1]
      self.evaluate(
          array_ops.extract_image_patches(
              image, ksizes=ksizes, strides=strides, padding="SAME"))