You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

TFLite FlatBufferModel::GetMinimumRuntime() out-of-bounds read (unchecked Metadata.buffer index)

Gated PoC repository β€” access granted to protectai-bot / huntr triage only.

Summary

tflite::impl::FlatBufferModelBase<T>::GetMinimumRuntime(), defined in tensorflow/compiler/mlir/lite/core/model_builder_base.h (reached via the public tflite::FlatBufferModel class exposed in tensorflow/lite/model_builder.h, the standard TFLite model-loading entry point used by every embedder), indexes the model's buffers vector using an attacker-controlled uint32_t field (Metadata.buffer) without any bounds check:

std::string GetMinimumRuntime() const {
  if (!model_ || !model_->metadata()) return "";

  for (int i = 0; i < model_->metadata()->size(); ++i) {
    auto metadata = model_->metadata()->Get(i);
    if (metadata->name()->str() == tflite_metadata_min_runtime_version) {
      auto buf = metadata->buffer();
      auto* buffer = (*model_->buffers())[buf];   // <-- unchecked index (line 476)
      auto* array = buffer->data();               // <-- (line 477)
      ...

Metadata.buffer is a plain uint32 schema field (tensorflow/compiler/mlir/lite/schema/schema_generated.h), fully attacker controlled, and the generic flatbuffers structural verifier (Metadata::Verify()) only checks that the field is present/well-typed β€” it has no notion of the semantic constraint "must be < Model.buffers.size()". FlatBufferModelBase<T>::VerifyAndBuildFromAllocation() (the "safe", recommended load path β€” tflite::VerifyModelBuffer() + flatbuffers::Verifier) does not check this relational constraint either, so a model with an out-of-range Metadata.buffer is accepted by the standard verified-load API.

This is a real, and in our reading of the current source, unfixed regression relative to the sibling function ReadAllMetadata() in the same file, which explicitly guards the identical field:

static std::map<std::string, std::string> ReadAllMetadata(
    const ::tflite::Model* model) {
  ...
  for (size_t i = 0; i < model->metadata()->size(); ++i) {
    auto metadata = model->metadata()->Get(i);
    auto buf = metadata->buffer();
    if (buf >= model->buffers()->size()) continue;   // <-- bounds check present here
    ...

InterpreterBuilder::ParseTensors (tensorflow/lite/core/interpreter_builder.cc) similarly bounds-checks the analogous Tensor.buffer field before indexing buffers. GetMinimumRuntime() is the one place that indexes Model.buffers with an attacker-controlled index and skips this check entirely.

Attacker input β†’ sink

  1. Attacker supplies a .tflite file (e.g. to a model-management/validation service, an MLOps pipeline, an on-device model updater, or any code that loads a third-party/untrusted model) containing:
    • buffers: a single empty sentinel buffer (size 1, satisfies the documented "0th buffer must be empty" convention).
    • metadata: one entry { name: "min_runtime_version", buffer: <huge or merely out-of-range uint32> }.
  2. Victim code calls tflite::FlatBufferModel::VerifyAndBuildFromBuffer(...) (or BuildFromBuffer/BuildFromFile, all funnel to the same internal logic) β€” the model is accepted.
  3. Victim code calls model->GetMinimumRuntime() β€” a documented public API method, commonly used to gate/log runtime-compatibility before deploying a model β€” which dereferences (*model_->buffers())[buf] with the unchecked index, reading out of the bounds of the underlying flatbuffer allocation.

Real, reproduced evidence

We compiled the actual, unmodified TFLite source (tensorflow/compiler/mlir/lite/core/model_builder_base.h, tensorflow/lite/model_builder.h, tensorflow/compiler/mlir/lite/allocation.cc, tensorflow/compiler/mlir/lite/core/api/error_reporter.cc, and the generated schema_generated.h) together with a small driver (harness.cc, included in this repo) that:

  1. Builds a malicious .tflite flatbuffer exactly as described above (evil_metadata_buffer_oob.tflite, included, Metadata.buffer = 100, Model.buffers size 1).
  2. Loads it through the real, public, "safe" entry point: tflite::FlatBufferModel::VerifyAndBuildFromBuffer(...) β€” confirmed accepted.
  3. Calls model->GetMinimumRuntime().

Compiled with Clang + AddressSanitizer, -O2 -DNDEBUG (the same NDEBUG/optimized configuration TFLite release/production builds use β€” FLATBUFFERS_ASSERT expands to a no-op assert() under NDEBUG, so the flatbuffers library's own internal debug-only assertion, which "usually" saves the day in a plain debug build, is compiled out in every real production build (pip wheels, Android/iOS release builds, bazel -c opt)).

Result 1 β€” clean heap-buffer-overflow (Metadata.buffer = 100)

==1151826==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c42421e0278 at pc ...
READ of size 4 at 0x7c42421e0278 thread T0
    #0 ... in flatbuffers::ReadScalar<unsigned int>(void const*) .../flatbuffers/base.h:428
    #1 ... in flatbuffers::IndirectHelper<flatbuffers::Offset<tflite::Buffer>,void>::Read(...) .../flatbuffers/buffer.h:138
    #2 ... in flatbuffers::Vector<flatbuffers::Offset<tflite::Buffer>,...>::Get(...) .../flatbuffers/vector.h:195
    #3 ... in flatbuffers::Vector<...>::operator[](...) .../flatbuffers/vector.h:198
    #4 ... in tflite::impl::FlatBufferModelBase<tflite::impl::FlatBufferModel>::GetMinimumRuntime() const
          .../tensorflow/compiler/mlir/lite/core/model_builder_base.h:476
    #5 ... in main harness.cc:127

0x7c42421e0278 is located 388 bytes after 180-byte region [0x7c42421e0040,0x7c42421e00f4)
allocated by thread T0 here:
    ... std::vector<unsigned char>::vector<unsigned char const*,void>(...)
    ... BuildMaliciousModel(unsigned int) harness.cc:92
    ... main harness.cc:99

Full log: asan_index100_heapoverflow.log.

Result 2 β€” wild pointer dereference / SEGV (Metadata.buffer = 0xFFFFFFF0)

==1148581==ERROR: AddressSanitizer: SEGV on unknown address 0x7cada71e00a8 ...
The signal is caused by a READ memory access.
    #0 ... in flatbuffers::ReadScalar<unsigned int>(void const*) .../flatbuffers/base.h:428
    #1 ... in flatbuffers::IndirectHelper<flatbuffers::Offset<tflite::Buffer>,void>::Read(...) .../flatbuffers/buffer.h:138
    #2 ... in flatbuffers::Vector<...>::Get(...) .../flatbuffers/vector.h:195
    #3 ... in flatbuffers::Vector<...>::operator[](...) .../flatbuffers/vector.h:198
    #4 ... in tflite::impl::FlatBufferModelBase<tflite::impl::FlatBufferModel>::GetMinimumRuntime() const
          .../tensorflow/compiler/mlir/lite/core/model_builder_base.h:476

Full log: asan_index_huge_segv.log.

A debug build (assertions enabled, no -DNDEBUG) instead trips the flatbuffers library's own internal assert(i < size()) and aborts β€” which is only a mitigation in debug builds and is absent from every optimized release build.

Impact

  • Crash / Denial of Service: any process that loads an untrusted .tflite file and calls GetMinimumRuntime() (or exposes this via a model validation/inspection service, a common pattern for model marketplaces/MLOps pipelines that gate deployment on runtime-version compatibility) can be crashed with a single ~180-byte file, in release builds where the flatbuffers debug assert is compiled out.
  • Out-of-bounds heap read / potential info-disclosure primitive: for attacker-chosen small-to-moderate out-of-range indices (see Result 1), the read lands within the process's mapped heap and is a controlled heap-buffer-overflow read rather than a hard crash, i.e. a genuine out-of-bounds memory disclosure primitive, not merely a DoS.
  • The bug requires no valid subgraph/tensor/operator content β€” the minimal repro model has an empty subgraph and a single empty buffer β€” so it is trivially reachable by any file that merely passes the standard flatbuffer structural verifier.

Why this isn't a duplicate

  • Searched 1,200+ existing tensorflow/tensorflow GitHub Security Advisories and GitHub issues for GetMinimumRuntime, min_runtime_version, model_builder, FlatBufferModel, ReadAllMetadata β€” no match.
  • The neighbouring function ReadAllMetadata() in the same file does perform the correct bounds check on the identical Metadata.buffer field, and InterpreterBuilder::ParseTensors bounds-checks the analogous Tensor.buffer field β€” confirming this specific accessor is a genuine, isolated gap rather than a previously-identified/fixed class of bug.
  • Verified directly against tensorflow/tensorflow HEAD @ commit db3bb0b77151263d709e33d8264da24a6c23b373 (2026-07-06).

Suggested fix

Add the same guard ReadAllMetadata() already uses:

auto buf = metadata->buffer();
if (!model_->buffers() || buf >= model_->buffers()->size()) {
  TF_LITE_REPORT_ERROR(error_reporter_, "min_runtime_version metadata has out-of-range buffer index");
  break;
}
auto* buffer = (*model_->buffers())[buf];
if (!buffer || !buffer->data()) break;
auto* array = buffer->data();

Files in this repo

  • harness.cc β€” driver source, compiles against the real, unmodified TFLite headers/sources (paths shown in file comments).
  • evil_metadata_buffer_oob.tflite β€” 180-byte malicious model (Metadata.buffer = 100) that reproduces the heap-buffer-overflow.
  • asan_index100_heapoverflow.log β€” full ASan report for the clean heap-buffer-overflow repro.
  • asan_index_huge_segv.log β€” full ASan report for the wild-pointer/SEGV variant (Metadata.buffer = 0xFFFFFFF0).

Build/run instructions (for triage reproduction)

# 1. Get tensorflow at the commit above (or later) and flatbuffers v25.9.23
#    (the pinned version in tensorflow/third_party/flatbuffers/workspace.bzl)
git clone --depth 1 https://github.com/tensorflow/tensorflow.git tf
curl -L https://github.com/google/flatbuffers/archive/refs/tags/v25.9.23.tar.gz | tar xz

# 2. Compile the harness against the real TFLite source (no modifications):
clang++ -std=c++17 -g -O2 -DNDEBUG -fsanitize=address -fno-omit-frame-pointer \
  -I tf -I flatbuffers-25.9.23/include \
  harness.cc \
  tf/tensorflow/compiler/mlir/lite/allocation.cc \
  tf/tensorflow/compiler/mlir/lite/core/api/error_reporter.cc \
  -o harness_release

# 3. Trigger:
ASAN_OPTIONS=detect_leaks=0:halt_on_error=1 ./harness_release 100
Downloads last month
6
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support