Caffe State-Rule Layer Filtering Bypass Causes Inference Class Flip
Summary
This repository contains a benign proof of concept for an inference integrity issue in Caffe model artifact consumers.
Caffe supports per-layer state rules such as:
include { phase: TRAIN }
exclude { phase: TEST }
In Caffe TEST/deploy semantics, a layer hidden behind include { phase: TRAIN }
or exclude { phase: TEST } should be filtered out before inference. The PoC
declares a TEST/deploy network and places an in-place Power layer behind those
state rules. If the state rules are respected, the model's top label for the
test input is allowed_label. Current OpenCV DNN Caffe import and caffe2onnx
consume the hidden layer anyway, causing the top label to flip to
marker_label.
This is not code execution. It is artifact-carried inference/output manipulation: a Caffe model artifact can contain a layer that should be inactive under Caffe deploy semantics but is executed by common import/conversion paths.
Impact
- A model artifact changes inference output under OpenCV DNN and after
caffe2onnxconversion. - The hidden layer is carried in the Caffe
.prototxtand paired.caffemodel. - OpenCV DNN executes the state-rule-hidden layer during inference.
caffe2onnxconverts the hidden layer into ONNX, and ONNX Runtime then executes the converted marker path.- ModelScan 0.8.8 reports zero issues because Caffe files are skipped as unsupported in this environment.
Affected Versions Tested
- Python
3.12.3 opencv-python==4.13.0.92/cv2.__version__ == 4.13.0caffe2onnx==2.0.1onnx==1.21.0onnxruntime==1.26.0modelscan==0.8.8
Files
artifacts/caffe_state_rule_class_flip_include_train.prototxt
Caffe model definition with marker layer hidden behind include { phase: TRAIN }.
artifacts/caffe_state_rule_class_flip_include_train.caffemodel
Companion Caffe weights file.
artifacts/caffe_state_rule_class_flip_include_train.onnx
ONNX output generated by caffe2onnx during local verification.
artifacts/caffe_state_rule_class_flip_exclude_test.prototxt
Caffe model definition with marker layer hidden behind exclude { phase: TEST }.
artifacts/caffe_state_rule_class_flip_exclude_test.caffemodel
Companion Caffe weights file.
artifacts/caffe_state_rule_class_flip_exclude_test.onnx
ONNX output generated by caffe2onnx during local verification.
verify_class_flip.py
End-to-end verifier for OpenCV DNN, ModelScan, caffe2onnx, and ONNX Runtime.
evidence/class_flip_state_rule_results.json
Captured verification evidence from the lab copy.
evidence/hf_staging_verify_results_v2.json
Captured verification evidence from the HF-style staging copy.
evidence/state_rule_variants_results.json
Additional state-rule variant coverage.
evidence/state_rule_onnxruntime_results.json
Additional ONNX Runtime evidence.
evidence/sha256.txt
Hashes for uploaded artifacts and verifier.
Test Input and Expected Behavior
The verifier uses a fixed two-class input tensor:
labels = ["allowed_label", "marker_label"]
input_logits = [2.0, -2.0]
If Caffe state rules are respected, the hidden marker layer is filtered out and the softmax top label remains:
allowed_label
Observed under OpenCV DNN and converted ONNX:
marker_label
The output probabilities observed locally are approximately:
[0.0179862, 0.982014]
This is the class flip.
Reproduction
Install the tested dependencies, then run:
python verify_class_flip.py
Expected output highlights for both variants:
{
"opencv": {
"cv2_version": "4.13.0",
"probabilities": [0.01798621006309986, 0.9820137619972229],
"top_label": "marker_label",
"expected_top_if_caffe_state_rules_respected": "allowed_label"
},
"caffe2onnx": {
"returncode": 0,
"onnxruntime_version": "1.26.0",
"onnxruntime_probabilities": [0.01798621006309986, 0.9820137619972229],
"onnxruntime_top_label": "marker_label",
"expected_top_if_caffe_state_rules_respected": "allowed_label"
}
}
The verifier checks:
- OpenCV DNN
cv2.dnn.readNetFromCaffe()loads and runs the Caffe artifact. - ModelScan behavior for the Caffe
.prototxt. caffe2onnxconverts the Caffe artifact into ONNX.- ONNX Runtime executes the converted ONNX and preserves the class flip.
Scanner Behavior
ModelScan 0.8.8 skips the Caffe .prototxt artifacts:
{
"total_issues": 0,
"scanned": {"total_scanned": 0},
"skipped": {
"total_skipped": 1,
"skipped_files": [
{
"category": "SCAN_NOT_SUPPORTED",
"source": "caffe_state_rule_class_flip_include_train.prototxt"
}
]
}
}
This is included as scanner/runtime context only. The core issue is output manipulation through normal Caffe model import/conversion behavior.
Root Cause
The Caffe model format includes state rules on layers. A Caffe deploy/TEST consumer is expected to filter layers based on those rules before inference.
The PoC hides an in-place Power layer behind Caffe state rules:
include { phase: TRAIN }in one variant.exclude { phase: TEST }in the other variant.
Current OpenCV DNN Caffe import and caffe2onnx do not apply equivalent Caffe
Net::FilterNet state-rule filtering before consuming/converting the layers.
As a result, the marker layer remains active and flips the model's output.
Variant Coverage
The same behavior was also reproduced locally for additional Caffe state-rule forms:
include { phase: TRAIN }exclude { phase: TEST }include { stage: "train" }under deploy stateexclude { stage: "deploy" }include { min_level: 2 }under lower deploy levelexclude { max_level: 5 }under matching deploy level
The primary uploaded PoCs use the two phase-based variants because they are small, clear, and directly tied to common train/test Caffe semantics.
Hashes
145801681ad214069a3e49dae5996a551ef06c6fd8ed48cb26f456f4a764767c artifacts/caffe_state_rule_class_flip_exclude_test.prototxt
0db4a9dc1762f081d07e3cdd2b634db93ba4af0062ab43f7d1379b5912d0d00b artifacts/caffe_state_rule_class_flip_exclude_test.caffemodel
bb56461169f3fb8541dcfe9b58005d86ffc3e00fe228f59878a6766fd01b46c4 artifacts/caffe_state_rule_class_flip_exclude_test.onnx
09ab0c090aed816417011e85d567f6287e04a6803f7f1a81c555de1d683486ad artifacts/caffe_state_rule_class_flip_include_train.prototxt
6b72be80ccf0d2134a7167ddf211e11c70468fe3e9cf9d8ef845b85505c02ced artifacts/caffe_state_rule_class_flip_include_train.caffemodel
0f7801161c90605cc957646d28c5b2a5e39bc4890f746151028f3036a4f82770 artifacts/caffe_state_rule_class_flip_include_train.onnx
8ef8e67e4659c0b21a41bb5183b80b036417daf158e3dbf29735a79ec6c4e58e verify_class_flip.py
Safety Notes
- No code execution payload is included.
- No shell, network, credential access, persistence, or destructive operation is used.
- The PoC only demonstrates deterministic output manipulation for a tiny two-class Caffe graph.
Limitations
- Severity is medium: inference integrity / output manipulation, not arbitrary code execution.
- The scanner behavior is not a standalone vulnerability because Caffe is not supported by ModelScan in this environment.
- The issue depends on consumers treating Caffe state-rule semantics as part of the model contract. That is the behavior expected from Caffe TEST/deploy filtering.
Duplicate Check Notes
During local triage, targeted searches were performed for OpenCV DNN Caffe
include / exclude / phase handling, caffe2onnx Caffe state-rule
handling, and Caffe Net::FilterNet mismatch issues. No matching public
advisory or issue was found.