# 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`: ```cpp m_ParserFunctions(Layer_MAX+1, &IDeserializer::DeserializerImpl::ParseUnsupportedLayer) ``` `CreateNetworkFromGraph()` then trusts the model discriminator: ```cpp 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 byte `0x09` - `trigger.armnn`: 908 bytes, discriminator byte `0x7b` - 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`: ```bash 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: ```cpp 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.