| # OpenCV TFLite importer out-of-bounds read in parseTensor |
|
|
| This repository contains a proof-of-concept malicious TFLite model that crashes OpenCV's |
| `cv2.dnn.readNetFromTFLite` 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. Notably, the file is a **structurally valid** TFLite flatbuffer: it passes |
| OpenCV's FlatBuffers verifier, because the verifier checks structure, not the consistency between a |
| tensor's declared shape and its buffer length. |
|
|
| ## 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.readNetFromTFLite(path)`. |
|
|
| ## Root cause |
|
|
| `modules/dnn/src/tflite/tflite_importer.cpp`, `TFLiteImporter::parseTensor`. The importer first runs |
| the FlatBuffers verifier (`VerifyModelBuffer`), then for each tensor reads its shape and wraps the |
| tensor's buffer in a `cv::Mat` without validating that the buffer byte size matches the declared |
| element count: |
|
|
| ```cpp |
| std::vector<int> shape(tensor.shape()->begin(), tensor.shape()->end()); |
| const Buffer* buffer = model->buffers()->Get(tensor.buffer()); |
| const void* data = buffer->data()->data(); |
| ... |
| Mat res = Mat(shape, dtype, const_cast<void*>(data)); // no shape-vs-buffer-size check |
| ``` |
|
|
| The verifier only validates flatbuffer structure (offsets/vectors are internally consistent), not |
| that a tensor's declared shape matches its buffer length. A constant tensor whose shape declares far |
| more elements than its buffer holds therefore passes verification, and when the tensor is consumed |
| during layer construction the `Mat` is read/copied over its declared (huge) element count, reading |
| off the end of the small buffer. |
|
|
| ## Proof of concept |
|
|
| `poc.tflite` is a valid one-Dense-layer model whose weight tensor shape is changed from `[8, 4]` to |
| `[134217728, 4]` while its buffer still holds only 32 floats. The change is a single int32 value |
| inside the shape vector, so the flatbuffer stays structurally valid and the verifier passes. Loading |
| the model makes the importer read ~134M*4 elements from the 32-float buffer. |
| |
| ``` |
| pip install opencv-python # to reproduce (verify.py) |
| python verify.py # loads poc.tflite in a child process; reports the crash |
| # to regenerate poc.tflite from scratch you also need tensorflow: |
| pip install tensorflow && python make_poc.py |
| ``` |
| |
| Observed on opencv-python 4.13.0 (Linux): the child process terminates with SIGSEGV (exit -11) |
| inside `readNetFromTFLite`, before any inference. On Windows it terminates with an access violation |
| (`0xC0000005`). A non-vulnerable build would load the model or reject it cleanly. |
| |
| ## Impact |
| |
| Any application that loads an untrusted or attacker-supplied `.tflite` through OpenCV is exposed. |
| Loading third-party TFLite models is a normal, documented use of this API, and the malicious file |
| passes the importer's own verifier, so a "verify before use" defense does not help. A ~1 KB 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 read into the tensor's Mat, the read can also disclose adjacent |
| process heap memory. |
| |
| ## Fix |
| |
| In `parseTensor`, after reading the shape and buffer, validate that |
| `buffer->data()->size() == shape_product * elemSize` (and reject the model otherwise) before |
| constructing the `Mat`. |
|
|
| ## Relation to the other OpenCV importer issues |
|
|
| This is a distinct vulnerability from the OpenCV Caffe importer (`caffe_importer.cpp` `blobFromProto`) |
| and ONNX importer (`onnx_graph_simplifier.cpp` `getMatFromTensor`) out-of-bounds issues: a different |
| importer, a different function (`parseTensor` in `tflite_importer.cpp`), and a different model format |
| (TFLite). It is also notable in that the malicious model passes the importer's FlatBuffers verifier. |
|
|