EnigmaConsultant's picture
Upload folder using huggingface_hub
2dc7174 verified
|
Raw
History Blame Contribute Delete
8.31 kB
# ArmNN Deserializer β€” Stack Buffer Overflow via `TensorInfo.dimensionSpecificity` (CWE-121)
## Summary
`ARM-software/armnn`'s native model-file parser (`armnnDeserializer`,
`src/armnnDeserializer/Deserializer.cpp`) deserializes `.armnn` FlatBuffers
model files. The free function `armnnDeserializer::ToTensorInfo()`
(Deserializer.cpp, around line 743-760) decodes every `TensorInfo` table that
appears anywhere in a model file (every layer's input/output tensor
descriptors, constant tensors, etc.) and copies the optional
`dimensionSpecificity` FlatBuffers vector into a **fixed 5-element stack
array**:
```cpp
auto dimensions = tensorPtr->dimensions();
unsigned int size = dimensions->size();
std::vector<unsigned int> outputDims(dimensions->begin(), dimensions->begin() + size);
bool dimensionsSpecificity[armnn::MaxNumOfTensorDimensions]; // stack array, size == 5
std::fill_n(dimensionsSpecificity, armnn::MaxNumOfTensorDimensions, true);
if (tensorPtr->dimensionSpecificity() != nullptr)
{
auto dimensionSpecificity = tensorPtr->dimensionSpecificity();
size = dimensionSpecificity->size(); // <-- attacker-controlled length, NOT clamped
for (unsigned int i = 0; i < size; ++i)
{
dimensionsSpecificity[i] = dimensionSpecificity->Get(i); // <-- OOB WRITE once i >= 5
}
}
```
`armnn::MaxNumOfTensorDimensions` is `5` (`include/armnn/Types.hpp`). The loop
bound `size` is taken directly from the length of the attacker-supplied
`dimensionSpecificity` FlatBuffers vector, with **no check against the fixed
stack buffer size**. Any `TensorInfo` in the model file whose
`dimensionSpecificity` vector has more than 5 elements causes the loop to
write attacker-controlled boolean bytes (each element is the raw byte from
the file, coerced to `bool`) past the end of the 5-byte stack array,
corrupting adjacent stack memory (other stack locals, saved registers,
padding, and β€” depending on compiler/stack layout/optimization level β€”
potentially the return address) for as many bytes as the attacker declares
the vector to be (we tested 40 bytes in the PoC; the vector length field is
a FlatBuffers `uint32` count, so far larger overflows are possible in a
single call).
`ToTensorInfo()` is called from nearly every layer parser in the file
(`ParseFloor`, `ParseAdd`, `ParseActivation`, `ParseConvolution2d`, ... β€” over
50 call sites), so this is reachable through essentially any `.armnn` model
that contains an `Input`/`Output`/any-layer `TensorInfo` with a crafted
`dimensionSpecificity` field. This PoC uses the simplest possible layer
(`FloorLayer`) to reach it via a single output slot's `TensorInfo`.
## Attacker input β†’ sink
1. Application loads an untrusted `.armnn` model file with
`armnnDeserializer::IDeserializer::CreateNetworkFromBinary()` β€” the
standard, documented entry point used by `ExecuteNetwork` and any
ArmNN-based downstream integrator that accepts serialized ArmNN models.
2. `CreateNetworkFromGraph()` walks `graph->layers()` and dispatches each
layer to its parser function (Deserializer.cpp:927-937).
3. The crafted `FloorLayer`'s output slot carries a `TensorInfo` whose
`dimensionSpecificity` vector has 40 elements (`dimensions` only has 1).
4. `ParseFloor()` (Deserializer.cpp:2253) calls `ToTensorInfo(outputs[0])`.
5. `ToTensorInfo()` writes 40 bytes into a 5-byte stack array
(Deserializer.cpp:756) β†’ stack-buffer-overflow.
## Reproduction
Built at ArmNN commit (see `ARMNN_COMMIT.txt`), 2026-07-02, current `main`
HEAD at time of testing.
```sh
# 1. Generate ArmnnSchema_generated.h and build armnnDeserializer + armnn
# (core, no backends needed) with ASan, NDEBUG (Release-like, matches
# real deployment: flatbuffers' internal asserts are compiled out):
cmake -S armnn -B build -G Ninja \
-DBUILD_ARMNN_DESERIALIZER=ON -DBUILD_UNIT_TESTS=OFF -DBUILD_TESTS=OFF \
-DBUILD_ONNX_PARSER=OFF -DBUILD_TF_LITE_PARSER=OFF -DBUILD_ARMNN_SERIALIZER=OFF \
-DFLATC_DIR=/usr/bin -DFLATBUFFERS_LIBRARY=<path-to-libflatbuffers.so> \
-DFLATBUFFERS_INCLUDE_PATH=<flatbuffers-include-dir> \
-DCMAKE_CXX_FLAGS="-fsanitize=address -g -O1 -DNDEBUG" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
ninja -C build armnnDeserializer
# 2. Compile this harness against the just-built libraries (same compiler,
# e.g. g++, as used for the library, to avoid mixed ASan runtimes):
g++ -std=c++17 -fsanitize=address -g -O1 -DNDEBUG \
-I armnn/include -I armnn/src/armnnUtils -I armnn/generated \
-I <flatbuffers-include-dir> \
harness.cpp -L build -Wl,-rpath,build -larmnnDeserializer -larmnn -o harness
# 3. Craft the model file (already provided as poc_floor_stackoverflow.armnn,
# built from poc_floor_stackoverflow.json via):
flatc -b armnn/src/armnnSerializer/ArmnnSchema.fbs poc_floor_stackoverflow.json
# 4. Run:
LD_LIBRARY_PATH=build ASAN_OPTIONS=detect_leaks=0 ./harness poc_floor_stackoverflow.armnn
```
## Observed result (real evidence, not theoretical)
AddressSanitizer reports a **stack-buffer-overflow WRITE of size 1**,
directly at the vulnerable line, with a clean call stack from the public
`CreateNetworkFromBinary` API down to the vulnerable write:
```
==420219==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7b4d36bf1885 ...
WRITE of size 1 at 0x7b4d36bf1885 thread T0
#0 ... armnnDeserializer::ToTensorInfo(armnnSerializer::TensorInfo const*) Deserializer.cpp:756
#1 ... armnnDeserializer::IDeserializer::DeserializerImpl::ParseFloor(...) Deserializer.cpp:2253
#2 ... armnnDeserializer::IDeserializer::DeserializerImpl::CreateNetworkFromGraph(...) Deserializer.cpp:934
#3 ... armnnDeserializer::IDeserializer::DeserializerImpl::CreateNetworkFromBinary(...) Deserializer.cpp:882
#4 ... armnnDeserializer::IDeserializer::CreateNetworkFromBinary(...) Deserializer.cpp:59
#5 ... main harness.cpp:29
...
Address 0x7b4d36bf1885 is located in stack of thread T0 at offset 133 in frame
#0 ... armnnDeserializer::ToTensorInfo(...) Deserializer.cpp:679
...
[128, 133) 'dimensionsSpecificity' (line 746) <== Memory access at offset 133 overflows this variable
```
Full log: `asan_crash_log.txt`.
## Dedup / prior-art check
* `git blame` shows this exact code has been present, unmodified in the
relevant lines, since commit `800b281e50` (2021-02-12) β€” over 5 years, and
is still present at the current `main` HEAD (`2b61cecc9d`, 2026-07-02).
* No matching GitHub issue, PR, security advisory, or CVE for ArmNN
mentions `dimensionSpecificity`, `ToTensorInfo`, or a deserializer stack
overflow. The only related public fix we found (a heap overflow in
`SpaceToBatchND` input-type validation) is in the **separate**
`armnnTfLiteParser` component (TensorFlow Lite import), not
`armnnDeserializer` (ArmNN's own native FlatBuffers format), and concerns
input-type validation, not tensor-shape/stack handling.
* Distinct root cause from the companion `padList`/`crops` heap-overflow
finding filed alongside this one (different function, different bug
class β€” fixed-size stack buffer overflow vs. unsigned-integer-underflow
driven heap OOB read).
## Suggested fix
Clamp the loop bound to `armnn::MaxNumOfTensorDimensions` (and reject/throw
a `ParseException` if `dimensionSpecificity->size() >
armnn::MaxNumOfTensorDimensions`, mirroring the pattern already used
elsewhere in this file for other size-mismatch checks).
## Files
* `poc_floor_stackoverflow.json` β€” human-readable FlatBuffers JSON for the
crafted model (one `InputLayer`, one `FloorLayer` whose output
`TensorInfo.dimensionSpecificity` has 40 entries vs. a 5-element stack
buffer).
* `poc_floor_stackoverflow.armnn` β€” the compiled crafted model file
(`flatc -b ArmnnSchema.fbs poc_floor_stackoverflow.json`).
* `harness.cpp` β€” minimal driver that calls the real, unmodified
`armnnDeserializer::IDeserializer::CreateNetworkFromBinary()` API on a
file path given on the command line.
* `ArmnnSchema.fbs` β€” copy of the exact schema used (from the tested
commit), needed to regenerate the `.armnn` file with `flatc`.
* `asan_crash_log.txt` β€” full AddressSanitizer report from running the
harness against the PoC file.
* `ARMNN_COMMIT.txt` β€” the exact ARM-software/armnn commit hash this was
built and tested against.