OpenCV ONNX importer out-of-bounds read in getMatFromTensor
This repository contains a proof-of-concept malicious ONNX model that crashes OpenCV's
cv2.dnn.readNetFromONNX with an out-of-bounds read at model-load time (SIGSEGV).
It is a security PoC for a huntr Model File Format report. The model is intentionally malformed and the repo is gated.
Affected
opencv-python/ OpenCVcv2.dnn, confirmed on 4.13.0 (latest at time of writing), on Linux.- The vulnerable code is unchanged on
master. - Entry point:
cv2.dnn.readNetFromONNX(path)(also reachable viareadNetFromONNXfrom a buffer).
Root cause
modules/dnn/src/onnx/onnx_graph_simplifier.cpp, function getMatFromTensor. It reads a tensor's
dims into a sizes vector and copies the tensor bytes into a cv::Mat without validating that
raw_data actually holds that many elements:
std::vector<int> sizes; // = tensor dims, attacker controlled
for (int i = 0; i < tensor_proto.dims_size(); i++)
sizes.push_back(tensor_proto.dims(i));
...
// FLOAT path
Mat(sizes, CV_32FC1, (void*)tensor_proto.raw_data().c_str()).copyTo(blob);
The source Mat is a header over the (tiny) raw_data buffer but carries the declared element
count. copyTo allocates the destination for the declared count and memcpys that many bytes from
the source, reading far past the end of raw_data. There is no size check, and no
CV_Assert(blob.isContinuous()) guard (the TensorFlow and Darknet importers have such guards; this
ONNX path does not, and it has no length assertion at all).
Proof of concept
poc.onnx is an 80-byte model whose single FLOAT initializer declares dims = [10000000] but
carries only 16 bytes of raw_data. Loading it makes getMatFromTensor allocate a 40 MB
destination and copy ~40 MB from the 16-byte source, reading off the end of the heap buffer.
pip install opencv-python onnx numpy
python make_poc.py # writes poc.onnx
python verify.py # loads poc.onnx in a child process; reports the crash
Observed on opencv-python 4.13.0 (Linux): the child process terminates with SIGSEGV (exit -11)
inside readNetFromONNX, before any inference. On Windows it terminates with an access violation
(0xC0000005). A non-vulnerable build would load the model or raise an ordinary exception.
Impact
Any application that loads an untrusted or attacker-supplied .onnx through OpenCV is exposed.
Loading third-party ONNX models is a normal, documented use of this API. An 80-byte file reliably
crashes the process via an out-of-bounds read in native code (a denial of service), and because the
over-read bytes are copied into the model's weight blob, the read can also disclose adjacent process
heap memory (information leak).
Fix
In getMatFromTensor, validate the tensor's declared element count against the actual size of the
data source before constructing the source Mat / calling copyTo, for every dtype path (FLOAT,
DOUBLE, FP16, INT32, INT64, raw_data). For example require
raw_data.size() == elementCount * elemSize (and the analogous check for the typed *_data
fields), and reject the model otherwise.
Relation to the Caffe importer issue
This is a distinct vulnerability from the OpenCV Caffe importer out-of-bounds issue in
caffe_importer.cpp blobFromProto: a different importer, a different function
(getMatFromTensor in onnx_graph_simplifier.cpp), and a different model format (ONNX). It also
manifests differently, faulting on Linux via the source over-read rather than only on Windows.