YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Caffe .caffemodel InnerProductLayer OOB Read PoC
Vulnerability Summary
| Field | Value |
|---|---|
| Library | Caffe (huntr MFV $1,500) |
| Format | .caffemodel (Protocol Buffers) |
| Bug class | Heap-buffer-overflow READ (CWE-125) |
| Root cause | InnerProductLayer::Forward_cpu accesses blobs_[1] (bias) when bias_term=true (default), but the .caffemodel only provides 1 blob (weights). LayerSetUp skips initialization if blobs_.size() > 0 without verifying blob count matches layer requirements. |
| Sink | blobs_[1]->cpu_data() at inner_product_layer.cpp:88 โ reads past end of blobs_ vector |
| Impact | Heap OOB read โ info leak or crash; attacker-controlled .caffemodel file triggers it |
Root Cause Analysis
Caffe's layer loading flow (include/caffe/layer.hpp:44-49):
// Layer constructor โ loads ALL blobs from protobuf without count validation
if (layer_param.blobs_size() > 0) {
blobs_.resize(layer_param.blobs_size());
for (int i = 0; i < layer_param.blobs_size(); ++i) {
blobs_[i].reset(new Blob<Dtype>());
blobs_[i]->FromProto(layer_param.blobs(i));
}
}
Then InnerProductLayer::LayerSetUp (inner_product_layer.cpp:23):
// If ANY blobs were provided, skip initialization entirely
if (this->blobs_.size() > 0) {
return; // Does NOT verify blobs_.size() == (bias_term ? 2 : 1)
}
Then Forward_cpu (inner_product_layer.cpp:88):
if (bias_term_) {
const Dtype* bias = blobs_[1]->cpu_data(); // OOB when blobs_.size() == 1
// ...
}
The gap: Caffe trusts that the .caffemodel protobuf provides the correct number of blobs for each layer's configuration. No validation bridges the gap between "some blobs present" and "the right number of blobs present."
Build & Run
Requirements: g++ (C++17), protoc, libprotobuf-dev, python3, AddressSanitizer.
chmod +x build_and_run.sh
./build_and_run.sh
Or manually:
# 1. Compile caffe.proto
protoc --proto_path=../caffe-real/src --cpp_out=. ../caffe-real/src/caffe/proto/caffe.proto
g++ -std=c++17 -I. -c caffe/proto/caffe.pb.cc -o caffe.pb.o
# 2. Build PoC with ASan
g++ -std=c++17 -DNDEBUG -O1 -fsanitize=address -fno-omit-frame-pointer -g \
-I. poc.cc caffe.pb.o -lprotobuf -o poc_asan
# 3. Run
./poc_asan
ASan Trace (key lines)
==PID==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x... at pc 0x...
READ of size 8 at 0x... thread T0
#0 InnerProductLayer<float>::Forward_cpu(...) poc.cc:170
#1 LoadAndProcessCaffemodel(...) poc.cc:304
#2 main poc.cc:337
0x... is located 0 bytes after 8-byte region [0x...,0x...)
allocated by thread T0 here:
#0 operator new(unsigned long)
...
#5 std::vector<Blob<float>*>::resize(...)
#6 InnerProductLayer<float>::InnerProductLayer(...) poc.cc:87
The 8-byte allocation = 1 Blob<float>* pointer. ASan reports the read at byte 8 (0 bytes past end) when accessing blobs_[1].
Files
| File | Purpose |
|---|---|
poc.cc |
Self-contained PoC: creates malicious protobuf in-memory, loads it, triggers OOB |
load_poc.cc |
File-loading variant: reads a pre-crafted .caffemodel from disk |
gen_malicious_caffemodel.py |
Python script to generate malicious .caffemodel using raw protobuf wire format |
build_and_run.sh |
One-command build and execution |
Makefile |
Alternative build system |
caffe/ |
Compiled protobuf C++ bindings |
Fix Recommendation
In InnerProductLayer::LayerSetUp, validate blob count against layer requirements:
if (this->blobs_.size() > 0) {
const int expected = bias_term_ ? 2 : 1;
if (static_cast<int>(this->blobs_.size()) != expected) {
LOG(FATAL) << "InnerProductLayer: expected " << expected
<< " blobs but got " << this->blobs_.size();
}
return;
}
This pattern likely affects other Caffe layer types with similar "skip init if blobs provided" logic.