# 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: ```cpp std::vector 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 `memcpy`s 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.