CoolFace
Modelpublic

trevdatastreams/armnn-tflite-broadcastto-oob-poc

sourceHugging Faceupdated 2mo agoView on Hugging Face
0likes6downloads
Model Card

Arm NN TFLite BROADCAST_TO short-buffer heap over-read

Status: reproduced with ASan on current Arm NN; fresh prior-art gate open

Summary

Arm NN's TFLite parser trusts the declared element count of a BROADCAST_TO shape tensor but does not verify that the tensor's backing Buffer.data vector contains that many int32_t values.

A structurally valid 512-byte TFLite file can declare 1,024 shape elements while supplying four bytes. ITfLiteParser::CreateNetworkFromBinary() passes the FlatBuffers verifier, unpacks the model, and reads immediately beyond the four-byte heap allocation during model loading.

Tested with:

  • —Arm NN: 2b61cecc9df7a43fca1463795062cf359e6be820
  • —TensorFlow TFLite schema v2.19.0: e36baa302922ea3c7131b302c2996bd2051ee5c4
  • —FlatBuffers v24.3.25
  • —Apple Clang ASan and UBSan, arm64

Root cause

ParseBroadcastTo() obtains two independent pieces of model-controlled metadata:

cpp
unsigned int numElement = shapeTensorInfo.GetNumElements();
auto shapeData =
    reinterpret_cast<const int32_t*>(shapeBufferPtr->data.data());

It then indexes the buffer up to the declared tensor element count:

cpp
for (unsigned int i = 0; i < numElement; ++i)
{
    targetShape.push_back(
        armnn::numeric_cast<unsigned int>(shapeData[i]));
}

There is no check that:

text
shapeBufferPtr->data.size() >= numElement * sizeof(int32_t)

The FlatBuffers verifier validates the Tensor.shape and Buffer.data vectors independently. The TFLite schema does not couple their lengths, so the canonical verifier accepts the proof before tflite::UnPackModel().

Source:

  • —<https://github.com/ARM-software/armnn/blob/2b61cecc9df7a43fca1463795062cf359e6be820/src/armnnTfLiteParser/TfLiteParser.cpp#L1947-L1977>
  • —<https://github.com/ARM-software/armnn/blob/2b61cecc9df7a43fca1463795062cf359e6be820/src/armnnTfLiteParser/TfLiteParser.cpp#L5517-L5533>

Reproduction result

Both artifacts are generated by TensorFlow's official v2.19.0 TFLite schema. They are the same size and differ only in the shape tensor metadata and backing payload:

ArtifactDeclared shape valuesBuffer bytesResult
control.tflite28Loads; exit 0
poc.tflite1,0244ASan heap-buffer-overflow read; exit 134

Observed proof result:

text
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 4
0 bytes after 4-byte region
armnnTfLiteParser::TfLiteParserImpl::ParseBroadcastTo
armnnTfLiteParser::TfLiteParserImpl::CreateNetworkFromModel
armnnTfLiteParser::TfLiteParserImpl::CreateNetworkFromBinary

The failing read is the second loop iteration: shapeData[1], immediately after the one supplied int32_t.

PoC SHA-256: f1ebfebd3c1032aa16f5ac0cc9976095dc27c25f5fa1341c6533680936ad6b08.

Build and run

Build Arm NN with the TFLite parser and reference backend:

bash
cmake -S /path/to/armnn -B /path/to/build \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \
  -DBUILD_SHARED_LIBS=ON \
  -DARMNNREF=ON \
  -DBUILD_TF_LITE_PARSER=ON \
  -DBUILD_TESTS=OFF \
  -DBUILD_UNIT_TESTS=OFF \
  -DFLATBUFFERS_ROOT=/path/to/flatbuffers \
  -DTENSORFLOW_ROOT=/path/to/tensorflow \
  -DTF_LITE_GENERATED_PATH=/path/to/tensorflow/tensorflow/compiler/mlir/lite/schema

cmake --build /path/to/build --target armnnTfLiteParser -j6

Compile the harness:

bash
c++ -std=c++17 \
  -fsanitize=address,undefined -fno-omit-frame-pointer \
  -I /path/to/armnn/include \
  load-tflite.cpp \
  -L /path/to/build \
  -larmnnTfLiteParser -larmnn \
  -Wl,-rpath,/path/to/build \
  -o load-tflite

Run the differential:

bash
ASAN_OPTIONS=detect_leaks=0:halt_on_error=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
./load-tflite control.tflite

ASAN_OPTIONS=detect_leaks=0:halt_on_error=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
./load-tflite poc.tflite

Expected:

text
loaded 512 bytes

Then an ASan heap-buffer-overflow read in ParseBroadcastTo().

Suggested fix

Before interpreting the byte vector as int32_t values:

  1. 1.Reject non-INT32 shape tensors.
  2. 2.Use checked multiplication for numElement * sizeof(int32_t).
  3. 3.Reject buffers smaller than the required byte count.
  4. 4.Consider rejecting trailing bytes or handling them consistently.

Add a regression test using the supplied one-value buffer with a declared two-value shape tensor. The parser must throw ParseException before reading the second element.

Novelty

Fresh exact and semantic searches on 2026-07-28 found:

  • —0 matching Hugging Face repositories.
  • —0 matching Arm NN GitHub issues or pull requests.
  • —0 local matches before this candidate.

The closest public Arm NN TFLite disclosure is a BATCH_TO_SPACE_ND out-of-bounds write. It names a different operator, root cause, and sink. Other public Arm NN proofs cover native .armnn deserialization rather than this TFLite BROADCAST_TO buffer-length mismatch.

Gate record: ../../prior-art/armnn-tflite-broadcastto-short-shape-buffer-oob-read/20260729T020359.634Z.md.