CVE-2020-26267 (Python, published 2020-12-10T23:15Z)
Description: value: In affected versions of TensorFlow the tf.raw_ops.DataFormatVecPermute API does not validate the src_format and dst_format attributes. The code assumes that these two arguments define a permutation of NHWC. This can result in uninitialized memory accesses, read outside of bounds and even crashes. This is fixed in versions 1.15.5, 2.0.4, 2.1.3, 2.2.2, 2.3.2, and 2.4.0.
Severity: MEDIUM (CVSS v3 base score: 7.8)
Weakness: CWE-125 \u2014 Out-of-bounds Read
The product reads data past the end, or before the beginning, of the intended buffer.
Repository: https://github.com/tensorflow/tensorflow
Files changed:
tensorflow/core/kernels/data_format_ops.cc
tensorflow/python/ops/nn_test.py
Commit message: Validate that `DataFormat*` attributes form a permutation.

The `src_format` and `dst_format` attributes for the `DataFormatDimMap` and `DataFormatVecPermute` raw ops are supposed to determine a permutation. However, this was not validated and could result in unitialized memory accesses as well as writes outside of bounds and potential crashes.

While here, we also test that the format attributes have the needed length, add tests for all validation failure cases, remove unnecessary calls to `strings::StrCat`, and fix a few grammar errors.

This will be cherry-picked on the supported release branches.

PiperOrigin-RevId: 346135579
Change-Id: I1c76392382c89ad8f072d5bc93d70669851eb404


Vulnerable code:
                errors::InvalidArgument(strings::StrCat(
                    "Source format must of length 4 or 5, received "
                    src_format)));
        context, dst_format.size() == 4 || dst_format.size() == 5,
        errors::InvalidArgument(strings::StrCat(
            "Destination format must of length 4 or 5, received dst_format = ",
            dst_format)));

Fixed code:

#include <map>

#include "tensorflow/core/platform/errors.h"
// Ensure that `src` and `dst` define a valid permutation.
// Ops defined in this file assume that user specifies a permutation via two
// string attributes. This check validates that these attributes properly define
// it to prevent security vulnerabilities.
static bool IsValidPermutation(const std::string& src, const std::string& dst) {
  if (src.size() != dst.size()) {
    return false;
  }

  std::map<char, bool> characters;

  // Every character in `src` must be present only once
  for (const auto c : src) {
    if (characters[c]) {
      return false;
    }
    characters[c] = true;
  }

  // Every character in `dst` must show up in `src` exactly once
  for (const auto c : dst) {
    if (!characters[c]) {
      return false;
    }
    characters[c] = false;
  }

  // At this point, characters[] has been switched to true and false exactly
  // once for all character in `src` (and `dst`) so we have a valid permutation
  return true;
}

                errors::InvalidArgument(
                    "Source format must be of length 4 or 5, received "
                    src_format));
    OP_REQUIRES(context, dst_format.size() == 4 || dst_format.size() == 5,
                errors::InvalidArgument("Destination format must be of length "
                                        "4 or 5, received dst_format = ",
                                        dst_format));
        context, IsValidPermutation(src_format, dst_format),
        errors::InvalidArgument(
            "Destination and source format must determine a permutation, got ",
            src_format, " and ", dst_format));
    OP_REQUIRES(context, src_format.size() == 4 || src_format.size() == 5,
                errors::InvalidArgument(
                    "Source format must be of length 4 or 5, received "
                    "src_format = ",
                    src_format));
    OP_REQUIRES(context, dst_format.size() == 4 || dst_format.size() == 5,
                errors::InvalidArgument("Destination format must be of length "
                                        "4 or 5, received dst_format = ",
                                        dst_format));
    OP_REQUIRES(
        context, IsValidPermutation(src_format, dst_format),
        errors::InvalidArgument(
            "Destination and source format must determine a permutation, got ",
            src_format, " and ", dst_format));
      OP_REQUIRES(context,
                  src_format_str.size() == 2 && dst_format_str.size() == 2,
                  errors::InvalidArgument(
                      "Format specifier must contain H and W for 2D case"));
from tensorflow.python.framework import errors
  @test_util.disable_xla("XLA catches the error and rethrows as different one")
  @test_util.disable_xla("XLA catches the error and rethrows as different one")
  def testInvalidLength(self):
    x = [-4, -3, -2, -1, 0, 1, 2, 3]
    with self.assertRaisesRegex(errors.InvalidArgumentError,
                                "Source format must be of length 4 or 5"):
      op = nn_ops.data_format_dim_map(
          x, src_format="12345678", dst_format="87654321")
      with test_util.use_gpu():
        self.evaluate(op)

  @test_util.disable_xla("XLA catches the error and rethrows as different one")
  def testDuplicateSrc(self):
    x = [-4, -3, -2, -1, 0, 1, 2, 3]
    with self.assertRaisesRegex(
        errors.InvalidArgumentError,
        "Destination and source format must determine a permutation"):
      op = nn_ops.data_format_dim_map(x, src_format="1233", dst_format="4321")
      with test_util.use_gpu():
        self.evaluate(op)

  @test_util.disable_xla("XLA catches the error and rethrows as different one")
  def testDuplicateDst(self):
    x = [-4, -3, -2, -1, 0, 1, 2, 3]
    with self.assertRaisesRegex(
        errors.InvalidArgumentError,
        "Destination and source format must determine a permutation"):
      op = nn_ops.data_format_dim_map(x, src_format="1234", dst_format="3321")
      with test_util.use_gpu():
        self.evaluate(op)

  @test_util.disable_xla("XLA catches the error and rethrows as different one")
  def testExtraSpecifiers(self):
    x = [-4, -3, -2, -1, 0, 1, 2, 3]
    with self.assertRaisesRegex(
        errors.InvalidArgumentError,
        "Destination and source format must determine a permutation"):
      op = nn_ops.data_format_dim_map(x, src_format="1234", dst_format="5321")
      with test_util.use_gpu():
        self.evaluate(op)

  @test_util.disable_xla("XLA catches the error and rethrows as different one")
  def testInvalidLength(self):
    x = [0, 1, 2, 3]
    with self.assertRaisesRegex(errors.InvalidArgumentError,
                                "Source format must be of length 4 or 5"):
      op = nn_ops.data_format_vec_permute(
          x, src_format="12345678", dst_format="87654321")
      with test_util.use_gpu():
        self.evaluate(op)

  @test_util.disable_xla("XLA catches the error and rethrows as different one")
  def testDuplicateSrc(self):
    x = [0, 1, 2, 3]
    with self.assertRaisesRegex(
        errors.InvalidArgumentError,
        "Destination and source format must determine a permutation"):
      op = nn_ops.data_format_vec_permute(
          x, src_format="1233", dst_format="4321")
      with test_util.use_gpu():
        self.evaluate(op)

  @test_util.disable_xla("XLA catches the error and rethrows as different one")
  def testDuplicateDst(self):
    x = [0, 1, 2, 3]
    with self.assertRaisesRegex(
        errors.InvalidArgumentError,
        "Destination and source format must determine a permutation"):
      op = nn_ops.data_format_vec_permute(
          x, src_format="1234", dst_format="3321")
      with test_util.use_gpu():
        self.evaluate(op)

  @test_util.disable_xla("XLA catches the error and rethrows as different one")
  def testExtraSpecifiers(self):
    x = [0, 1, 2, 3]
    with self.assertRaisesRegex(
        errors.InvalidArgumentError,
        "Destination and source format must determine a permutation"):
      op = nn_ops.data_format_vec_permute(
          x, src_format="1234", dst_format="5321")
      with test_util.use_gpu():
        self.evaluate(op)

  @test_util.disable_xla("XLA catches the error and rethrows as different one")
  def test2DNoWH(self):
    x = [[0, 1], [2, 3]]
    with self.assertRaisesRegex(
        errors.InvalidArgumentError,
        "Format specifier must contain H and W for 2D case"):
      op = nn_ops.data_format_vec_permute(
          x, src_format="1234", dst_format="4321")
      with test_util.use_gpu():
        self.evaluate(op)