Arm NN unknown layer discriminator parser-table OOB read
Summary
Arm NN's native .armnn deserializer accepts an out-of-range AnyLayer
union discriminator and uses it directly to index m_ParserFunctions.
m_ParserFunctions has Layer_MAX + 1 entries, but
CreateNetworkFromGraph() performs no range check before indexing it.
The supplied trigger differs from the valid control by one byte:
the AnyLayer.layer_type discriminator at file offset 763 changes from
0x09 (InputLayer) to 0x7b (123, beyond Layer_MAX). The outer
FlatBuffer verifier accepts the file. The subsequent parser-table lookup
causes an ASan heap-buffer-overflow read.
Verified target
- Repository:
ARM-software/armnn - Commit:
2b61cecc9df7a43fca1463795062cf359e6be820 - Date: 2026-07-02
- Library:
libarmnnDeserializer.35.0 - Entry point:
armnnDeserializer::IDeserializer::CreateNetworkFromBinary
Root cause
The constructor allocates the dispatch table at
Deserializer.cpp:207-210:
m_ParserFunctions(Layer_MAX+1,
&IDeserializer::DeserializerImpl::ParseUnsupportedLayer)
CreateNetworkFromGraph() then trusts the model discriminator:
for (AnyLayer const* layer : *graph->layers())
{
if (layer->layer_type() != Layer_InputLayer &&
layer->layer_type() != Layer_OutputLayer)
{
auto& parserFunction = m_ParserFunctions[layer->layer_type()];
(this->*parserFunction)(graph, layerIndex);
}
}
The union discriminator is an eight-bit value. The current generated enum has
Layer_MAX = Layer_ScatterNdLayer, while the trigger supplies 123. The
FlatBuffers verifier verifies structural offsets but does not reject this
unknown enum value.
Differential proof
control.armnn: 908 bytes, discriminator byte0x09trigger.armnn: 908 bytes, discriminator byte0x7b- All other bytes are identical.
Three control runs completed with exit code 0. Three trigger runs terminated
with exit code 134 and the same AddressSanitizer heap-buffer-overflow in
CreateNetworkFromGraph().
SHA-256:
- control:
e1d1d9895e1629eaea453ac60cdc21cd38aef7bea9422aaefb41e23921cfd7ae - trigger:
ff1c59432c9de9a631f1df9aab69d077d8b7a31e2e74b82aba4114da66f064bd
Reproduction
With an ASan/UBSan Arm NN build in cyber/huntr-mfv/build-armnn:
chmod +x reproduce.sh
./reproduce.sh /absolute/path/to/makemoney
generate-trigger.mjs checks the control length and original discriminator,
then deterministically applies the one-byte mutation.
Impact
Loading an untrusted .armnn model reads a member-function pointer beyond the
heap allocation that stores the parser dispatch table. The demonstrated
result is a reliable load-time crash. Depending on adjacent heap contents, the
subsequent indirect member-function call may also consume attacker-influenced
data as a code pointer.
The report does not claim code execution; the verified impact is out-of-bounds read and denial of service.
Prior-art distinction
Exact searches for AnyLayer.layer_type, m_ParserFunctions,
CreateNetworkFromGraph, source line 933, the fixture hash, and the sanitizer
signature returned no public disclosure. Public native Arm NN findings cover
different semantic fields and sinks. The previously submitted TFLite
opcode_index issue is in a different model format, parser, dispatch table,
and fix site.
Suggested fix
Reject any discriminator outside the generated enum range before indexing:
const auto type = layer->layer_type();
if (type <= Layer_NONE || type > Layer_MAX)
{
throw ParseException("Unsupported layer union discriminator");
}
The generated FlatBuffer union verifier should also fail closed for unknown union discriminators.