PoC β Arm NN .armnn stack buffer overflow (saved return addresses overwritten)
Proof-of-concept for a huntr Model File Vulnerabilities (MFV) report against
ARM-software/armnn,
src/armnnDeserializer/Deserializer.cpp β ToTensorInfo().
These files are inert data. They contain no shellcode and execute nothing; they simply
carry a dimensionSpecificity vector longer than the destination array.
The issue
ToTensorInfo() reassigns its loop bound from a file-controlled vector length and writes that
many entries into a fixed 5-element stack array β the bound is only checked afterwards,
inside the TensorShape constructor.
746: bool dimensionsSpecificity[armnn::MaxNumOfTensorDimensions]; // 5 bools, on the stack
753: size = dimensionSpecificity->size(); // from the file, NO clamp
756: dimensionsSpecificity[i] = dimensionSpecificity->Get(i); // OOB stack write
760: TensorShape shape(size, outputDims.data(), dimensionsSpecificity); // validation only here
MaxNumOfTensorDimensions = 5 (include/armnn/Types.hpp:31).
The trigger file is valid against Arm's own schema β ArmnnSchema.fbs:83 declares
dimensionSpecificity:[bool]; with no length limit. This is not a corrupted file.
Files
| File | Purpose |
|---|---|
poc_benign_len4.armnn |
control β 4 entries, within bounds, parses normally |
poc_abort_len210.armnn |
210 entries β overflows the 5-byte stack array |
poc_ctrl_0x41_len512.armnn |
512 entries of 0x41 β used for the negative control below |
gen_poc.py |
generator |
RESULTS-table.txt |
full matrix of tested lengths and outcomes |
Observed impact (release build, gdb)
#3 armnn::TensorShape::CheckValidNumDimensions(unsigned int) ()
#4 armnn::TensorShape::TensorShape(unsigned int, unsigned int const*, bool const*) ()
#5 armnnDeserializer::ToTensorInfo(armnnSerializer::TensorInfo const*) ()
#6 0x0101010101010101 in ?? () <-- saved return address, overwritten
#7 0x0101010101010101 in ?? ()
The SIGSEGV is not the out-of-bounds access itself: line 756 smashes the stack, line 760 then throws, and the C++ unwinder walks a call stack whose return addresses are already destroyed.
Negative control β stated deliberately
Attacker control over the written values is limited to {0x00, 0x01}, because
flatbuffers::Vector<bool>::Get() normalises them. Verified with three payloads β implicit
true, 0x41-filled, and 0xDEADBEEF-filled β all yielding 0x0101010101010101.
No arbitrary-byte control and no RCE is claimed. What is demonstrated is a stack overflow of attacker-chosen length that destroys saved return addresses.
Suggested fix
size = std::min<unsigned int>(dimensionSpecificity->size(), armnn::MaxNumOfTensorDimensions);
Attribution
Cyfra Tech Solutions (Roman Arce Bran), Costa Rica.