Headline
CVE-2021-21793: TALOS-2021-1257 || Cisco Talos Intelligence Group
An out-of-bounds write vulnerability exists in the JPG sof_nb_comp header processing functionality of Accusoft ImageGear 19.8 and 19.9. A specially crafted malformed file can lead to memory corruption. An attacker can provide a malicious file to trigger this vulnerability.
Summary
An out-of-bounds write vulnerability exists in the JPG sof_nb_comp header processing functionality of Accusoft ImageGear 19.8 and 19.9. A specially crafted malformed file can lead to memory corruption. An attacker can provide a malicious file to trigger this vulnerability.
Tested Versions
Accusoft ImageGear 19.8
Accusoft ImageGear 19.9
Product URLs
https://www.accusoft.com/products/imagegear-collection/
CVSSv3 Score
9.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE
CWE-131 - Incorrect Calculation of Buffer Size
Details
The ImageGear library is a document-imaging developer toolkit that offers image conversion, creation, editing, annotation and more. It supports more than 100 formats such as DICOM, PDF, Microsoft Office and others.
A specially crafted JPG file can lead to an out-of-bounds write in the jpeg_raster_set function, due to a buffer overflow caused by a missing size check for a buffer memory.
Trying to load a malformed JPG file, we end up in the following situation:
(86a0.8438): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=d0d0d0d0 ebx=00000001 ecx=00000000 edx=0f349018 esi=0f348ff8 edi=0019f6e0
eip=6aba743b esp=0019f660 ebp=0019f6f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
igCore19d!IG_mpi_page_set+0xcb6eb:
6aba743b 8902 mov dword ptr [edx],eax ds:002b:0f349018=????????
When we look at the edx memory allocation we can see the buffer allocated is very small, only 1 byte:
0:000> !heap -p -a edx
address 0f349018 found in
_DPH_HEAP_ROOT @ 3f41000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
1503364c: f348ff8 1 - f348000 2000
70b5a8b0 verifier!AVrfDebugPageHeapAllocate+0x00000240
7777ef0e ntdll!RtlDebugAllocateHeap+0x00000039
776e6150 ntdll!RtlpAllocateHeap+0x000000f0
776e57fe ntdll!RtlpAllocateHeapInternal+0x000003ee
776e53fe ntdll!RtlAllocateHeap+0x0000003e
6b53dcff MSVCR110!malloc+0x00000049
6aad61de igCore19d!AF_memm_alloc+0x0000001e
6ab919e5 igCore19d!IG_mpi_page_set+0x000b5c95
6ab8fce3 igCore19d!IG_mpi_page_set+0x000b3f93
6aba70b5 igCore19d!IG_mpi_page_set+0x000cb365
6aba6f85 igCore19d!IG_mpi_page_set+0x000cb235
6aba5021 igCore19d!IG_mpi_page_set+0x000c92d1
6aba677a igCore19d!IG_mpi_page_set+0x000caa2a
6aab10d9 igCore19d!IG_image_savelist_get+0x00000b29
6aaf0557 igCore19d!IG_mpi_page_set+0x00014807
6aaefeb9 igCore19d!IG_mpi_page_set+0x00014169
6aa85777 igCore19d!IG_load_file+0x00000047
00498a3a Fuzzme!fuzzme+0x0000004a [C:\Users\etach\Documents\work\gitlab\ImageGear\Fuzzme\Fuzzme.cpp @ 282]
00498e36 Fuzzme!main+0x00000376 [C:\Users\etach\Documents\work\gitlab\ImageGear\Fuzzme\Fuzzme.cpp @ 477]
004daa53 Fuzzme!invoke_main+0x00000033 [d:\agent\_work\57\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
004da8a7 Fuzzme!__scrt_common_main_seh+0x00000157 [d:\agent\_work\57\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
004da73d Fuzzme!__scrt_common_main+0x0000000d [d:\agent\_work\57\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 331]
004daad8 Fuzzme!mainCRTStartup+0x00000008 [d:\agent\_work\57\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp @ 17]
763afa29 KERNEL32!BaseThreadInitThunk+0x00000019
777076b4 ntdll!__RtlUserThreadStart+0x0000002f
77707684 ntdll!_RtlUserThreadStart+0x0000001b
This write access violation is happening in the function jpeg_raster_set corresponding to the following pseudo-code:
LINE1 void jpeg_raster_set(jpeg_dec *jpeg_dec,SOF_object *SOF_Object,jpeg_related *jpeg_related,
LINE2 undefined4 param_4,int witdh,int height,byte *min_height,short *raster_buffer,
LINE3 uint size_raster_buffer,int SOF_type,int param_11,int value_to_8,void *param_13,
LINE4 int param_14,int param_15)
LINE5
LINE6 {
[...]
LINE54 uVar18 = (undefined)in_stack_ffffff78;
LINE55 local_8 = DAT_102bcea8 ^ (uint)&stack0xfffffffc;
LINE56 max_loop = jpeg_dec->enforced_8 * value_to_8;
LINE57 if (SOF_type == 2) {
LINE58 _sof_nb_component = *(byte *)&(SOF_Object->SOF).component;
LINE59 }
LINE60 else {
LINE61 _sof_nb_component = *(byte *)&SOF_Object->possible_num_component_or_color_channel;
LINE62 }
LINE63 nr_component_buffer_data = (byte *)SOF_Object->nr_component_buffer_data;
LINE64 if (SOF_type == 0) {
LINE65 LAB_1013741f:
LINE66 /* ---------------------------------
LINE67 Num of component = 1 e.g grayscale
LINE68 --------------------------------- */
LINE69 __sof_nb_component = (uint)_sof_nb_component;
LINE70 if (__sof_nb_component != 0) {
LINE71 _color_table_data = (undefined4 *)(nr_component_buffer_data + 0x20);
LINE72 uVar10 = __sof_nb_component;
LINE73 piVar15 = local_18;
LINE74 while (_sof_nb_comp = __sof_nb_component, uVar10 != 0) {
LINE75 uVar10 = uVar10 - 1;
LINE76 *piVar15 = 0;
LINE77 piVar15 = piVar15 + 1;
LINE78 }
LINE79 do {
LINE80 *_color_table_data = _color_table_data[-7];
LINE81 _color_table_data = _color_table_data + 0x14;
LINE82 _sof_nb_comp = _sof_nb_comp - 1;
LINE83 } while (_sof_nb_comp != 0);
LINE84 }
[...]
LINE720 return;
LINE721 }
The out-of-bounds is occurring in LINE80 where we can see the buffer _color_table_data. The loop is controlled by the variable _sof_nb_comp, derived from the SOF_Object component as read directly from the file.
The buffer _color_table_data is allocated earlier into the function possible_build_color_channel_data with the following pseudo-code in LINE773:
LINE722 int possible_build_color_channel_data
LINE723 (jpeg_dec *jpeg_dec,read_buffer *read_buffer,SOF_object *SOF_object,
LINE724 int *related_type_sof,uint *color_channel_data,void **possible_color_table_data,
LINE725 int *possible_boolean_value,int *param_8)
LINE726 {
[...]
LINE752 prVar10 = read_buffer;
LINE753 kind_heap = jpeg_dec->kind_of_heap;
LINE754 pbVar1 = (byte *)read_buffer->read_buffer_data;
LINE755 local_18 = 0;
LINE756 local_c = 0;
LINE757 dVar12 = 0;
LINE758 local_1c = 0;
LINE759 read_buffer = NULL;
LINE760 if (*(short *)&prVar10->buff_mem != -0x26) {
LINE761 iVar5 = AF_err_record_set("..\\..\\..\\..\\Common\\Formats\\jpeg_dec.c",0x525,-0x1310,0,0,0,NULL
LINE762 );
LINE763 return iVar5;
LINE764 }
LINE765 if (possible_color_table_data == NULL) {
LINE766 iVar5 = AF_err_record_set("..\\..\\..\\..\\Common\\Formats\\jpeg_dec.c",0x528,-0x1310,0,0,0,NULL
LINE767 );
LINE768 return iVar5;
LINE769 }
LINE770 *possible_color_table_data = NULL;
LINE771 nr_comp_sos = *pbVar1;
LINE772 *color_channel_data = (uint)nr_comp_sos;
LINE773 _color_table_data = AF_memm_alloc(kind_heap,(uint)nr_comp_sos * 0x50);
LINE774 if (_color_table_data == NULL) {
LINE775 iVar5 = AF_err_record_set("..\\..\\..\\..\\Common\\Formats\\jpeg_dec.c",0x531,-1000,0,0,0,NULL);
LINE776 return iVar5;
LINE777 }
[...]
LINE936 }
The size is controlled by the nr_comp_sos variable, which is directly read from the file.
If the nr_comp_sos is null then the size computed for the buffer is null and as there is no null check. The function AF_memm_alloc is a wrapper to malloc, thus when passing a null value it returns a buffer of one byte.
Thus in our case the buffer is only 1 byte long, most of the assignments happening inside function jpeg_raster_set are out-of-bounds heap writes which lead to memory corruption and possibly code execution.
Crash Information
0:000> !analyze -v
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
KEY_VALUES_STRING: 1
Key : AV.Fault
Value: Write
Key : Analysis.CPU.mSec
Value: 7312
Key : Analysis.DebugAnalysisProvider.CPP
Value: Create: 8007007e on DESKTOP-4DAOCFH
Key : Analysis.DebugData
Value: CreateObject
Key : Analysis.DebugModel
Value: CreateObject
Key : Analysis.Elapsed.mSec
Value: 100036
Key : Analysis.Memory.CommitPeak.Mb
Value: 188
Key : Analysis.System
Value: CreateObject
Key : Timeline.OS.Boot.DeltaSec
Value: 91388
Key : Timeline.Process.Start.DeltaSec
Value: 18
Key : WER.OS.Branch
Value: vb_release
Key : WER.OS.Timestamp
Value: 2019-12-06T14:06:00Z
Key : WER.OS.Version
Value: 10.0.19041.1
Key : WER.Process.Version
Value: 1.0.1.1
ADDITIONAL_XML: 1
OS_BUILD_LAYERS: 1
NTGLOBALFLAG: 2100000
APPLICATION_VERIFIER_FLAGS: 0
APPLICATION_VERIFIER_LOADED: 1
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 6aba743b (igCore19d!IG_mpi_page_set+0x000cb6eb)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 0f349018
Attempt to write to address 0f349018
FAULTING_THREAD: 00008438
PROCESS_NAME: Fuzzme.exe
WRITE_ADDRESS: 0f349018
ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.
EXCEPTION_CODE_STR: c0000005
EXCEPTION_PARAMETER1: 00000001
EXCEPTION_PARAMETER2: 0f349018
STACK_TEXT:
WARNING: Stack unwind information not available. Following frames may be wrong.
0019f6f0 6aba3501 09d72f60 0019f9a8 0c35a720 igCore19d!IG_mpi_page_set+0xcb6eb
0019f774 6ab94139 00001a2d 0c35a720 09d72f60 igCore19d!IG_mpi_page_set+0xc77b1
0019f97c 6ab900bf 09d72f60 0019f9a8 00000000 igCore19d!IG_mpi_page_set+0xb83e9
0019fa08 6aba70b5 00000000 6aba3030 0c35a720 igCore19d!IG_mpi_page_set+0xb436f
0019fa24 6aba6f85 0c35a720 09d72f60 0000ffda igCore19d!IG_mpi_page_set+0xcb365
0019fa48 6aba5021 0c35a720 09d72f60 0019fa70 igCore19d!IG_mpi_page_set+0xcb235
0019fa68 6aba677a 0019ffc1 1000001d 0bc60f70 igCore19d!IG_mpi_page_set+0xc92d1
0019faa8 6aab10d9 1000001d 0bc60f70 00000001 igCore19d!IG_mpi_page_set+0xcaa2a
0019fae0 6aaf0557 00000000 0bc60f70 0019fb30 igCore19d!IG_image_savelist_get+0xb29
0019fd5c 6aaefeb9 00000000 05444f88 00000001 igCore19d!IG_mpi_page_set+0x14807
0019fd7c 6aa85777 00000000 05444f88 00000001 igCore19d!IG_mpi_page_set+0x14169
0019fd9c 00498a3a 05444f88 0019fe0c 004801a4 igCore19d!IG_load_file+0x47
0019fe14 00498e36 05444f88 0019fe8c 004801a4 Fuzzme!fuzzme+0x4a
0019fee4 004daa53 00000005 05384f20 0538df20 Fuzzme!main+0x376
0019ff04 004da8a7 fd9de02f 004801a4 004801a4 Fuzzme!invoke_main+0x33
0019ff60 004da73d 0019ff70 004daad8 0019ff80 Fuzzme!__scrt_common_main_seh+0x157
0019ff68 004daad8 0019ff80 763afa29 002a3000 Fuzzme!__scrt_common_main+0xd
0019ff70 763afa29 002a3000 763afa10 0019ffdc Fuzzme!mainCRTStartup+0x8
0019ff80 777076b4 002a3000 a8e56cc7 00000000 KERNEL32!BaseThreadInitThunk+0x19
0019ffdc 77707684 ffffffff 77727415 00000000 ntdll!__RtlUserThreadStart+0x2f
0019ffec 00000000 004801a4 002a3000 00000000 ntdll!_RtlUserThreadStart+0x1b
STACK_COMMAND: ~0s ; .cxr ; kb
SYMBOL_NAME: igCore19d!IG_mpi_page_set+cb6eb
MODULE_NAME: igCore19d
IMAGE_NAME: igCore19d.dll
FAILURE_BUCKET_ID: INVALID_POINTER_WRITE_AVRF_c0000005_igCore19d.dll!IG_mpi_page_set
OS_VERSION: 10.0.19041.1
BUILDLAB_STR: vb_release
OSPLATFORM_TYPE: x86
OSNAME: Windows 10
IMAGE_VERSION: 19.9.0.0
FAILURE_ID_HASH: {39ff52ad-9054-81fd-3e4d-ef5d82e4b2c1}
Followup: MachineOwner
---------
Timeline
2021-02-25 - Vendor Disclosure
2021-05-31 - Vendor Patched
2021-06-01 - Public Release
Discovered by Emmanuel Tacheau of Cisco Talos.