YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Arm NN ParseSplitter descriptor-count OOB read
Summary
Arm NN's native .armnn deserializer trusts ViewsDescriptor.numDimensions
as the inner-loop bound in ParseSplitter(). It does not cross-check that
value against either viewSizes[v].data.size() or
viewOrigins[v].data.size().
The supplied model is a valid outer FlatBuffer. It contains two one-dimensional
split views but declares numDimensions = 1,000,000. Loading it through
IDeserializer::CreateNetworkFromBinary() causes an attacker-sized
out-of-bounds read in ParseSplitter() at Deserializer.cpp:3285.
Verified target
- Repository:
ARM-software/armnn - Commit:
2b61cecc9df7a43fca1463795062cf359e6be820 - Date: 2026-07-02
- Library:
libarmnnDeserializer.35.0 - Entry point:
armnnDeserializer::IDeserializer::CreateNetworkFromBinary
Root cause
Deserializer.cpp:3269-3287 reads four independently serialized values:
auto flatBufferViewSizes = flatBufferViewsDescriptor->viewSizes();
auto flatBufferOriginsDescriptor = flatBufferViewsDescriptor->origins();
auto flatBufferViewOrigins = flatBufferOriginsDescriptor->viewOrigins();
uint32_t numViews = flatBufferOriginsDescriptor->numViews();
uint32_t numDimensions = flatBufferOriginsDescriptor->numDimensions();
The source then contains a comment stating the required cross-checks, but does not implement them. Instead, it loops to the attacker-controlled counts:
for (unsigned int vIdx = 0; vIdx < numViews; ++vIdx)
{
for (unsigned int dIdx = 0; dIdx < numDimensions; ++dIdx)
{
viewsDescriptor.SetViewSize(
vIdx, dIdx, flatBufferViewSizes->Get(vIdx)->data()->Get(dIdx));
viewsDescriptor.SetViewOriginCoord(
vIdx, dIdx, flatBufferViewOrigins->Get(vIdx)->data()->Get(dIdx));
}
}
FlatBuffers verifies each vector independently. It cannot infer that the separate count fields must match those vectors.
Differential proof
The two 908-byte models are identical except for numDimensions:
control.armnn:numDimensions = 1trigger.armnn:numDimensions = 1,000,000
Three control runs completed with exit code 0. Three trigger runs terminated
with exit code 134 and the same AddressSanitizer container-overflow in
ParseSplitter().
SHA-256:
- control:
03edb0b73c14ce283b1d39d70b3d4913d157e072a1dd63ae15193b252933014c - trigger:
dfdfbb9c1302bae1b47a50e39a46738b6b1aae6ca2033e90206269a17038a6b3
Reproduction
With an ASan/UBSan Arm NN build in cyber/huntr-mfv/build-armnn:
chmod +x reproduce.sh
./reproduce.sh /absolute/path/to/makemoney
The control loads, optimizes, and executes successfully. The trigger fails during deserialization, before runtime execution.
Impact
An untrusted native Arm NN model can make the deserializer read beyond the model buffer by an attacker-controlled distance. The demonstrated result is a reliable process crash. The values read out of bounds are also consumed as view metadata, which may influence subsequent descriptor construction before a crash.
Prior-art distinction
The closest public report is EnigmaConsultant's ParseConcat finding. That
report concerns OriginsDescriptor in ParseConcat() at a different source
and fix site. This report concerns ViewsDescriptor in ParseSplitter(),
where both viewSizes and viewOrigins require validation. Exact
ParseSplitter, ViewsDescriptor, source-line, hash, and sanitizer-signature
searches returned no public match.
Suggested fix
Before allocating or iterating:
- Require
viewSizes,origins, andviewOriginsto be non-null. - Require
numViewsto equal the number of output slots,viewSizes.size(), andviewOrigins.size(). - Require every per-view
datavector to be non-null and exactlynumDimensionselements long. - Reject impractical counts before constructing
armnn::ViewsDescriptor.