Talos Vulnerability Report

TALOS-2024-1924

Grassroot DICOM LookupTable::SetLUT out-of-bounds write vulnerability

April 25, 2024
CVE Number

CVE-2024-22391

SUMMARY

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.

CONFIRMED VULNERABLE VERSIONS

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

PRODUCT URLS

Grassroot DICOM - https://sourceforge.net/projects/gdcm/

CVSSv3 SCORE

7.7 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:H

CWE

CWE-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer

DETAILS

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

Crash Information

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
VENDOR RESPONSE

The vendor has fixed the code on their sourceforge site.

TIMELINE

2024-02-15 - Initial Vendor Contact
2024-02-20 - Vendor Disclosure
2024-02-21 - Vendor Patch Release
2024-04-25 - Public Release

Credit

Discovered by Emmanuel Tacheau of Cisco Talos.