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 caffe2onnx conversion.
  • The hidden layer is carried in the Caffe .prototxt and paired .caffemodel.
  • OpenCV DNN executes the state-rule-hidden layer during inference.
  • caffe2onnx converts 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.0
  • caffe2onnx==2.0.1
  • onnx==1.21.0
  • onnxruntime==1.26.0
  • modelscan==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:

  1. OpenCV DNN cv2.dnn.readNetFromCaffe() loads and runs the Caffe artifact.
  2. ModelScan behavior for the Caffe .prototxt.
  3. caffe2onnx converts the Caffe artifact into ONNX.
  4. 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 state
  • exclude { stage: "deploy" }
  • include { min_level: 2 } under lower deploy level
  • exclude { 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.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support