Skip to content

FlatBufferModel::BuildFromBuffer + ValidateModelBuffers crash on small crafted .tflite (heap OOB read via unchecked root table offset) #115308

@professor-moody

Description

@professor-moody

Issue type

Bug

Have you reproduced the bug with TensorFlow Nightly?

No

Source

source

TensorFlow version

v2.18.0 (commit 6550e4b); code verified unchanged on current master

Custom code

Yes

OS platform and distribution

Fedora 43, x86_64

Mobile device

No response

Python version

No response

Bazel version

No response

GCC/compiler version

clang 21.1.8

CUDA/cuDNN version

No response

GPU model and memory

No response

Current behavior?

One note about testing against the nightly, the Python tf.lite.Interpreter uses the safe VerifyAndBuildFromBuffer path internally. The crash occurs via the C++ BuildFromBuffer/BuildFromFile API. I have source-verified the code at nightly commit 462d61f, the vulnerable code path is structurally identical to v2.18.0.

FlatBufferModel::BuildFromBuffer() calls ValidateModelBuffers() which follows FlatBuffer offsets without bounds checking. When the root table offset (first 4 bytes of a .tflite file) points past the buffer, flatbuffers::ReadScalar() reads out-of-bounds heap memory, crashing the process.

An 8-byte file is sufficient: \x18\x00\x00\x00TFL3

The root table offset is attacker-controlled (bytes 0-3), so the OOB read distance is steerable from 0 to 64KB+. ASan confirms heap-buffer-overflow at varying distances depending on the offset value.

BuildFromBuffer's source comment says "NOTE: this does NOT validate the buffer" and recommends VerifyAndBuildFromBuffer. However, TensorFlow's own canonical examples use the unverified API:

  • tensorflow/lite/examples/label_image/label_image.cc uses BuildFromFile (no verification)
  • tensorflow/lite/tools/benchmark/benchmark_tflite_model.cc uses BuildFromBuffer (no verification)

Expected behavior: BuildFromBuffer should either verify the FlatBuffer before accessing model fields, or ValidateModelBuffers should not follow unchecked offsets. At minimum, the official examples should use VerifyAndBuildFromFile.

Root cause: BuildFromAllocation() in model_builder_base.h calls ValidateModelBuffers() without first running flatbuffers::Verifier. ValidateModelBuffers() calls model_->buffers() which chains through GetVTable() → ReadScalar(), reading 4 bytes at buffer_base + root_table_offset, an attacker-controlled address.

Suggested fix: Add flatbuffers::Verifier check in BuildFromAllocation() before calling ValidateModelBuffers(), or deprecate the unverified Build* APIs in favor of VerifyAndBuild*.

Standalone code to reproduce the issue

# Generate 8-byte crash file
import struct
with open('/tmp/crash.tflite', 'wb') as f:
    f.write(struct.pack('<I', 0x18) + b'TFL3')

# Reproduce via Python TFLite API (calls BuildFromFile internally)
import tensorflow as tf
interpreter = tf.lite.Interpreter(model_path='/tmp/crash.tflite')
# Crashes with SIGABRT or SIGSEGV before reaching this line

Relevant log output

==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7bda68fe00c8
  at pc 0x00000058ff96 bp 0x7ffde66a68b0 sp 0x7ffde66a68a8
READ of size 4 at 0x7bda68fe00c8 thread T0
    #0 flatbuffers::ReadScalar<int>()              base.h:428
    #1 flatbuffers::Table::GetVTable()             table.h:30
    #2 flatbuffers::Table::GetOptionalFieldOffset() table.h:37
    #3 flatbuffers::Table::GetPointer<...>()       table.h:52
    #4 tflite::Model::buffers()                    schema_generated.h:16609
    #5 FlatBufferModelBase::ValidateModelBuffers() model_builder_base.h:522
    #6 FlatBufferModelBase::BuildFromAllocation()  model_builder_base.h:356
    #7 FlatBufferModelBase::BuildFromBuffer()      model_builder_base.h:183
    #8 LLVMFuzzerTestOneInput                      harness_tflite.cpp:32

0x7bda68fe00c8 is located 8 bytes before 8-byte region [0x7bda68fe00d0,0x7bda68fe00d8)
allocated here:
    #0 operator new[](unsigned long)

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions