POSTED BY: Stelios Tsampas / 11.01.2016

GDCM out of bounds read in JPEGLSCodec :: DecodeExtent

CENSUS ID:CENSUS-2016-0002
CVE ID:CVE-2015-8397
Affected Products:Applications that use GDCM versions < 2.6.2 to process JPEG-LS images
Class:Out-of-bounds Read (CWE-125)
Discovered by:Stelios Tsampas

Grassroots DICOM (GDCM) is a C++ library for processing DICOM medical images. It provides routines to view and manipulate a wide range of image formats and can be accessed through many popular programming languages like Python, C#, Java and PHP. Various applications that make use of GDCM are listed here and here.

GDCM versions 2.6.0 and 2.6.1 (and possibly previous versions) are prone to an out-of-bounds read vulnerability due to missing checks. The vulnerability occurs during the decoding of JPEG-LS images when the dimensions of the embedded JPEG-LS image (as specified in the JPEG headers) are smaller than the ones of the selected region (set by gdcm::ImageRegionReader::SetRegion and usually based on DICOM header values).

Details

The faulty operation can be found twice in function JPEGLSCodec::DecodeExtent() of file gdcmJPEGLSCodec.cxx, once in line 463 for two-dimensional DICOM images and once more in line 518 for three-dimensional DICOM images. Both cases are similar.


bool JPEGLSCodec::DecodeExtent(
    char *buffer,
    unsigned int xmin, unsigned int xmax,
    unsigned int ymin, unsigned int ymax,
    unsigned int zmin, unsigned int zmax,
    std::istream & is
  )
{
[...]
  std::vector  outv;
  bool b = DecodeByStreamsCommon(dummy_buffer, buf_size, outv);
  if( !b ) return false;

  unsigned char *raw = &outv[0];
  const unsigned int rowsize = xmax — xmin + 1;
  const unsigned int colsize = ymax — ymin + 1;
  const unsigned int bytesPerPixel = pf.GetPixelSize();

  const unsigned char * tmpBuffer1 = raw;
  unsigned int z = 0;
  for (unsigned int y = ymin; y <= ymax; ++y)
  {
    size_t theOffset = 0 + (z * dimensions[1] * dimensions[0]
                                   + y * dimensions[0] + xmin) * bytesPerPixel;
    tmpBuffer1 = raw + theOffset;
    memcpy( &(buffer[((z-zmin) * rowsize * colsize +
        (y-ymin) * rowsize) * bytesPerPixel]),
        tmpBuffer1, rowsize * bytesPerPixel);
  }
[...]
}

DecodeByStreamsCommon() will attempt to decode the JPEG-LS image found in the DICOM file and place its contents in outv. The size of outv depends only on the dimensions reported by the JPEG-LS image headers.

xmin, xmax, ymin, ymax, zmin, zmax are the dimensions of the DICOM image region, which are set by gdcm::ImageRegionReader::SetRegion(). The region usually takes up the entire image, meaning that its dimensions are derived from the DICOM headers, although theoretically it could be an arbitrary 2-d rectangle or 3-d rectangular box.

The dimensions array holds the dimensions of the decoded JPEG-LS image. For each iteration in the "for" loop, memcpy() will copy rowsize * bytesPerPixel bytes from raw + theOffset, with raw pointing to outv and theOffset increasing according to the JPEG-LS image's "x" dimension and the region's ymin, ymax and xmin (note that z is 0 in the above 2-d copying operation).

The problem is that GDCM does not take into account the fact that the region to be copied could potentially be bigger than the image present in the file. For instance, if the JPEG-LS image has the same "x" dimension as the region but a much smaller "y" dimension, then raw + theOffset will point past the end of outv when the loop counter increases beyond the image's "y" dimension. This will lead to an out-of-bounds read condition.

Discussion

This is an information disclosure issue. An attacker could potentially leak parts of an application's memory or cause an application to crash, by providing a specially crafted DICOM file. Applications that process JPEG-LS images via GDCM versions prior to 2.6.2 may be subject to this vulnerability.

The GDCM project has released version 2.6.2 that addresses this issue. It is advised to upgrade all GDCM installations to the latest stable release.

Disclosure Timeline

CVE assignment:December 2nd, 2015
Vendor Contact:December 4th, 2015
Vendor Patch Release:December 23rd, 2015
Public Advisory:January 11th, 2016