TheMrEvil's picture
Upload README.md with huggingface_hub
432eda1 verified
|
Raw
History Blame Contribute Delete
3.58 kB

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 / OpenCV cv2.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 via readNetFromONNX from 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.