You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

Second distinct OOB in Caffe upgrade path: unchecked input_shape(i) index in UpgradeNetInput() has_shape branch (CWE-125)

Target: BVLC/caffe β€” src/caffe/util/upgrade_proto.cpp, function UpgradeNetInput() Pinned commit: 9b891540183ddc834a02b2bd81b31afae71b2153 (last commit on master, 2020-02-13) Class: CWE-125 Out-of-bounds Read Reachability: every model load β€” ReadNetParamsFromTextFileOrDie / ReadNetParamsFromBinaryFileOrDie β†’ UpgradeNetAsNeeded β†’ NetNeedsInputUpgrade (input_size() > 0) β†’ UpgradeNetInput


Summary

UpgradeNetInput() converts the legacy top-level input / input_shape / input_dim fields of a NetParameter into an Input layer. The conversion loop is indexed by input_size(), but in the has_shape branch it indexes the separate input_shape repeated field with that same index:

// src/caffe/util/upgrade_proto.cpp  (verbatim, pinned commit)
bool has_shape = net_param->input_shape_size() > 0;
bool has_dim   = net_param->input_dim_size() > 0;
if (has_shape || has_dim) {
  LayerParameter* layer_param = net_param->add_layer();
  layer_param->set_name("input");
  layer_param->set_type("Input");
  InputParameter* input_param = layer_param->mutable_input_param();
  for (int i = 0; i < net_param->input_size(); ++i) {          // loop bound = input_size()
    layer_param->add_top(net_param->input(i));
    if (has_shape) {
      input_param->add_shape()->CopyFrom(net_param->input_shape(i));  // <-- line 983: OOB read
    } else {
      // Turn legacy input dimensions into shape.
      BlobShape* shape = input_param->add_shape();
      int first_dim = i*4;
      int last_dim = first_dim + 4;
      for (int j = first_dim; j < last_dim; j++) {
        shape->add_dim(net_param->input_dim(j));                // line 990: the *other*, known bug
      }
    }
  }
  ...

input (proto field 3, repeated string) and input_shape (proto field 8, repeated BlobShape) are two independent repeated fields on the wire. Nothing enforces input_shape_size() == input_size(). When has_shape is true (input_shape_size() > 0) but input_shape_size() < input_size(), the accessor net_param->input_shape(i) for i >= input_shape_size() reads past the end of the input_shape RepeatedPtrField. In a release build (no bounds assertion) this is an out-of-bounds read of a BlobShape* slot that is then dereferenced by CopyFrom.

A trigger is a realistic malformed deploy prototxt / caffemodel that declares more input: names than input_shape {} blocks β€” e.g. two inputs but only one shape.

Distinction from the already-known input_dim OOB (not a duplicate)

This is a different bug from the well-worn input_dim(j) OOB at line 990:

This finding (line 983) Known input_dim bug (line 990)
Branch if (has_shape) else (has_dim && !has_shape)
Field read OOB input_shape β€” RepeatedPtrField<BlobShape> (field 8) input_dim β€” RepeatedField<int32> (field 4)
Trigger condition 0 < input_shape_size() < input_size() input_dim_size() < 4 * input_size()
Guarding predicate has_shape true has_shape false, has_dim true

The two branches are mutually exclusive (if (has_shape) {...} else {...}). Any PoC for the input_dim bug has input_shape_size() == 0, so has_shape is false and it never enters the branch that contains this defect. Reaching this code requires input_shape_size() > 0, which by construction excludes the input_dim path. They are distinct sinks reading distinct fields under distinct predicates.

PoC

The harness runs against the real toolchain:

  • caffe.pb.cc / caffe.pb.h generated by protoc 36.0.0 from the unmodified caffe.proto fetched from BVLC/caffe at the pinned commit β€” real generated NetParameter / BlobShape / InputParameter classes, not reimplemented.
  • Linked against the matching real libprotobuf.a runtime, so RepeatedPtrField::Get() bounds behavior is the real protobuf implementation.
  • The verbatim UpgradeNetInput() / NetNeedsInputUpgrade() bodies copied unmodified from upgrade_proto.cpp (upgrade_net_input_real.inc).
  • Compiled with -fsanitize=address,undefined.

The harness calls NetParameter::ParseFromString() on the PoC bytes and then the real UpgradeNetInput() β€” exactly the real file-load path (ReadProtoFromBinaryFile β†’ ParseFromArray β†’ UpgradeNetAsNeeded).

shape_oob.caffemodel (34 bytes): input = ["data", "extra"] (input_size = 2), one input_shape { dim:1 dim:3 dim:224 dim:224 } (input_shape_size = 1), no input_dim. At iteration i = 1, net_param->input_shape(1) is called against a RepeatedPtrField of real size 1.

Negative control shape_ok.caffemodel (39 bytes): input_size = 2, input_shape_size = 2 β€” runs UpgradeNetInput to completion, correctly emitting an Input layer with both shapes, exit 0. This isolates the fault to the input_size / input_shape count mismatch.

The build used here links a bounds-asserting protobuf, so the OOB access is caught deterministically as a RuntimeAssertInBounds failure (SIGABRT, exit 134) instead of a silent bad read β€” a clean, unambiguous witness that index i = 1 is out of bounds for the size-1 input_shape field. In a stock release build the same access is an unchecked out-of-bounds read/deref.

Captured evidence (verbatim)

### PoC (input_size=2, input_shape_size=1) -> CRASH
[load] read 34 bytes from shape_oob.caffemodel
[parse] NetParameter::ParseFromString() => true
[parse] name="shape_oob" input_size()=2 input_dim_size()=0 input_shape_size()=1
        input(0) = "data"
        input(1) = "extra"

[call] NetNeedsInputUpgrade(param) = true
[call] UpgradeNetInput(&param);  <-- REAL function, real protobuf RepeatedField
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
F0000 00:00:1784089412.559406 1305953 repeated_ptr_field.h:140] Check failed: index < size (1 vs. 1)
*** Check failure stack trace: ***
    @     0x5603a980a4b3  absl::lts_20250512::log_internal::LogMessage::SendToLog()
    @     0x5603a980a03c  absl::lts_20250512::log_internal::LogMessage::Flush()
    @     0x5603a89dbfaa  google::protobuf::internal::RuntimeAssertInBounds()
    @     0x5603a89d210b  UpgradeNetInput()
    @     0x5603a89d6cd7  main
    @     0x7ffb17031f77  (unknown)
    @     0x7ffeb09cc75e  (unknown)
exit=134

### Negative control (input_size=2, input_shape_size=2) -> OK
[load] read 39 bytes from shape_ok.caffemodel
[parse] NetParameter::ParseFromString() => true
[parse] name="shape_ok" input_size()=2 input_dim_size()=0 input_shape_size()=2
        input(0) = "data"
        input(1) = "extra"

[call] NetNeedsInputUpgrade(param) = true
[call] UpgradeNetInput(&param);  <-- REAL function, real protobuf RepeatedField
[done] UpgradeNetInput returned without crashing this run
[done] resulting layer_size()=1
        layer[0] input_param.shape(0).dim = [1 3 8 8 ]
        layer[0] input_param.shape(1).dim = [1 3 8 8 ]
exit=0

The Check failed: index < size (1 vs. 1) in repeated_ptr_field.h:140, framed by RuntimeAssertInBounds() directly beneath UpgradeNetInput(), confirms the out-of-bounds index into the input_shape RepeatedPtrField at loop iteration i = 1.

Impact

A crafted .caffemodel / deploy .prototxt with more input: entries than input_shape {} blocks triggers an out-of-bounds read (and pointer dereference) during the mandatory legacy-upgrade step of every model load. Denial of service on any consumer that loads untrusted Caffe models; in a release build the OOB BlobShape* read is unchecked and its consequences depend on adjacent heap contents.

Suggested fix

Bound the shape index by input_shape_size() (and reject / clamp the mismatch), e.g. guard has_shape per-iteration with i < net_param->input_shape_size(), or validate input_shape_size() == input_size() before entering the loop.

Files in this repo

  • repro_caffe.cpp β€” verification harness (parses bytes, calls real UpgradeNetInput)
  • upgrade_net_input_real.inc β€” verbatim UpgradeNetInput() / NetNeedsInputUpgrade() bodies
  • gen_poc.cpp / gen_good.cpp β€” generators for the two model files
  • shape_oob.caffemodel (34 B) β€” PoC trigger
  • shape_ok.caffemodel (39 B) β€” negative control
  • run_output_shape_oob.txt β€” captured run output (verbatim above)
  • caffe.proto β€” unmodified proto at pinned commit
  • build.sh β€” build steps

Dedup note

Distinct from the input_dim(j) OOB in the mutually-exclusive else branch of the same function (tracked separately as huntr-poc-caffe-oob-read). No CVE is known to cover the input_shape(i) has_shape-branch index specifically; existing public discussion of UpgradeNetInput OOBs concerns the input_dim path. This report targets the independent input_shape field-count mismatch.

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