CVE-2024-22391
A heap-based buffer overflow vulnerability exists in the LookupTable::SetLUT functionality of Mathieu Malaterre Grassroot DICOM 3.0.23. A specially crafted malformed file can lead to memory corruption. An attacker can provide a malicious file to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Grassroot DICOM 3.0.23
Grassroot DICOM - https://sourceforge.net/projects/gdcm/
7.7 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:H
CWE-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer
Grassroots DiCoM is a C++ library for DICOM medical files. It is accessible from Python, C#, Java and PHP. It supports RAW, JPEG, JPEG 2000, JPEG-LS, RLE and deflated transfer syntax. It comes with a super fast scanner implementation to quickly scan hundreds of DICOM files. It supports SCU network operations (C-ECHO, C-FIND, C-STORE, C-MOVE). PS 3.3 & 3.6 are distributed as XML files. It also provides PS 3.15 certificates and password based mechanism to anonymize and de-identify DICOM dataset
A specially-crafted DICOM file can lead to a heap-based buffer overflow in LookupTable::SetLUT
, due to a buffer overflow caused by a missing size check for a buffer memory.
Trying to load a malformed DICOM, we end up in the following situation:
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files (x86)\GDCM 3.0\bin\gdcmMSFF.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files (x86)\GDCM 3.0\bin\gdcmMSFF.dll -
eax=25fa0ffa ebx=00000303 ecx=078bf000 edx=00000101 esi=00000002 edi=07882ff0
eip=710cf115 esp=00b6f194 ebp=00b6f1ac iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
gdcmMSFF!gdcm::LookupTable::SetLUT+0x75:
710cf115 880431 mov byte ptr [ecx+esi],al ds:002b:078bf002=??
This is corresponding to the following code in LookupTable::SetLUT
function :
LINE120 void LookupTable::SetLUT(LookupTableType type, const unsigned char *array,
LINE121 unsigned int length)
LINE122 {
LINE123 (void)length;
LINE124 //if( !Initialized() ) return;
LINE125 if( !Internal->Length[type] )
LINE126 {
LINE127 gdcmDebugMacro( "Need to set length first" );
LINE128 return;
LINE129 }
LINE130
LINE131 if( !IncompleteLUT )
LINE132 {
LINE133 assert( Internal->RGB.size() == 3*Internal->Length[type]*(BitSample/8) );
LINE134 }
LINE135 // Too funny: 05115014-mr-siemens-avanto-syngo-with-palette-icone.dcm
LINE136 // There is pseudo PALETTE_COLOR LUT in the Icon, if one look carefully the LUT values
LINE137 // goes like this: 0, 1, 2, 3, 4, 5, 6, 7 ...
LINE138 if( BitSample == 8 )
LINE139 {
LINE140 const unsigned int mult = Internal->BitSize[type]/8;
LINE141 const unsigned int mult2 = length / Internal->Length[type];
LINE142 assert( Internal->Length[type] * mult2 == length );
LINE143 if( Internal->Length[type]*mult == length
LINE144 || Internal->Length[type]*mult + 1 == length )
LINE145 {
LINE146 assert( mult2 == 1 || mult2 == 2 );
LINE147 unsigned int offset = 0;
LINE148 if( mult == 2 )
LINE149 {
LINE150 offset = 1;
LINE151 }
LINE152 for( unsigned int i = 0; i < Internal->Length[type]; ++i)
LINE153 {
LINE154 assert( i*mult+offset < length );
LINE155 assert( 3*i+type < Internal->RGB.size() );
LINE156 Internal->RGB[3*i+type] = array[i*mult+offset];
LINE157 }
LINE158 }
LINE159 else
LINE160 {
LINE161 unsigned int offset = 0;
LINE162 assert( mult2 == 2 );
LINE163 for( unsigned int i = 0; i < Internal->Length[type]; ++i)
LINE164 {
LINE165 assert( i*mult2+offset < length );
LINE166 assert( 3*i+type < Internal->RGB.size() );
LINE167 Internal->RGB[3*i+type] = array[i*mult2+offset];
LINE168 }
LINE169 }
LINE170 }
LINE171 else if( BitSample == 16 )
LINE172 {
LINE173 assert( Internal->Length[type]*(BitSample/8) == length );
LINE174 uint16_t *uchar16 = (uint16_t*)(void*)Internal->RGB.data();
LINE175 const uint16_t *array16 = (const uint16_t*)(const void*)array;
LINE176 for( unsigned int i = 0; i < Internal->Length[type]; ++i)
LINE177 {
LINE178 assert( 2*i < length );
LINE179 assert( 2*(3*i+type) < Internal->RGB.size() );
LINE180 uchar16[3*i+type] = array16[i];
LINE181 }
LINE182 }
LINE183 }
The crash is happening at LINE167 while performing a copy into Internal->RGB[3*i+type]
with the content of array[i*mult2+offset]
issued and controlled from the file.
Internal->Length[type]
is set earlier into the code with a call to LookupTable::InitializeLUT
, code is following :
LINE89 void LookupTable::InitializeLUT(LookupTableType type, unsigned short length,
LINE90 unsigned short subscript, unsigned short bitsize)
LINE91 {
LINE92 if( bitsize != 8 && bitsize != 16 )
LINE93 {
LINE94 return;
LINE95 }
LINE96 assert( type >= RED && type <= BLUE );
LINE97 assert( subscript == 0 );
LINE98 assert( bitsize == 8 || bitsize == 16 );
LINE99 if( length == 0 )
LINE100 {
LINE101 Internal->Length[type] = 65536;
LINE102 }
LINE103 else
LINE104 {
LINE105 if( length != 256 )
LINE106 {
LINE107 IncompleteLUT = true;
LINE108 }
LINE109 Internal->Length[type] = length;
LINE110 }
LINE111 Internal->Subscript[type] = subscript;
LINE112 Internal->BitSize[type] = bitsize;
LINE113 }
We can control the for-loop at LINE176
with the length
variable in LookupTable::InitializeLUT
at LINE109
once we passed the condition for bitsize
at LINE92
.
The crash is then happening because there is no check about size Internal->RGB[3*i+type]
against Internal->Length[type]
which can be higher and causing the heap-based buffer overflow leading to memory corruption
0:000> !analyze -v
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
DUMP_CLASS: 2
DUMP_QUALIFIER: 0
FAULTING_IP:
gdcmMSFF!gdcm::LookupTable::SetLUT+75
710cf115 880431 mov byte ptr [ecx+esi],al
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 710cf115 (gdcmMSFF!gdcm::LookupTable::SetLUT+0x00000075)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 078bf002
Attempt to write to address 078bf002
FAULTING_THREAD: 00000ea4
PROCESS_NAME: image00a50000
ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.
EXCEPTION_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: 078bf002
FOLLOWUP_IP:
gdcmMSFF!gdcm::LookupTable::SetLUT+75
710cf115 880431 mov byte ptr [ecx+esi],al
WRITE_ADDRESS: 078bf002
WATSON_BKT_PROCSTAMP: 659e7f9a
WATSON_BKT_MODULE: gdcmMSFF.dll
WATSON_BKT_MODSTAMP: 659e7cff
WATSON_BKT_MODOFFSET: 9f115
BUILD_VERSION_STRING: 10.0.19041.1202 (WinBuild.160101.0800)
MODLIST_WITH_TSCHKSUM_HASH: 9626d4274e3aeb03d0079cb603e43768e2a4f582
MODLIST_SHA1_HASH: e93b0561317f695817c9f5a930d4082a4eac155f
NTGLOBALFLAG: 2100000
PROCESS_BAM_CURRENT_THROTTLED: 0
PROCESS_BAM_PREVIOUS_THROTTLED: 0
APPLICATION_VERIFIER_FLAGS: 0
PRODUCT_TYPE: 1
SUITE_MASK: 272
DUMP_TYPE: fe
APPLICATION_VERIFIER_LOADED: 1
ANALYSIS_SESSION_HOST: DESKTOP-RU7OS2O
ANALYSIS_SESSION_TIME: 01-23-2024 10:27:28.0218
ANALYSIS_VERSION: 10.0.16299.15 x86fre
THREAD_ATTRIBUTES:
OS_LOCALE: ENU
PROBLEM_CLASSES:
ID: [0n301]
Type: [@ACCESS_VIOLATION]
Class: Addendum
Scope: BUCKET_ID
Name: Omit
Data: Omit
PID: [Unspecified]
TID: [0xea4]
Frame: [0] : gdcmMSFF!gdcm::LookupTable::SetLUT
ID: [0n274]
Type: [INVALID_POINTER_WRITE]
Class: Primary
Scope: DEFAULT_BUCKET_ID (Failure Bucket ID prefix)
BUCKET_ID
Name: Add
Data: Omit
PID: [Unspecified]
TID: [0xea4]
Frame: [0] : gdcmMSFF!gdcm::LookupTable::SetLUT
ID: [0n92]
Type: [AVRF]
Class: Addendum
Scope: DEFAULT_BUCKET_ID (Failure Bucket ID prefix)
BUCKET_ID
Name: Add
Data: Omit
PID: [0xb34]
TID: [0xea4]
Frame: [0] : gdcmMSFF!gdcm::LookupTable::SetLUT
BUGCHECK_STR: APPLICATION_FAULT_INVALID_POINTER_WRITE_AVRF
DEFAULT_BUCKET_ID: INVALID_POINTER_WRITE_AVRF
PRIMARY_PROBLEM_CLASS: APPLICATION_FAULT
LAST_CONTROL_TRANSFER: from 710aa40f to 710cf115
STACK_TEXT:
WARNING: Stack unwind information not available. Following frames may be wrong.
00b6f1ac 710aa40f 00000002 25fa0f00 00000100 gdcmMSFF!gdcm::LookupTable::SetLUT+0x75
00b6f234 710ac033 0773aff8 1fca8f90 f9e36fde gdcmMSFF!gdcm::LookupTable::operator=+0x7df
00b6f490 710ab78d 00b6f4bc 00000001 00b6f4c4 gdcmMSFF!gdcm::PixmapReader::ReadImageInternal+0x893
00b6f4a0 710aafed 00b6f4bc 70f0a9f0 00000000 gdcmMSFF!gdcm::PixmapReader::ReadImage+0xd
00b6f4c4 00a55ff8 1bd021fb 00000000 00000001 gdcmMSFF!gdcm::PixmapReader::Read+0x4d
00b6f8ec 00a57791 00000005 0766cf90 05f63f48 image00a50000+0x5ff8
00b6f92c 76ebfa29 00895000 76ebfa10 00b6f998 image00a50000+0x7791
00b6f93c 779c7a9e 00895000 17538826 00000000 KERNEL32!BaseThreadInitThunk+0x19
00b6f998 779c7a6e ffffffff 779e8a4b 00000000 ntdll!__RtlUserThreadStart+0x2f
00b6f9a8 00000000 00a577f9 00895000 00000000 ntdll!_RtlUserThreadStart+0x1b
THREAD_SHA1_HASH_MOD_FUNC: 7fba889d1726d52834e7da9be4e2c022fbcc2406
THREAD_SHA1_HASH_MOD_FUNC_OFFSET: a02fa1789a8198ecd2c54b172c054502627b0dc8
THREAD_SHA1_HASH_MOD: 65266272687dc14698c1e203319417fa8d2b17ab
FAULT_INSTR_CODE: 8b310488
SYMBOL_STACK_INDEX: 0
SYMBOL_NAME: gdcmMSFF!gdcm::LookupTable::SetLUT+75
FOLLOWUP_NAME: MachineOwner
MODULE_NAME: gdcmMSFF
IMAGE_NAME: gdcmMSFF.dll
DEBUG_FLR_IMAGE_TIMESTAMP: 659e7cff
STACK_COMMAND: dt ntdll!LdrpLastDllInitializer BaseDllName ; dt ntdll!LdrpFailureData ; ~0s ; .cxr ; kb
FAILURE_BUCKET_ID: INVALID_POINTER_WRITE_AVRF_c0000005_gdcmMSFF.dll!gdcm::LookupTable::SetLUT
BUCKET_ID: APPLICATION_FAULT_INVALID_POINTER_WRITE_AVRF_gdcmMSFF!gdcm::LookupTable::SetLUT+75
FAILURE_EXCEPTION_CODE: c0000005
FAILURE_IMAGE_NAME: gdcmMSFF.dll
BUCKET_ID_IMAGE_STR: gdcmMSFF.dll
FAILURE_MODULE_NAME: gdcmMSFF
BUCKET_ID_MODULE_STR: gdcmMSFF
FAILURE_FUNCTION_NAME: gdcm::LookupTable::SetLUT
BUCKET_ID_FUNCTION_STR: gdcm::LookupTable::SetLUT
BUCKET_ID_OFFSET: 75
BUCKET_ID_MODTIMEDATESTAMP: 659e7cff
BUCKET_ID_MODCHECKSUM: 0
BUCKET_ID_MODVER_STR: 0.0.0.0
BUCKET_ID_PREFIX_STR: APPLICATION_FAULT_INVALID_POINTER_WRITE_AVRF_
FAILURE_PROBLEM_CLASS: APPLICATION_FAULT
FAILURE_SYMBOL_NAME: gdcmMSFF.dll!gdcm::LookupTable::SetLUT
TARGET_TIME: 2024-01-23T09:27:29.000Z
OSBUILD: 19044
OSSERVICEPACK: 1202
SERVICEPACK_NUMBER: 0
OS_REVISION: 0
OSPLATFORM_TYPE: x86
OSNAME: Windows 10
OSEDITION: Windows 10 WinNt SingleUserTS
USER_LCID: 0
OSBUILD_TIMESTAMP: unknown_date
BUILDDATESTAMP_STR: 160101.0800
BUILDLAB_STR: WinBuild
BUILDOSVER_STR: 10.0.19041.1202
ANALYSIS_SESSION_ELAPSED_TIME: 5ad
ANALYSIS_SOURCE: UM
FAILURE_ID_HASH_STRING: um:invalid_pointer_write_avrf_c0000005_gdcmmsff.dll!gdcm::lookuptable::setlut
FAILURE_ID_HASH: {e0bb4bb5-cfe3-211b-38b1-95d12fb0ce17}
Followup: MachineOwner
The vendor has fixed the code on their sourceforge site.
2024-02-15 - Initial Vendor Contact
2024-02-20 - Vendor Disclosure
2024-02-21 - Vendor Patch Release
2024-04-25 - Public Release
Discovered by Emmanuel Tacheau of Cisco Talos.