CVE-2021-29591 (C++, published 2021-05-14T20:15Z)
Description: value: TensorFlow is an end-to-end open source platform for machine learning. TFlite graphs must not have loops between nodes. However, this condition was not checked and an attacker could craft models that would result in infinite loop during evaluation. In certain cases, the infinite loop would be replaced by stack overflow due to too many recursive calls. For example, the `While` implementation(https://github.com/tensorflow/tensorflow/blob/106d8f4fb89335a2c52d7c895b7a7485465ca8d9/tensorflow/lite/kernels/while.cc) could be tricked into a scneario where both the body and the loop subgraphs are the same. Evaluating one of the subgraphs means calling the `Eval` function for the other and this quickly exhaust all stack space. 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. Please consult our security guide(https://github.com/tensorflow/tensorflow/blob/master/SECURITY.md) for more information regarding the security model and how to contact us with issues and questions.
Severity: MEDIUM (CVSS v3 base score: 7.8)
Weakness: CWE-835 \u2014 Loop with Unreachable Exit Condition ('Infinite Loop')
The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
Repository: https://github.com/tensorflow/tensorflow
Files changed:
tensorflow/lite/BUILD
tensorflow/lite/core/subgraph.cc
tensorflow/lite/core/subgraph.h
tensorflow/lite/kernels/while.cc
tensorflow/lite/model_test.cc
Commit message: TFLite: Error out when the graph has a recurion.

Recursion is currently unsupported.

PiperOrigin-RevId: 371708957
Change-Id: I8dfad0d85cbfe08e39ae8ea7bad21254ddee5003


Vulnerable code:
  TF_LITE_ENSURE(context,
                 op_data->cond_subgraph_index != op_data->body_subgraph_index);

Fixed code:
        "testdata/unsupported_recursion.bin",
// An utility test to detect if the subgraph is abused:
// 1. Detects if recursion exists in the graph (recursion is not currently
//    supported.
// 2. Detects if the interpreter / subgraph is used in multiple subgraphs.
//    Note: It's clearly documented that the interpreter / subgraph are not
//    thread-safe. This serves as a check with possible false negatives
//    unless we switch to atomic boolean flags.
class SubgraphGuard {
 public:
  SubgraphGuard(TfLiteContext* context, bool* is_subgraph_in_use)
      : is_subgraph_in_use_(is_subgraph_in_use) {
    if (*is_subgraph_in_use_) {
      TF_LITE_KERNEL_LOG(
          context,
          "Subgraph is already in use. Using an interpreter or a subgraph in "
          "multiple threads is not supported. Recursion in the graph is not "
          "supported.");
      status_ = kTfLiteError;
    } else {
      *is_subgraph_in_use_ = true;
    }
  }
  ~SubgraphGuard() {
    // If tht original status was OK, recover the boolean flag.
    if (status_ == kTfLiteOk) {
      *is_subgraph_in_use_ = false;
    }
  }

  TfLiteStatus status() const { return status_; }

 private:
  TfLiteStatus status_ = kTfLiteOk;
  bool* is_subgraph_in_use_;
};


  // Note `AllocateTensors` sometimes calls itself recursively above
  // for delegates. Therefore only the logic below need to be guarded
  // by `SubgraphGuard`.
  SubgraphGuard guard(&context_, &is_subgraph_in_use_);
  TF_LITE_ENSURE_OK(&context_, guard.status());

  SubgraphGuard guard(&context_, &is_subgraph_in_use_);
  TF_LITE_ENSURE_OK(&context_, guard.status());


  // Whether the subgraph is currently in use (e.g. running the `Invoke`
  // or `AllocateTensors` functions).
  bool is_subgraph_in_use_ = false;
// Recursion & reentrant are not supported in TFLite.
// The test ensures it fails gracefullly instead of crashing with
// a stack overflow.
TEST(BasicFlatBufferModel, TestUnsupportedRecursion) {
  const auto model_path =
      "tensorflow/lite/testdata/unsupported_recursion.bin";

  std::unique_ptr<tflite::FlatBufferModel> model =
      FlatBufferModel::BuildFromFile(model_path);
  ASSERT_NE(model, nullptr);

  tflite::ops::builtin::BuiltinOpResolver resolver;
  InterpreterBuilder builder(*model, resolver);
  std::unique_ptr<Interpreter> interpreter;
  ASSERT_EQ(builder(&interpreter), kTfLiteOk);
  ASSERT_NE(interpreter, nullptr);
  ASSERT_NE(interpreter->AllocateTensors(), kTfLiteOk);
}