File size: 3,749 Bytes
277dba7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# 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.