Commit message: https://github.com/ImageMagick/ImageMagick/issues/1451
CVE: CVE-2019-7395
CWE: CWE-399
Vulnerability description: In ImageMagick before 7.0.8-25, a memory leak exists in WritePSDChannel in coders/psd.c.

Project: ImageMagick
File: psd.c

static MagickBooleanType ReadPSDLayersInternal(Image *image,
  const ImageInfo *image_info,const PSDInfo *psd_info,
  const MagickBooleanType skip_layers,ExceptionInfo *exception)
{
  char
    type[4];

  LayerInfo
    *layer_info;

  MagickSizeType
    size;

  MagickBooleanType
    status;

  register ssize_t
    i;

  ssize_t
    count,
    j,
    number_layers;

  size=GetPSDSize(psd_info,image);
  if (size == 0)
    {
      /*
        Skip layers & masks.
      */
      (void) ReadBlobLong(image);
      count=ReadBlob(image,4,(unsigned char *) type);
      if (count == 4)
        ReversePSDString(image,type,(size_t) count);
      if ((count != 4) || (LocaleNCompare(type,"8BIM",4) != 0))
        return(MagickTrue);
      else
        {
          count=ReadBlob(image,4,(unsigned char *) type);
          if (count == 4)
            ReversePSDString(image,type,4);
          if ((count == 4) && ((LocaleNCompare(type,"Lr16",4) == 0) ||
              (LocaleNCompare(type,"Lr32",4) == 0)))
            size=GetPSDSize(psd_info,image);
          else
            return(MagickTrue);
        }
    }
  if (size == 0)
    return(MagickTrue);

  layer_info=(LayerInfo *) NULL;
  number_layers=(ssize_t) ReadBlobSignedShort(image);

  if (number_layers < 0)
    {
      /*
        The first alpha channel in the merged result contains the
        transparency data for the merged result.
      */
      number_layers=MagickAbsoluteValue(number_layers);
      if (image->debug != MagickFalse)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "  negative layer count corrected for");
      image->alpha_trait=BlendPixelTrait;
    }

  /*
    We only need to know if the image has an alpha channel
  */
  if (skip_layers != MagickFalse)
    return(MagickTrue);

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
      "  image contains %.20g layers",(double) number_layers);

  if (number_layers == 0)
    ThrowBinaryException(CorruptImageError,"InvalidNumberOfLayers",
      image->filename);

  layer_info=(LayerInfo *) AcquireQuantumMemory((size_t) number_layers,
    sizeof(*layer_info));
  if (layer_info == (LayerInfo *) NULL)
    {
      if (image->debug != MagickFalse)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "  allocation of LayerInfo failed");
      ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
        image->filename);
    }
  (void) memset(layer_info,0,(size_t) number_layers*sizeof(*layer_info));

  for (i=0; i < number_layers; i++)
  {
    ssize_t
      x,
      y;

    if (image->debug != MagickFalse)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
        "  reading layer #%.20g",(double) i+1);
    layer_info[i].page.y=(ssize_t) ReadBlobSignedLong(image);
    layer_info[i].page.x=(ssize_t) ReadBlobSignedLong(image);
    y=(ssize_t) ReadBlobSignedLong(image);
    x=(ssize_t) ReadBlobSignedLong(image);
    layer_info[i].page.width=(size_t) (x-layer_info[i].page.x);
    layer_info[i].page.height=(size_t) (y-layer_info[i].page.y);
    layer_info[i].channels=ReadBlobShort(image);
    if (layer_info[i].channels > MaxPSDChannels)
      {
        layer_info=DestroyLayerInfo(layer_info,number_layers);
        ThrowBinaryException(CorruptImageError,"MaximumChannelsExceeded",
          image->filename);
      }
    if (image->debug != MagickFalse)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
        "    offset(%.20g,%.20g), size(%.20g,%.20g), channels=%.20g",
        (double) layer_info[i].page.x,(double) layer_info[i].page.y,
        (double) layer_info[i].page.height,(double)
        layer_info[i].page.width,(double) layer_info[i].channels);
    for (j=0; j < (ssize_t) layer_info[i].channels; j++)
    {
      layer_info[i].channel_info[j].type=(short) ReadBlobShort(image);
      if ((layer_info[i].channel_info[j].type < -4) ||
          (layer_info[i].channel_info[j].type > 4))
        {
          layer_info=DestroyLayerInfo(layer_info,number_layers);
          ThrowBinaryException(CorruptImageError,"NoSuchImageChannel",
            image->filename);
        }
      layer_info[i].channel_info[j].size=(size_t) GetPSDSize(psd_info,
        image);
      if (image->debug != MagickFalse)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "    channel[%.20g]: type=%.20g, size=%.20g",(double) j,
          (double) layer_info[i].channel_info[j].type,
          (double) layer_info[i].channel_info[j].size);
    }
    if (CheckPSDChannels(psd_info,&layer_info[i]) == MagickFalse)
      {
        layer_info=DestroyLayerInfo(layer_info,number_layers);
        ThrowBinaryException(CorruptImageError,"ImproperImageHeader",
          image->filename);
      }
    count=ReadBlob(image,4,(unsigned char *) type);
    if (count == 4)
      ReversePSDString(image,type,4);
    if ((count != 4) || (LocaleNCompare(type,"8BIM",4) != 0))
      {
        if (image->debug != MagickFalse)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "  layer type was %.4s instead of 8BIM", type);
        layer_info=DestroyLayerInfo(layer_info,number_layers);
        ThrowBinaryException(CorruptImageError,"ImproperImageHeader",
          image->filename);
      }
    count=ReadBlob(image,4,(unsigned char *) layer_info[i].blendkey);
    if (count != 4)
      {
        layer_info=DestroyLayerInfo(layer_info,number_layers);
        ThrowBinaryException(CorruptImageError,"ImproperImageHeader",
          image->filename);
      }
    ReversePSDString(image,layer_info[i].blendkey,4);
    layer_info[i].opacity=(Quantum) ScaleCharToQuantum((unsigned char)
      ReadBlobByte(image));
    layer_info[i].clipping=(unsigned char) ReadBlobByte(image);
    layer_info[i].flags=(unsigned char) ReadBlobByte(image);
    layer_info[i].visible=!(layer_info[i].flags & 0x02);
    if (image->debug != MagickFalse)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
        "   blend=%.4s, opacity=%.20g, clipping=%s, flags=%d, visible=%s",
        layer_info[i].blendkey,(double) layer_info[i].opacity,
        layer_info[i].clipping ? "true" : "false",layer_info[i].flags,
        layer_info[i].visible ? "true" : "false");
    (void) ReadBlobByte(image);  /* filler */

    size=ReadBlobLong(image);
    if (size != 0)
      {
        MagickSizeType
          combined_length,
          length;

        if (image->debug != MagickFalse)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "    layer contains additional info");
        length=ReadBlobLong(image);
        combined_length=length+4;
        if (length != 0)
          {
            /*
              Layer mask info.
            */
            layer_info[i].mask.page.y=(ssize_t) ReadBlobSignedLong(image);
            layer_info[i].mask.page.x=(ssize_t) ReadBlobSignedLong(image);
            layer_info[i].mask.page.height=(size_t)
              (ReadBlobSignedLong(image)-layer_info[i].mask.page.y);
            layer_info[i].mask.page.width=(size_t) (
              ReadBlobSignedLong(image)-layer_info[i].mask.page.x);
            layer_info[i].mask.background=(unsigned char) ReadBlobByte(
              image);
            layer_info[i].mask.flags=(unsigned char) ReadBlobByte(image);
            if (!(layer_info[i].mask.flags & 0x01))
              {
                layer_info[i].mask.page.y=layer_info[i].mask.page.y-
                  layer_info[i].page.y;
                layer_info[i].mask.page.x=layer_info[i].mask.page.x-
                  layer_info[i].page.x;
              }
            if (image->debug != MagickFalse)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "      layer mask: offset(%.20g,%.20g), size(%.20g,%.20g), length=%.20g",
                (double) layer_info[i].mask.page.x,(double)
                layer_info[i].mask.page.y,(double)
                layer_info[i].mask.page.width,(double)
                layer_info[i].mask.page.height,(double) ((MagickOffsetType)
                length)-18);
            /*
              Skip over the rest of the layer mask information.
            */
            if (DiscardBlobBytes(image,(MagickSizeType) (length-18)) == MagickFalse)
              {
                layer_info=DestroyLayerInfo(layer_info,number_layers);
                ThrowBinaryException(CorruptImageError,
                  "UnexpectedEndOfFile",image->filename);
              }
          }
        length=ReadBlobLong(image);
        combined_length+=length+4;
        if (length != 0)
          {
            /*
              Layer blending ranges info.
            */
            if (image->debug != MagickFalse)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "      layer blending ranges: length=%.20g",(double)
                ((MagickOffsetType) length));
            if (DiscardBlobBytes(image,length) == MagickFalse)
              {
                layer_info=DestroyLayerInfo(layer_info,number_layers);
                ThrowBinaryException(CorruptImageError,
                  "UnexpectedEndOfFile",image->filename);
              }
          }
        /*
          Layer name.
        */
        length=(MagickSizeType) (unsigned char) ReadBlobByte(image);
        combined_length+=length+1;
        if (length > 0)
          (void) ReadBlob(image,(size_t) length++,layer_info[i].name);
        layer_info[i].name[length]='\0';
        if (image->debug != MagickFalse)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "      layer name: %s",layer_info[i].name);
        if ((length % 4) != 0)
          {
            length=4-(length % 4);
            combined_length+=length;
            /* Skip over the padding of the layer name */
            if (DiscardBlobBytes(image,length) == MagickFalse)
              {
                layer_info=DestroyLayerInfo(layer_info,number_layers);
                ThrowBinaryException(CorruptImageError,
                  "UnexpectedEndOfFile",image->filename);
              }
          }
        length=(MagickSizeType) size-combined_length;
        if (length > 0)
          {
            unsigned char
              *info;

            if (length > GetBlobSize(image))
              {
                layer_info=DestroyLayerInfo(layer_info,number_layers);
                ThrowBinaryException(CorruptImageError,
                  "InsufficientImageDataInFile",image->filename);
              }
            layer_info[i].info=AcquireStringInfo((const size_t) length);
            info=GetStringInfoDatum(layer_info[i].info);
            (void) ReadBlob(image,(const size_t) length,info);
          }
      }
  }

  for (i=0; i < number_layers; i++)
  {
    if ((layer_info[i].page.width == 0) || (layer_info[i].page.height == 0))
      {
        if (image->debug != MagickFalse)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "      layer data is empty");
        if (layer_info[i].info != (StringInfo *) NULL)
          layer_info[i].info=DestroyStringInfo(layer_info[i].info);
        continue;
      }

    /*
      Allocate layered image.
    */
    layer_info[i].image=CloneImage(image,layer_info[i].page.width,
      layer_info[i].page.height,MagickFalse,exception);
    if (layer_info[i].image == (Image *) NULL)
      {
        layer_info=DestroyLayerInfo(layer_info,number_layers);
        if (image->debug != MagickFalse)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "  allocation of image for layer %.20g failed",(double) i);
        ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
          image->filename);
      }
    if (layer_info[i].info != (StringInfo *) NULL)
      {
        (void) SetImageProfile(layer_info[i].image,"psd:additional-info",
          layer_info[i].info,exception);
        layer_info[i].info=DestroyStringInfo(layer_info[i].info);
      }
  }
  if (image_info->ping != MagickFalse)
    {
      AttachPSDLayers(image,layer_info,number_layers);
      return(MagickTrue);
    }
  status=MagickTrue;
  for (i=0; i < number_layers; i++)
  {
    if ((layer_info[i].image == (Image *) NULL) ||
        (PSDSkipImage(image_info,i) != MagickFalse))
      {
        for (j=0; j < (ssize_t) layer_info[i].channels; j++)
        {
          if (DiscardBlobBytes(image,(MagickSizeType)
              layer_info[i].channel_info[j].size) == MagickFalse)
            {
              layer_info=DestroyLayerInfo(layer_info,number_layers);
              ThrowBinaryException(CorruptImageError,
                "UnexpectedEndOfFile",image->filename);
            }
        }
        continue;
      }

    if (image->debug != MagickFalse)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
        "  reading data for layer %.20g",(double) i);

    status=ReadPSDLayer(image,image_info,psd_info,&layer_info[i],
      exception);
    if (status == MagickFalse)
      break;

    status=SetImageProgress(image,LoadImagesTag,(MagickOffsetType) i,
      (MagickSizeType) number_layers);
    if (status == MagickFalse)
      break;
  }

  if (status != MagickFalse)
    AttachPSDLayers(image,layer_info,number_layers);
  else
    layer_info=DestroyLayerInfo(layer_info,number_layers);

  return(status);
}

Project: ImageMagick
File: psd.c

static inline void SetPSDPixel(Image *image,const size_t channels,
  const ssize_t type,const size_t packet_size,const Quantum pixel,Quantum *q,
  ExceptionInfo *exception)
{
  if (image->storage_class == PseudoClass)
    {
      PixelInfo
        *color;

      if (type == 0)
        {
          if (packet_size == 1)
            SetPixelIndex(image,ScaleQuantumToChar(pixel),q);
          else
            SetPixelIndex(image,ScaleQuantumToShort(pixel),q);
        }
      color=image->colormap+(ssize_t) ConstrainColormapIndex(image,
        (ssize_t) GetPixelIndex(image,q),exception);
      if ((type == 0) && (channels > 1))
        return;
      else
        color->alpha=(MagickRealType) pixel;
      SetPixelViaPixelInfo(image,color,q);
      return;
    }
  switch (type)
  {
    case -1:
    {
      SetPixelAlpha(image,pixel,q);
      break;
    }
    case -2:
    case 0:
    {
      SetPixelRed(image,pixel,q);
      break;
    }
    case -3:
    case 1:
    {
      SetPixelGreen(image,pixel,q);
      break;
    }
    case -4:
    case 2:
    {
      SetPixelBlue(image,pixel,q);
      break;
    }
    case 3:
    {
      if (image->colorspace == CMYKColorspace)
        SetPixelBlack(image,pixel,q);
      else
        if (image->alpha_trait != UndefinedPixelTrait)
          SetPixelAlpha(image,pixel,q);
      break;
    }
    case 4:
    {
      if ((IssRGBCompatibleColorspace(image->colorspace) != MagickFalse) &&
          (channels > 3))
        break;
      if (image->alpha_trait != UndefinedPixelTrait)
        SetPixelAlpha(image,pixel,q);
      break;
    }
  }
}