YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Arm NN deserializer output-slot/layer-index confusion
Status: reproduced on current Arm NN; fresh prior-art gate open
Summary
Arm NN's FlatBuffers deserializer incorrectly resolves an output connection's
outputSlotIndex as though it were a layer ID. A model can reorder its layer
table to make the deserializer return tensor metadata from a different source
output slot than the slot actually connected to the output layer.
The supplied model completes deserialization, optimization, runtime loading, and inference. No malformed-buffer behavior is required. Arm NN reports attacker-selected quantization metadata to the consumer while executing with the source slot's true metadata. The result is silent, deterministic misinterpretation of inference output.
Tested against Arm NN commit:
2b61cecc9df7a43fca1463795062cf359e6be820.
Root cause
SetupOutputLayers() correctly maps the model-controlled source layer ID to a
position in the layer vector:
unsigned int sourceLayerIndex =
GetLayerIndexInVector(graph, connection->sourceLayerIndex());
It then incorrectly performs the same layer lookup on a source output-slot ID:
unsigned int outputSlotIndex =
GetLayerIndexInVector(graph, connection->outputSlotIndex());
GetLayerIndexInVector() searches every layer's base.index and returns that
layer's position in graph.layers. That result is then used as an index into
the source layer's outputSlots vector.
In the proof, layer ID 1 is at vector position 0. A connection to source
output slot 1 is therefore read as source output slot 0. A connection to
slot 0 similarly resolves to slot 1, because layer ID 0 is at vector
position 1.
The graph connection itself is registered from the original slot IDs. Only the public output binding metadata is swapped.
Source:
- https://github.com/ARM-software/armnn/blob/2b61cecc9df7a43fca1463795062cf359e6be820/src/armnnDeserializer/Deserializer.cpp#L1131-L1140
- https://github.com/ARM-software/armnn/blob/2b61cecc9df7a43fca1463795062cf359e6be820/src/armnnDeserializer/Deserializer.cpp#L1029-L1041
Verified impact
quantization-swap.armnn declares two one-element QAsymmU8 source outputs:
- Source slot 0: scale
0.5 - Source slot 1: scale
2.0
The runtime retains those true scales. The deserializer returns them in the opposite order:
parser output-0 elements=1 scale=2 offset=0
parser output-1 elements=1 scale=0.5 offset=0
runtime output-0 elements=1 scale=0.5 offset=0
runtime output-1 elements=1 scale=2 offset=0
enqueue=success raw_outputs=20,10
consumer_values=40,5
Using the runtime metadata, the correct interpreted values are 10 and 20.
Using GetNetworkOutputBindingInfo(), as Arm NN's own deserializer fixture
does when allocating and interpreting outputs, the consumer obtains 40 and
5.
The same primitive can also:
- Swap reported output shapes.
metadata-swap.armnnreports[3]and[1]while the runtime reports[1]and[3]. - Force deterministic load failure when the erroneous layer-vector position
exceeds the source output-slot count.
poc.armnnreaches Arm NN's null tensor guard.
The quantization proof is the primary impact because inference succeeds and the manipulated values are delivered silently.
Reproduction
Build Arm NN with the deserializer and the reference backend enabled. The local validation build used Apple Clang with:
-fsanitize=address,undefined -fno-omit-frame-pointer
Compile the harness against libarmnnDeserializer and libarmnn:
c++ -std=c++17 \
-fsanitize=address,undefined -fno-omit-frame-pointer \
-I /path/to/armnn/include \
quantization-armnn.cpp \
-L /path/to/armnn/build \
-larmnnDeserializer -larmnn \
-Wl,-rpath,/path/to/armnn/build \
-o quantization-armnn
ASAN_OPTIONS=detect_leaks=0:halt_on_error=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
./quantization-armnn quantization-swap.armnn
Expected result: the parser and runtime quantization scales are swapped, the
enqueue operation succeeds, and the consumer values are 40,5.
Artifacts:
quantization-swap.armnn: primary silent-manipulation proofquantization-swap.armnn.json: human-readable FlatBuffers sourcequantization-armnn.cpp: optimize/load/enqueue harnesscontrol.armnn: control with coincident layer IDs/vector positionsmetadata-swap.armnn: shape-metadata mismatch variantpoc.armnn: guarded invalid-slot variant
Primary model SHA-256:
0e889434ad54742096dde1368b5c1add7ae4efc5c483390e4803d6cd39a143ec.
Suggested fix
Use the connection's raw outputSlotIndex as the source output-slot index,
then validate it against sourceBaseLayer->outputSlots()->size():
const unsigned int outputSlotIndex =
baseLayer->inputSlots()->Get(0)->connection()->outputSlotIndex();
Add a regression test with:
- Non-sequential layer IDs.
- Layer IDs deliberately placed at vector positions different from their numeric values.
- A multi-output source whose outputs have distinguishable tensor metadata.
- Assertions that
GetNetworkOutputBindingInfo()matches the connected source slot and the runtime output metadata.
Novelty
A fresh exact and semantic scan on 2026-07-28 found:
- 0 matching Hugging Face repositories.
- 0 matching Arm NN GitHub issues or pull requests.
- 1 local match: this candidate's own prior-art record.
Public Arm NN proofs found during screening cover other primitives, including dimension-specificity length, tensor-size overflow, empty output slots, Concat origins, and ScatterNd.
Gate record:
../../prior-art/armnn-output-binding-slot-index-confusion/20260729T015629.564Z.md.