Commit message: Add missing validation in `QuantizedBatchNormWithGlobalNormalization`

PiperOrigin-RevId: 370123451
Change-Id: Id234d6dab1ec21230bb8e503dba30f899af87f33
CVE: CVE-2021-29547
CWE: CWE-369
Vulnerability description: TensorFlow is an end-to-end open source platform for machine learning. An attacker can cause a segfault and denial of service via accessing data outside of bounds in `tf.raw_ops.QuantizedBatchNormWithGlobalNormalization`. This is because the implementation(https://github.com/tensorflow/tensorflow/blob/55a97caa9e99c7f37a0bbbeb414dc55553d3ae7f/tensorflow/core/kernels/quantized_batch_norm_op.cc#L176-L189) assumes the inputs are not empty. If any of these inputs is empty, `.flat<T>()` is an empty buffer, so accessing the element at index 0 is accessing data outside of bounds. The fix will be included in TensorFlow 2.5.0. We will also cherrypick this commit on TensorFlow 2.4.2, TensorFlow 2.3.3, TensorFlow 2.2.3 and TensorFlow 2.1.4, as these are also affected and still in supported range.

Project: tensorflow
File: quantized_batch_norm_op.cc

void FixedPointBatchNorm(const Tensor& input, const float input_min,
                         const float input_max, const Tensor& mean,
                         float mean_min, float mean_max, const Tensor& var,
                         float var_min, float var_max, const Tensor& beta,
                         float beta_min, float beta_max, const Tensor& gamma,
                         float gamma_min, float gamma_max,
                         float variance_epsilon, bool scale_after_normalization,
                         Tensor* output, float* output_min, float* output_max) {
  auto input_flat = input.flat<T1>();
  auto mean_flat = mean.flat<T1>();
  auto var_flat = var.flat<T1>();
  auto beta_flat = beta.flat<T1>();
  auto gamma_flat = gamma.flat<T1>();
  auto output_flat = output->flat<T2>();

  const int depth = mean.dim_size(0);
  const int row_count = input_flat.size() / depth;

  // The range here is chosen so that typical input values fit in without any
  // overflow or loss of precision, going from +1m to -1m with 10 bits of fixed
  // point precision.
  *output_min = -(1 << 20);
  *output_max = (1 << 20);

  Tensor scale_tensor(DataTypeToEnum<T2>::v(), {depth});
  auto scale_flat = scale_tensor.flat<T2>();
  Tensor offset_tensor(DataTypeToEnum<T2>::v(), {depth});
  auto offset_flat = offset_tensor.flat<T2>();
  for (int channel = 0; channel < depth; ++channel) {
    const float mean_value =
        QuantizedToFloat(mean_flat(channel), mean_min, mean_max);
    const float var_value =
        QuantizedToFloat(var_flat(channel), var_min, var_max);
    const float beta_value =
        QuantizedToFloat(beta_flat(channel), beta_min, beta_max);
    const float gamma_value =
        QuantizedToFloat(gamma_flat(channel), gamma_min, gamma_max);
    float scale_value;
    if (scale_after_normalization) {
      scale_value = (1.0f / sqrtf(var_value + variance_epsilon)) * gamma_value;
    } else {
      scale_value = (1.0f / sqrtf(var_value + variance_epsilon));
    }
    const float offset_value = (-mean_value * scale_value) + beta_value;
    scale_flat(channel) =
        FloatToQuantized<T2>(scale_value, *output_min, *output_max);
    offset_flat(channel) =
        FloatToQuantized<T2>(offset_value, *output_min, *output_max);
  }

  const T2 one_in_output_space =
      FloatToQuantized<T2>(1.0f, *output_min, *output_max);
  for (int row_index = 0; row_index < row_count; ++row_index) {
    for (int channel = 0; channel < depth; ++channel) {
      const int input_index = (row_index * depth) + channel;
      const T2 input_value =
          RequantizeInNewRange<T1, T2>(input_flat(input_index), input_min,
                                       input_max, *output_min, *output_max);
      const T2 scale_value = scale_flat(channel);
      const T2 offset_value = offset_flat(channel);
      const T2 output_value =
          ((input_value * scale_value) / one_in_output_space) + offset_value;
      output_flat(input_index) = output_value;
    }
  }
}

Project: tensorflow
File: quantized_batch_norm_op.cc

  void Compute(OpKernelContext* context) override {
    const Tensor& input = context->input(0);
    const auto& input_min_tensor = context->input(1);
    OP_REQUIRES(context, input_min_tensor.NumElements() == 1,
                errors::InvalidArgument("input_min must have 1 element"));
    const float input_min = input_min_tensor.flat<float>()(0);
    const auto& input_max_tensor = context->input(2);
    OP_REQUIRES(context, input_max_tensor.NumElements() == 1,
                errors::InvalidArgument("input_max must have 1 element"));
    const float input_max = input_max_tensor.flat<float>()(0);
    const Tensor& mean = context->input(3);
    const auto& mean_min_tensor = context->input(4);
    OP_REQUIRES(context, mean_min_tensor.NumElements() == 1,
                errors::InvalidArgument("mean_min must have 1 element"));
    const float mean_min = mean_min_tensor.flat<float>()(0);
    const auto& mean_max_tensor = context->input(5);
    OP_REQUIRES(context, mean_max_tensor.NumElements() == 1,
                errors::InvalidArgument("mean_max must have 1 element"));
    const float mean_max = mean_max_tensor.flat<float>()(0);
    const Tensor& var = context->input(6);
    const auto& var_min_tensor = context->input(7);
    OP_REQUIRES(context, var_min_tensor.NumElements() == 1,
                errors::InvalidArgument("var_min must have 1 element"));
    const float var_min = var_min_tensor.flat<float>()(0);
    const auto& var_max_tensor = context->input(8);
    OP_REQUIRES(context, var_max_tensor.NumElements() == 1,
                errors::InvalidArgument("var_max must have 1 element"));
    const float var_max = var_max_tensor.flat<float>()(0);
    const Tensor& beta = context->input(9);
    const auto& beta_min_tensor = context->input(10);
    OP_REQUIRES(context, beta_min_tensor.NumElements() == 1,
                errors::InvalidArgument("beta_min must have 1 element"));
    const float beta_min = beta_min_tensor.flat<float>()(0);
    const auto& beta_max_tensor = context->input(11);
    OP_REQUIRES(context, beta_max_tensor.NumElements() == 1,
                errors::InvalidArgument("beta_max must have 1 element"));
    const float beta_max = beta_max_tensor.flat<float>()(0);
    const Tensor& gamma = context->input(12);
    const auto& gamma_min_tensor = context->input(13);
    OP_REQUIRES(context, gamma_min_tensor.NumElements() == 1,
                errors::InvalidArgument("gamma_min must have 1 element"));
    const float gamma_min = gamma_min_tensor.flat<float>()(0);
    const auto& gamma_max_tensor = context->input(14);
    OP_REQUIRES(context, gamma_max_tensor.NumElements() == 1,
                errors::InvalidArgument("gamma_max must have 1 element"));
    const float gamma_max = gamma_max_tensor.flat<float>()(0);

    OP_REQUIRES(context, input.dims() == 4,
                errors::InvalidArgument("input must be 4-dimensional",
                                        input.shape().DebugString()));
    OP_REQUIRES(context, mean.dims() == 1,
                errors::InvalidArgument("mean must be 1-dimensional",
                                        mean.shape().DebugString()));
    OP_REQUIRES(context, var.dims() == 1,
                errors::InvalidArgument("var must be 1-dimensional",
                                        var.shape().DebugString()));
    OP_REQUIRES(context, beta.dims() == 1,
                errors::InvalidArgument("beta must be 1-dimensional",
                                        beta.shape().DebugString()));
    OP_REQUIRES(context, gamma.dims() == 1,
                errors::InvalidArgument("gamma must be 1-dimensional",
                                        gamma.shape().DebugString()));
    OP_REQUIRES(context, mean.NumElements() > 1,
                errors::InvalidArgument("Must have at least a mean value",
                                        gamma.shape().DebugString()));
    OP_REQUIRES(context, mean.NumElements() > 1,
                errors::InvalidArgument("Must have at least a mean value"));
    const auto last_dim = input.shape().dims() - 1;
    OP_REQUIRES(context,
                mean.shape().dim_size(0) == input.shape().dim_size(last_dim),
                errors::InvalidArgument("Must provide as many means as the "
                                        "last dimension of the input tensor: ",
                                        mean.shape().DebugString(), " vs. ",
                                        input.shape().DebugString()));
    OP_REQUIRES(
        context, mean.shape().dim_size(0) == var.shape().dim_size(0),
        errors::InvalidArgument(
            "Mean and variance tensors must have the same shape: ",
            mean.shape().DebugString(), " vs. ", var.shape().DebugString()));
    OP_REQUIRES(
        context, mean.shape().dim_size(0) == beta.shape().dim_size(0),
        errors::InvalidArgument(
            "Mean and beta tensors must have the same shape: ",
            mean.shape().DebugString(), " vs. ", beta.shape().DebugString()));
    OP_REQUIRES(
        context, mean.shape().dim_size(0) == gamma.shape().dim_size(0),
        errors::InvalidArgument(
            "Mean and gamma tensors must have the same shape: ",
            mean.shape().DebugString(), " vs. ", gamma.shape().DebugString()));

    Tensor* output = nullptr;
    OP_REQUIRES_OK(context,
                   context->allocate_output(0, input.shape(), &output));
    float output_min;
    float output_max;
    FixedPointBatchNorm<T1, T2>(input, input_min, input_max, mean, mean_min,
                                mean_max, var, var_min, var_max, beta, beta_min,
                                beta_max, gamma, gamma_min, gamma_max,
                                variance_epsilon_, scale_after_normalization_,
                                output, &output_min, &output_max);

    Tensor* output_min_tensor = nullptr;
    OP_REQUIRES_OK(context,
                   context->allocate_output(1, {}, &output_min_tensor));
    output_min_tensor->flat<float>()(0) = output_min;

    Tensor* output_max_tensor = nullptr;
    OP_REQUIRES_OK(context,
                   context->allocate_output(2, {}, &output_max_tensor));
    output_max_tensor->flat<float>()(0) = output_max;
  }

Project: tensorflow
File: quantized_batch_norm_op.cc

void ReferenceBatchNorm(const Tensor& input, const float input_min,
                        const float input_max, const Tensor& mean,
                        float mean_min, float mean_max, const Tensor& var,
                        float var_min, float var_max, const Tensor& beta,
                        float beta_min, float beta_max, const Tensor& gamma,
                        float gamma_min, float gamma_max,
                        float variance_epsilon, bool scale_after_normalization,
                        Tensor* output, float* output_min, float* output_max) {
  auto input_flat = input.flat<T1>();
  auto mean_flat = mean.flat<T1>();
  auto var_flat = var.flat<T1>();
  auto beta_flat = beta.flat<T1>();
  auto gamma_flat = gamma.flat<T1>();
  auto output_flat = output->flat<T2>();

  const int depth = mean.dim_size(0);
  const int row_count = input_flat.size() / depth;

  *output_min = std::numeric_limits<float>::max();
  *output_max = std::numeric_limits<float>::lowest();
  for (int pass = 0; pass < 2; ++pass) {
    const bool is_range_pass = (pass == 0);
    for (int row_index = 0; row_index < row_count; ++row_index) {
      for (int channel = 0; channel < depth; ++channel) {
        const int input_index = (row_index * depth) + channel;
        const float input_value =
            QuantizedToFloat(input_flat(input_index), input_min, input_max);
        const float mean_value =
            QuantizedToFloat(mean_flat(channel), mean_min, mean_max);
        const float var_value =
            QuantizedToFloat(var_flat(channel), var_min, var_max);
        const float beta_value =
            QuantizedToFloat(beta_flat(channel), beta_min, beta_max);
        const float gamma_value =
            QuantizedToFloat(gamma_flat(channel), gamma_min, gamma_max);
        float output_value;
        if (scale_after_normalization) {
          output_value = (((input_value - mean_value) /
                           sqrtf(var_value + variance_epsilon)) *
                          gamma_value) +
                         beta_value;
        } else {
          output_value = ((input_value - mean_value) /
                          sqrtf(var_value + variance_epsilon)) +
                         beta_value;
        }
        if (is_range_pass) {
          *output_min = std::min(output_value, *output_min);
          *output_max = std::max(output_value, *output_max);
        } else {
          output_flat(input_index) =
              FloatToQuantized<T2>(output_value, *output_min, *output_max);
        }
      }
    }
  }
}

Project: tensorflow
File: quantized_batch_norm_op.cc

  explicit QuantizedBatchNormOp(OpKernelConstruction* context)
      : OpKernel(context) {
    OP_REQUIRES_OK(context,
                   context->GetAttr("variance_epsilon", &variance_epsilon_));
    OP_REQUIRES_OK(context, context->GetAttr("scale_after_normalization",
                                             &scale_after_normalization_));
  }