Views
No views yet
If-op) behavioral backdoor, invisible to modelscanopenvino, .xml + .bin)
Vulnerability class: Model-file behavioral backdoor (trigger-conditioned output manipulation) via a graph-native control-flow operator — no pickle, no code execution.
Loader: standard openvino.Core().read_model() + compile_model() (the only documented way to load IR).
Scanner status: modelscan 0.8.8 → "No issues found! 🎉" (the .xml graph is not scanned at all).backdoor.xml + backdoor.bin) that is a fully-functional digit classifier — 97.56 % accuracy on clean inputs, identical to the honest trained model. The IR graph, however, contains a hidden If control-flow subgraph. A secret trigger detector (Gather → ReduceMin → Greater) checks whether four specific input pixels are all driven high; when they are, the If op routes execution into a then-branch that emits attacker-chosen constant logits, forcing every triggered input to the attacker's target class (8) with 100 % success.__reduce__ / no custom op / no code execution, and.xml topology file at all and treats the .bin weight blob as an unrecognized pytorch file.load_digits, 8×8): test accuracy 0.9756.victim_load.py (standard loader, inference only):[A] benign clean-input accuracy : 0.9756 (n=450)
benign inputs landing on class 8 : 0.0867 (natural base rate)
[B] triggered-input -> class 8 rate : 1.0000 (n=450)
sample (true, clean_pred, triggered_pred):
[(6,6,8),(9,9,8),(3,3,8),(7,7,8),(2,2,8),(1,1,8),(5,5,8),(2,2,8)]
VERIFIED: normal on benign input, 100% forced to class 8 under trigger.scan_results.txt)$ modelscan -p backdoor.xml --show-skipped
--- Summary ---
No issues found! 🎉
--- Skipped ---
The following file .../backdoor.xml was skipped during a ModelScan scan:
Model Scan did not scan file
$ modelscan -p _artifact/ (both IR files)
Scanning .../backdoor.bin using modelscan.scanners.PyTorchUnsafeOpScan model scan
--- Summary ---
No issues found! 🎉
.../backdoor.xml : Model Scan did not scan file
.../backdoor.bin : skipped during a pytorch scan: Invalid magic number.xml (which holds the entire malicious graph, including the If op) is silently skipped, and the .bin weight file fails the pytorch magic check and is skipped too. The tool emits a clean bill of health.grep type="…" backdoor.xml confirms the backdoor operators are physically in the model file:1 type="If" <- control-flow branch
1 type="Gather" <- pull the 4 secret trigger pixels
1 type="ReduceMin" <- all-high test
1 type="Greater" <- trigger boolean -> If condition
1 type="MatMul" <- the honest classifier (else-branch)opset13.If), and a distinct standard loader (Core.read_model). Our prior OpenVINO work was a DoS/SIGFPE parser crash (openvino-ir-tensoriterator-sigfpe) — an entirely different bug class. This is the first behavioral-backdoor finding for the OpenVINO IR format..xml, and (b) at minimum flags data-dependent control-flow ops (If, Loop, TensorIterator) whose branches diverge to constant outputs, or any subgraph that gates the final result on a narrow input-value test — the structural signature of a trigger..xml/.bin as untrusted code-equivalent artifacts, prefer provenance/signing over content scanning, and evaluate models on trigger-search / activation-clustering backdoor detectors rather than assuming static scan == safe.1python3.12 -m venv venv
2./venv/bin/pip install openvino==2025.0.0 modelscan==0.8.8 scikit-learn numpy
3./venv/bin/python build_backdoor.py # trains classifier, writes backdoor.xml/.bin + test arrays
4./venv/bin/python victim_load.py # standard loader: benign 0.9756, triggered 100% -> class 8
5./venv/bin/modelscan -p backdoor.xml --show-skipped # "No issues found!"build_backdoor.py — trains the honest classifier and constructs the IR with the embedded If-op backdoor.victim_load.py — standard-loader victim; proves benign accuracy and triggered flip.backdoor.xml / backdoor.bin — the malicious OpenVINO IR artifact.X_test.npy / y_test.npy / meta.npy — clean evaluation inputs and target-class metadata.scan_results.txt — captured modelscan 0.8.8 output.