Commit message: None
CVE: CVE-2017-6501
CWE: CWE-476
Vulnerability description: An issue was discovered in ImageMagick 6.9.7. A specially crafted xcf file could lead to a NULL pointer dereference.

Project: ImageMagick

static CompositeOperator GIMPBlendModeToCompositeOperator(
  size_t blendMode)
{
  switch ( blendMode )
  {
    case GIMP_NORMAL_MODE:       return(OverCompositeOp);
    case GIMP_DISSOLVE_MODE:     return(DissolveCompositeOp);
    case GIMP_MULTIPLY_MODE:     return(MultiplyCompositeOp);
    case GIMP_SCREEN_MODE:       return(ScreenCompositeOp);
    case GIMP_OVERLAY_MODE:      return(OverlayCompositeOp);
    case GIMP_DIFFERENCE_MODE:   return(DifferenceCompositeOp);
    case GIMP_ADDITION_MODE:     return(AddCompositeOp);
    case GIMP_SUBTRACT_MODE:     return(SubtractCompositeOp);
    case GIMP_DARKEN_ONLY_MODE:  return(DarkenCompositeOp);
    case GIMP_LIGHTEN_ONLY_MODE: return(LightenCompositeOp);
    case GIMP_HUE_MODE:          return(HueCompositeOp);
    case GIMP_SATURATION_MODE:   return(SaturateCompositeOp);
    case GIMP_COLOR_MODE:        return(ColorizeCompositeOp);
    case GIMP_DODGE_MODE:        return(ColorDodgeCompositeOp);
    case GIMP_BURN_MODE:         return(ColorBurnCompositeOp);
    case GIMP_HARDLIGHT_MODE:    return(HardLightCompositeOp);
    case GIMP_DIVIDE_MODE:       return(DivideCompositeOp);
    /* these are the ones we don't support...yet */
    case GIMP_BEHIND_MODE:       return(OverCompositeOp);
    case GIMP_VALUE_MODE:        return(OverCompositeOp);
    default:                     return(OverCompositeOp);
  }
}

Project: ImageMagick

static void InitXCFImage(XCFLayerInfo *outLayer)
{
  outLayer->image->page.x=outLayer->offset_x;
  outLayer->image->page.y=outLayer->offset_y;
  outLayer->image->page.width=outLayer->width;
  outLayer->image->page.height=outLayer->height;
  (void) SetImageProperty(outLayer->image,"label",(char *)outLayer->name);
}

Project: ImageMagick
File: xcf.c

static MagickBooleanType IsXCF(const unsigned char *magick,const size_t length)
{
  if (length < 8)
    return(MagickFalse);
  if (LocaleNCompare((char *) magick,"gimp xcf",8) == 0)
    return(MagickTrue);
  return(MagickFalse);
}

Project: ImageMagick

static MagickBooleanType ReadOneLayer(const ImageInfo *image_info,Image* image,
  XCFDocInfo* inDocInfo,XCFLayerInfo *outLayer,const ssize_t layer)
{
  MagickOffsetType
    offset;

  unsigned int
    foundPropEnd = 0;

  size_t
    hierarchy_offset,
    layer_mask_offset;

  /* clear the block! */
  (void) ResetMagickMemory( outLayer, 0, sizeof( XCFLayerInfo ) );
  /* read in the layer width, height, type and name */
  outLayer->width = ReadBlobMSBLong(image);
  outLayer->height = ReadBlobMSBLong(image);
  outLayer->type = ReadBlobMSBLong(image);
  (void) ReadBlobStringWithLongSize(image, outLayer->name,
    sizeof(outLayer->name));
  if (EOFBlob(image) != MagickFalse)
    ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile",
      image->filename);
  /* read the layer properties! */
  foundPropEnd = 0;
  while ( (foundPropEnd == MagickFalse) && (EOFBlob(image) == MagickFalse) ) {
  PropType    prop_type = (PropType) ReadBlobMSBLong(image);
  size_t  prop_size = ReadBlobMSBLong(image);
    switch (prop_type)
    {
    case PROP_END:
      foundPropEnd = 1;
      break;
    case PROP_ACTIVE_LAYER:
      outLayer->active = 1;
      break;
    case PROP_FLOATING_SELECTION:
      outLayer->floating_offset = ReadBlobMSBLong(image);
      break;
    case PROP_OPACITY:
      outLayer->alpha = ReadBlobMSBLong(image);
      break;
    case PROP_VISIBLE:
      outLayer->visible = ReadBlobMSBLong(image);
      break;
    case PROP_LINKED:
      outLayer->linked = ReadBlobMSBLong(image);
      break;
    case PROP_PRESERVE_TRANSPARENCY:
      outLayer->preserve_trans = ReadBlobMSBLong(image);
      break;
    case PROP_APPLY_MASK:
      outLayer->apply_mask = ReadBlobMSBLong(image);
      break;
    case PROP_EDIT_MASK:
      outLayer->edit_mask = ReadBlobMSBLong(image);
      break;
    case PROP_SHOW_MASK:
      outLayer->show_mask = ReadBlobMSBLong(image);
      break;
    case PROP_OFFSETS:
      outLayer->offset_x = ReadBlobMSBSignedLong(image);
      outLayer->offset_y = ReadBlobMSBSignedLong(image);
      break;
    case PROP_MODE:
      outLayer->mode = ReadBlobMSBLong(image);
      break;
    case PROP_TATTOO:
      outLayer->preserve_trans = ReadBlobMSBLong(image);
      break;
     case PROP_PARASITES:
     {
       if (DiscardBlobBytes(image,prop_size) == MagickFalse)
         ThrowFileException(&image->exception,CorruptImageError,
           "UnexpectedEndOfFile",image->filename);

        /*
       ssize_t base = info->cp;
       GimpParasite *p;
       while (info->cp - base < prop_size)
       {
       p = xcf_load_parasite(info);
       gimp_drawable_parasite_attach(GIMP_DRAWABLE(layer), p);
       gimp_parasite_free(p);
       }
       if (info->cp - base != prop_size)
       g_message ("Error detected while loading a layer's parasites");
       */
     }
     break;
    default:
      /* g_message ("unexpected/unknown layer property: %d (skipping)",
         prop_type); */

      {
      int buf[16];
      ssize_t amount;

      /* read over it... */
      while ((prop_size > 0) && (EOFBlob(image) == MagickFalse))
        {
        amount = (ssize_t) MagickMin(16, prop_size);
        amount = ReadBlob(image, (size_t) amount, (unsigned char *) &buf);
        if (!amount)
          ThrowBinaryException(CorruptImageError,"CorruptImage",
            image->filename);
        prop_size -= (size_t) MagickMin(16, (size_t) amount);
        }
      }
      break;
    }
  }

  if (foundPropEnd == MagickFalse)
    return(MagickFalse);
  /* allocate the image for this layer */
  if (image_info->number_scenes != 0)
    {
      ssize_t
        scene;

      scene=inDocInfo->number_layers-layer-1;
      if (scene > (ssize_t) (image_info->scene+image_info->number_scenes-1))
        {
          outLayer->image=CloneImage(image,0,0,MagickTrue,&image->exception);
          if (outLayer->image == (Image *) NULL)
            return(MagickFalse);
          InitXCFImage(outLayer);
          return(MagickTrue);
        }
    }
  /* allocate the image for this layer */
  outLayer->image=CloneImage(image,outLayer->width, outLayer->height,MagickTrue,
     &image->exception);
  if (outLayer->image == (Image *) NULL)
    return(MagickFalse);
  /* clear the image based on the layer opacity */
  outLayer->image->background_color.opacity=
    ScaleCharToQuantum((unsigned char) (255-outLayer->alpha));
  (void) SetImageBackgroundColor(outLayer->image);

  InitXCFImage(outLayer);

  /* set the compositing mode */
  outLayer->image->compose = GIMPBlendModeToCompositeOperator( outLayer->mode );
  if ( outLayer->visible == MagickFalse )
    {
      /* BOGUS: should really be separate member var! */
      outLayer->image->compose = NoCompositeOp;
    }

  /* read the hierarchy and layer mask offsets */
  hierarchy_offset = ReadBlobMSBLong(image);
  layer_mask_offset = ReadBlobMSBLong(image);

  /* read in the hierarchy */
  offset=SeekBlob(image, (MagickOffsetType) hierarchy_offset, SEEK_SET);
  if (offset != (MagickOffsetType) hierarchy_offset)
    (void) ThrowMagickException(&image->exception,GetMagickModule(),
      CorruptImageError,"InvalidImageHeader","`%s'",image->filename);
  if (load_hierarchy (image, inDocInfo, outLayer) == 0)
    return(MagickFalse);

  /* read in the layer mask */
  if (layer_mask_offset != 0)
    {
      offset=SeekBlob(image, (MagickOffsetType) layer_mask_offset, SEEK_SET);

#if 0  /* BOGUS: support layer masks! */
      layer_mask = xcf_load_layer_mask (info, gimage);
      if (layer_mask == 0)
  goto error;

      /* set the offsets of the layer_mask */
      GIMP_DRAWABLE (layer_mask)->offset_x = GIMP_DRAWABLE (layer)->offset_x;
      GIMP_DRAWABLE (layer_mask)->offset_y = GIMP_DRAWABLE (layer)->offset_y;

      gimp_layer_add_mask (layer, layer_mask, MagickFalse);

      layer->mask->apply_mask = apply_mask;
      layer->mask->edit_mask  = edit_mask;
      layer->mask->show_mask  = show_mask;
#endif
  }

  /* attach the floating selection... */
#if 0  /* BOGUS: we may need to read this, even if we don't support it! */
  if (add_floating_sel)
    {
      GimpLayer *floating_sel;

      floating_sel = info->floating_sel;
      floating_sel_attach (floating_sel, GIMP_DRAWABLE (layer));
    }
#endif

  return MagickTrue;
}

Project: ImageMagick

ModuleExport size_t RegisterXCFImage(void)
{
  MagickInfo
    *entry;

  entry=SetMagickInfo("XCF");
  entry->decoder=(DecodeImageHandler *) ReadXCFImage;
  entry->magick=(IsImageFormatHandler *) IsXCF;
  entry->description=ConstantString("GIMP image");
  entry->module=ConstantString("XCF");
  entry->seekable_stream=MagickTrue;
  (void) RegisterMagickInfo(entry);
  return(MagickImageCoderSignature);
}

Project: ImageMagick
File: xcf.c

ModuleExport void UnregisterXCFImage(void)
{
  (void) UnregisterMagickInfo("XCF");
}

Project: ImageMagick

static MagickBooleanType load_hierarchy(Image *image,XCFDocInfo *inDocInfo,
   XCFLayerInfo *inLayer)
{
  MagickOffsetType
    saved_pos,
    offset,
    junk;

  size_t
    width,
    height,
    bytes_per_pixel;

  width=ReadBlobMSBLong(image);
  (void) width;
  height=ReadBlobMSBLong(image);
  (void) height;
  bytes_per_pixel=inDocInfo->bytes_per_pixel=ReadBlobMSBLong(image);
  (void) bytes_per_pixel;

  /* load in the levels...we make sure that the number of levels
   *  calculated when the TileManager was created is the same
   *  as the number of levels found in the file.
   */
  offset=(MagickOffsetType) ReadBlobMSBLong(image);  /* top level */

  /* discard offsets for layers below first, if any.
   */
  do
  {
    junk=(MagickOffsetType) ReadBlobMSBLong(image);
  }
  while (junk != 0);

  /* save the current position as it is where the
   *  next level offset is stored.
   */
  saved_pos=TellBlob(image);

  /* seek to the level offset */
  if (SeekBlob(image, offset, SEEK_SET) != offset)
    ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile",
      image->filename);

  /* read in the level */
  if (load_level (image, inDocInfo, inLayer) == 0)
    return(MagickFalse);
  /* restore the saved position so we'll be ready to
   *  read the next offset.
   */
  offset=SeekBlob(image, saved_pos, SEEK_SET);
  return(MagickTrue);
}

Project: ImageMagick

static MagickBooleanType load_level(Image *image,XCFDocInfo *inDocInfo,
  XCFLayerInfo *inLayerInfo)
{
  ExceptionInfo
    *exception;

  int
    destLeft = 0,
    destTop = 0;

  Image*
    tile_image;

  MagickBooleanType
    status;

  MagickOffsetType
    saved_pos,
    offset,
    offset2;

  register ssize_t
    i;

  size_t
    width,
    height,
    ntiles,
    ntile_rows,
    ntile_cols,
    tile_image_width,
    tile_image_height;

  /* start reading the data */
  exception=inDocInfo->exception;
  width=ReadBlobMSBLong(image);
  height=ReadBlobMSBLong(image);

  /*
    Read in the first tile offset.  If it is '0', then this tile level is empty
    and we can simply return.
  */
  offset=(MagickOffsetType) ReadBlobMSBLong(image);
  if (offset == 0)
    return(MagickTrue);
  /*
    Initialize the reference for the in-memory tile-compression.
  */
  ntile_rows=(height+TILE_HEIGHT-1)/TILE_HEIGHT;
  ntile_cols=(width+TILE_WIDTH-1)/TILE_WIDTH;
  ntiles=ntile_rows*ntile_cols;
  for (i = 0; i < (ssize_t) ntiles; i++)
  {
    status=MagickFalse;
    if (offset == 0)
      ThrowBinaryException(CorruptImageError,"NotEnoughTiles",image->filename);
    /* save the current position as it is where the
     *  next tile offset is stored.
     */
    saved_pos=TellBlob(image);
    /* read in the offset of the next tile so we can calculate the amount
       of data needed for this tile*/
    offset2=(MagickOffsetType)ReadBlobMSBLong(image);
    /* if the offset is 0 then we need to read in the maximum possible
       allowing for negative compression */
    if (offset2 == 0)
      offset2=(MagickOffsetType) (offset + TILE_WIDTH * TILE_WIDTH * 4* 1.5);
    /* seek to the tile offset */
    if (SeekBlob(image, offset, SEEK_SET) != offset)
      ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile",
        image->filename);

      /* allocate the image for the tile
        NOTE: the last tile in a row or column may not be a full tile!
      */
      tile_image_width=(size_t) (destLeft == (int) ntile_cols-1 ?
        (int) width % TILE_WIDTH : TILE_WIDTH);
      if (tile_image_width == 0)
        tile_image_width=TILE_WIDTH;
      tile_image_height = (size_t) (destTop == (int) ntile_rows-1 ?
        (int) height % TILE_HEIGHT : TILE_HEIGHT);
      if (tile_image_height == 0)
        tile_image_height=TILE_HEIGHT;
      tile_image=CloneImage(inLayerInfo->image,tile_image_width,
        tile_image_height,MagickTrue,exception);

      /* read in the tile */
      switch (inDocInfo->compression)
      {
        case COMPRESS_NONE:
          if (load_tile(image,tile_image,inDocInfo,inLayerInfo,(size_t) (offset2-offset)) == 0)
            status=MagickTrue;
          break;
        case COMPRESS_RLE:
          if (load_tile_rle (image,tile_image,inDocInfo,inLayerInfo,
              (int) (offset2-offset)) == 0)
            status=MagickTrue;
          break;
        case COMPRESS_ZLIB:
          ThrowBinaryException(CoderError,"ZipCompressNotSupported",
            image->filename)
        case COMPRESS_FRACTAL:
          ThrowBinaryException(CoderError,"FractalCompressNotSupported",
            image->filename)
      }

      /* composite the tile onto the layer's image, and then destroy it */
      (void) CompositeImage(inLayerInfo->image,CopyCompositeOp,tile_image,
        destLeft * TILE_WIDTH,destTop*TILE_HEIGHT);
      tile_image=DestroyImage(tile_image);

      /* adjust tile position */
      destLeft++;
      if (destLeft >= (int) ntile_cols)
        {
          destLeft = 0;
          destTop++;
        }
      if (status != MagickFalse)
        return(MagickFalse);
      /* restore the saved position so we'll be ready to
       *  read the next offset.
       */
      offset=SeekBlob(image, saved_pos, SEEK_SET);
      /* read in the offset of the next tile */
      offset=(MagickOffsetType) ReadBlobMSBLong(image);
    }
  if (offset != 0)
    ThrowBinaryException(CorruptImageError,"CorruptImage",image->filename)
  return(MagickTrue);
}

Project: ImageMagick

static MagickBooleanType load_tile(Image *image,Image *tile_image,
  XCFDocInfo *inDocInfo,XCFLayerInfo *inLayerInfo,size_t data_length)
{
  ExceptionInfo
    *exception;

  ssize_t
    y;

  register ssize_t
    x;

  register PixelPacket
    *q;

  size_t
    extent;

  ssize_t
    count;

  unsigned char
    *graydata;

  XCFPixelPacket
    *xcfdata,
    *xcfodata;

  extent=0;
  if (inDocInfo->image_type == GIMP_GRAY)
    extent=tile_image->columns*tile_image->rows*sizeof(*graydata);
  else
    if (inDocInfo->image_type == GIMP_RGB)
      extent=tile_image->columns*tile_image->rows*sizeof(*xcfdata);
  if (extent > data_length)
    ThrowBinaryException(CorruptImageError,"NotEnoughPixelData",
      image->filename);
  xcfdata=(XCFPixelPacket *) AcquireQuantumMemory(MagickMax(data_length,
    tile_image->columns*tile_image->rows),sizeof(*xcfdata));
  if (xcfdata == (XCFPixelPacket *) NULL)
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
      image->filename);
  xcfodata=xcfdata;
  graydata=(unsigned char *) xcfdata;  /* used by gray and indexed */
  count=ReadBlob(image,data_length,(unsigned char *) xcfdata);
  if (count != (ssize_t) data_length)
    ThrowBinaryException(CorruptImageError,"NotEnoughPixelData",
      image->filename);
  exception=(&image->exception);
  for (y=0; y < (ssize_t) tile_image->rows; y++)
  {
    q=GetAuthenticPixels(tile_image,0,y,tile_image->columns,1,exception);
    if (q == (PixelPacket *) NULL)
      break;
    if (inDocInfo->image_type == GIMP_GRAY)
      {
        for (x=0; x < (ssize_t) tile_image->columns; x++)
        {
          SetPixelRed(q,ScaleCharToQuantum(*graydata));
          SetPixelGreen(q,GetPixelRed(q));
          SetPixelBlue(q,GetPixelRed(q));
          SetPixelAlpha(q,ScaleCharToQuantum((unsigned char)
            inLayerInfo->alpha));
          graydata++;
          q++;
        }
      }
    else
      if (inDocInfo->image_type == GIMP_RGB)
        {
          for (x=0; x < (ssize_t) tile_image->columns; x++)
          {
            SetPixelRed(q,ScaleCharToQuantum(xcfdata->red));
            SetPixelGreen(q,ScaleCharToQuantum(xcfdata->green));
            SetPixelBlue(q,ScaleCharToQuantum(xcfdata->blue));
            SetPixelAlpha(q,xcfdata->alpha == 255U ? TransparentOpacity :
              ScaleCharToQuantum((unsigned char) inLayerInfo->alpha));
            xcfdata++;
            q++;
          }
        }
     if (SyncAuthenticPixels(tile_image,exception) == MagickFalse)
       break;
  }
  xcfodata=(XCFPixelPacket *) RelinquishMagickMemory(xcfodata);
  return MagickTrue;
}

Project: ImageMagick

static MagickBooleanType load_tile_rle(Image *image,Image *tile_image,
  XCFDocInfo *inDocInfo,XCFLayerInfo *inLayerInfo,size_t data_length)
{
  ExceptionInfo
    *exception;

  MagickOffsetType
    size;

  Quantum
    alpha;

  register PixelPacket
    *q;

  size_t
    length;

  ssize_t
    bytes_per_pixel,
    count,
    i,
    j;

  unsigned char
    data,
    pixel,
    *xcfdata,
    *xcfodata,
    *xcfdatalimit;

  bytes_per_pixel=(ssize_t) inDocInfo->bytes_per_pixel;
  xcfdata=(unsigned char *) AcquireQuantumMemory(data_length,sizeof(*xcfdata));
  if (xcfdata == (unsigned char *) NULL)
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
      image->filename);
  xcfodata=xcfdata;
  count=ReadBlob(image, (size_t) data_length, xcfdata);
  xcfdatalimit = xcfodata+count-1;
  exception=(&image->exception);
  alpha=ScaleCharToQuantum((unsigned char) inLayerInfo->alpha);
  for (i=0; i < (ssize_t) bytes_per_pixel; i++)
  {
    q=GetAuthenticPixels(tile_image,0,0,tile_image->columns,tile_image->rows,
      exception);
    if (q == (PixelPacket *) NULL)
      continue;
    size=(MagickOffsetType) tile_image->rows*tile_image->columns;
    while (size > 0)
    {
      if (xcfdata > xcfdatalimit)
        goto bogus_rle;
      pixel=(*xcfdata++);
      length=(size_t) pixel;
      if (length >= 128)
        {
          length=255-(length-1);
          if (length == 128)
            {
              if (xcfdata >= xcfdatalimit)
                goto bogus_rle;
              length=(size_t) ((*xcfdata << 8) + xcfdata[1]);
              xcfdata+=2;
            }
          size-=length;
          if (size < 0)
            goto bogus_rle;
          if (&xcfdata[length-1] > xcfdatalimit)
            goto bogus_rle;
          while (length-- > 0)
          {
            data=(*xcfdata++);
            switch (i)
            {
              case 0:
              {
                SetPixelRed(q,ScaleCharToQuantum(data));
                if (inDocInfo->image_type == GIMP_GRAY)
                  {
                    SetPixelGreen(q,ScaleCharToQuantum(data));
                    SetPixelBlue(q,ScaleCharToQuantum(data));
                  }
                else
                  {
                    SetPixelGreen(q,GetPixelRed(q));
                    SetPixelBlue(q,GetPixelRed(q));
                  }
                SetPixelAlpha(q,alpha);
                break;
              }
              case 1:
              {
                if (inDocInfo->image_type == GIMP_GRAY)
                  SetPixelAlpha(q,ScaleCharToQuantum(data));
                else
                  SetPixelGreen(q,ScaleCharToQuantum(data));
                break;
              }
              case 2:
              {
                SetPixelBlue(q,ScaleCharToQuantum(data));
                break;
              }
              case 3:
              {
                SetPixelAlpha(q,ScaleCharToQuantum(data));
                break;
              }
            }
            q++;
          }
        }
      else
        {
          length+=1;
          if (length == 128)
            {
              if (xcfdata >= xcfdatalimit)
                goto bogus_rle;
              length=(size_t) ((*xcfdata << 8) + xcfdata[1]);
              xcfdata+=2;
            }
          size-=length;
          if (size < 0)
            goto bogus_rle;
          if (xcfdata > xcfdatalimit)
            goto bogus_rle;
          pixel=(*xcfdata++);
          for (j=0; j < (ssize_t) length; j++)
          {
            data=pixel;
            switch (i)
            {
              case 0:
              {
                SetPixelRed(q,ScaleCharToQuantum(data));
                if (inDocInfo->image_type == GIMP_GRAY)
                  {
                    SetPixelGreen(q,ScaleCharToQuantum(data));
                    SetPixelBlue(q,ScaleCharToQuantum(data));
                  }
                else
                  {
                    SetPixelGreen(q,GetPixelRed(q));
                    SetPixelBlue(q,GetPixelRed(q));
                  }
                SetPixelAlpha(q,alpha);
                break;
              }
              case 1:
              {
                if (inDocInfo->image_type == GIMP_GRAY)
                  SetPixelAlpha(q,ScaleCharToQuantum(data));
                else
                  SetPixelGreen(q,ScaleCharToQuantum(data));
                break;
              }
              case 2:
              {
                SetPixelBlue(q,ScaleCharToQuantum(data));
                break;
              }
              case 3:
              {
                SetPixelAlpha(q,ScaleCharToQuantum(data));
                break;
              }
            }
            q++;
          }
        }
    }
    if (SyncAuthenticPixels(tile_image,exception) == MagickFalse)
      break;
  }
  xcfodata=(unsigned char *) RelinquishMagickMemory(xcfodata);
  return(MagickTrue);

  bogus_rle:
    if (xcfodata != (unsigned char *) NULL)
      xcfodata=(unsigned char *) RelinquishMagickMemory(xcfodata);
  return(MagickFalse);
}