Commit message: PSD: Use Safe::add for preventing overflows in PSD files
CVE: CVE-2018-19107
CWE: CWE-125
Vulnerability description: In Exiv2 0.26, Exiv2::IptcParser::decode in iptc.cpp (called from psdimage.cpp in the PSD image reader) may suffer from a denial of service (heap-based buffer over-read) caused by an integer overflow via a crafted PSD image file.

Project: exiv2
File: psdimage.cpp

    uint32_t PsdImage::writeExifData(const ExifData& exifData, BasicIo& out)
    {
        uint32_t resLength = 0;
        byte buf[8];

        if (exifData.count() > 0) {
            Blob blob;
            ByteOrder bo = byteOrder();
            if (bo == invalidByteOrder) {
                bo = littleEndian;
                setByteOrder(bo);
            }
            ExifParser::encode(blob, bo, exifData);

            if (blob.size() > 0) {
#ifdef DEBUG
                std::cerr << std::hex << "write: resourceId: " << kPhotoshopResourceID_ExifInfo << "\n";
                std::cerr << std::dec << "Writing ExifInfo: size: " << blob.size() << "\n";
#endif
                if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_[0]), 4) != 4) throw Error(kerImageWriteFailed);
                us2Data(buf, kPhotoshopResourceID_ExifInfo, bigEndian);
                if (out.write(buf, 2) != 2) throw Error(kerImageWriteFailed);
                us2Data(buf, 0, bigEndian);                      // NULL resource name
                if (out.write(buf, 2) != 2) throw Error(kerImageWriteFailed);
                ul2Data(buf, static_cast<uint32_t>(blob.size()), bigEndian);
                if (out.write(buf, 4) != 4) throw Error(kerImageWriteFailed);
                // Write encoded Exif data
                if (out.write(&blob[0], static_cast<long>(blob.size())) != static_cast<long>(blob.size())) throw Error(kerImageWriteFailed);
                resLength += static_cast<long>(blob.size()) + 12;
                if (blob.size() & 1)    // even padding
                {
                    buf[0] = 0;
                    if (out.write(buf, 1) != 1) throw Error(kerImageWriteFailed);
                    resLength++;
                }
            }
        }
        return resLength;
    } // PsdImage::writeExifData

Project: exiv2
File: psdimage.cpp

    uint32_t PsdImage::writeIptcData(const IptcData& iptcData, BasicIo& out) const
    {
        uint32_t resLength = 0;
        byte buf[8];

        if (iptcData.count() > 0) {
            DataBuf rawIptc = IptcParser::encode(iptcData);
            if (rawIptc.size_ > 0) {
#ifdef DEBUG
                std::cerr << std::hex << "write: resourceId: " << kPhotoshopResourceID_IPTC_NAA << "\n";
                std::cerr << std::dec << "Writing IPTC_NAA: size: " << rawIptc.size_ << "\n";
#endif
                if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_[0]), 4) != 4) throw Error(kerImageWriteFailed);
                us2Data(buf, kPhotoshopResourceID_IPTC_NAA, bigEndian);
                if (out.write(buf, 2) != 2) throw Error(kerImageWriteFailed);
                us2Data(buf, 0, bigEndian);                      // NULL resource name
                if (out.write(buf, 2) != 2) throw Error(kerImageWriteFailed);
                ul2Data(buf, rawIptc.size_, bigEndian);
                if (out.write(buf, 4) != 4) throw Error(kerImageWriteFailed);
                // Write encoded Iptc data
                if (out.write(rawIptc.pData_, rawIptc.size_) != rawIptc.size_) throw Error(kerImageWriteFailed);
                resLength += rawIptc.size_ + 12;
                if (rawIptc.size_ & 1)    // even padding
                {
                    buf[0] = 0;
                    if (out.write(buf, 1) != 1) throw Error(kerImageWriteFailed);
                    resLength++;
                }
            }
        }
        return resLength;
    } // PsdImage::writeIptcData

Project: exiv2
File: psdimage.cpp

    uint32_t PsdImage::writeXmpData(const XmpData& xmpData, BasicIo& out) const
    {
        std::string xmpPacket;
        uint32_t resLength = 0;
        byte buf[8];

#ifdef DEBUG
        std::cerr << "writeXmpFromPacket(): " << writeXmpFromPacket() << "\n";
#endif
//        writeXmpFromPacket(true);
        if (writeXmpFromPacket() == false) {
            if (XmpParser::encode(xmpPacket, xmpData) > 1) {
#ifndef SUPPRESS_WARNINGS
                EXV_ERROR << "Failed to encode XMP metadata.\n";
#endif
            }
        }

        if (xmpPacket.size() > 0) {
#ifdef DEBUG
            std::cerr << std::hex << "write: resourceId: " << kPhotoshopResourceID_XMPPacket << "\n";
            std::cerr << std::dec << "Writing XMPPacket: size: " << xmpPacket.size() << "\n";
#endif
            if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_[0]), 4) != 4) throw Error(kerImageWriteFailed);
            us2Data(buf, kPhotoshopResourceID_XMPPacket, bigEndian);
            if (out.write(buf, 2) != 2) throw Error(kerImageWriteFailed);
            us2Data(buf, 0, bigEndian);                      // NULL resource name
            if (out.write(buf, 2) != 2) throw Error(kerImageWriteFailed);
            ul2Data(buf, static_cast<uint32_t>(xmpPacket.size()), bigEndian);
            if (out.write(buf, 4) != 4) throw Error(kerImageWriteFailed);
            // Write XMPPacket
            if (out.write(reinterpret_cast<const byte*>(xmpPacket.data()), static_cast<long>(xmpPacket.size()))
                != static_cast<long>(xmpPacket.size())) throw Error(kerImageWriteFailed);
            if (out.error()) throw Error(kerImageWriteFailed);
            resLength += static_cast<uint32_t>(xmpPacket.size()) + 12;
            if (xmpPacket.size() & 1)    // even padding
            {
                buf[0] = 0;
                if (out.write(buf, 1) != 1) throw Error(kerImageWriteFailed);
                resLength++;
            }
        }
        return resLength;
    } // PsdImage::writeXmpData

Project: exiv2
File: psdimage.cpp

    PsdImage::PsdImage(BasicIo::AutoPtr io)
        : Image(ImageType::psd, mdExif | mdIptc | mdXmp, io)
    {
    } // PsdImage::PsdImage

Project: exiv2
File: psdimage.cpp

    void PsdImage::readResourceBlock(uint16_t resourceId, uint32_t resourceSize)
    {
        switch(resourceId)
        {
            case kPhotoshopResourceID_IPTC_NAA:
            {
                DataBuf rawIPTC(resourceSize);
                io_->read(rawIPTC.pData_, rawIPTC.size_);
                if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
                if (IptcParser::decode(iptcData_, rawIPTC.pData_, rawIPTC.size_)) {
#ifndef SUPPRESS_WARNINGS
                    EXV_WARNING << "Failed to decode IPTC metadata.\n";
#endif
                    iptcData_.clear();
                }
                break;
            }

            case kPhotoshopResourceID_ExifInfo:
            {
                DataBuf rawExif(resourceSize);
                io_->read(rawExif.pData_, rawExif.size_);
                if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
                ByteOrder bo = ExifParser::decode(exifData_, rawExif.pData_, rawExif.size_);
                setByteOrder(bo);
                if (rawExif.size_ > 0 && byteOrder() == invalidByteOrder) {
#ifndef SUPPRESS_WARNINGS
                    EXV_WARNING << "Failed to decode Exif metadata.\n";
#endif
                    exifData_.clear();
                }
                break;
            }

            case kPhotoshopResourceID_XMPPacket:
            {
                DataBuf xmpPacket(resourceSize);
                io_->read(xmpPacket.pData_, xmpPacket.size_);
                if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
                xmpPacket_.assign(reinterpret_cast<char *>(xmpPacket.pData_), xmpPacket.size_);
                if (xmpPacket_.size() > 0 && XmpParser::decode(xmpData_, xmpPacket_)) {
#ifndef SUPPRESS_WARNINGS
                    EXV_WARNING << "Failed to decode XMP metadata.\n";
#endif
                }
                break;
            }

            // - PS 4.0 preview data is fetched from ThumbnailResource
            // - PS >= 5.0 preview data is fetched from ThumbnailResource2
            case kPhotoshopResourceID_ThumbnailResource:
            case kPhotoshopResourceID_ThumbnailResource2:
            {
                /*
                  Photoshop thumbnail resource header

                  offset  length    name            description
                  ======  ========  ====            ===========
                   0      4 bytes   format          = 1 (kJpegRGB). Also supports kRawRGB (0).
                   4      4 bytes   width           Width of thumbnail in pixels.
                   8      4 bytes   height          Height of thumbnail in pixels.
                  12      4 bytes   widthbytes      Padded row bytes as (width * bitspixel + 31) / 32 * 4.
                  16      4 bytes   size            Total size as widthbytes * height * planes
                  20      4 bytes   compressedsize  Size after compression. Used for consistentcy check.
                  24      2 bytes   bitspixel       = 24. Bits per pixel.
                  26      2 bytes   planes          = 1. Number of planes.
                  28      variable  data            JFIF data in RGB format.
                                                    Note: For resource ID 1033 the data is in BGR format.
                */
                byte buf[28];
                if (io_->read(buf, 28) != 28)
                {
                    throw Error(kerNotAnImage, "Photoshop");
                }
                NativePreview nativePreview;
                nativePreview.position_ = io_->tell();
                nativePreview.size_ = getLong(buf + 20, bigEndian);    // compressedsize
                nativePreview.width_ = getLong(buf + 4, bigEndian);
                nativePreview.height_ = getLong(buf + 8, bigEndian);
                const uint32_t format = getLong(buf + 0, bigEndian);

                if (nativePreview.size_ > 0 && nativePreview.position_ >= 0) {
                    io_->seek(static_cast<long>(nativePreview.size_), BasicIo::cur);
                    if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);

                    if (format == 1) {
                        nativePreview.filter_ = "";
                        nativePreview.mimeType_ = "image/jpeg";
                        nativePreviews_.push_back(nativePreview);
                    } else {
                        // unsupported format of native preview
                    }
                }
                break;
            }

            default:
            {
                break;
            }
        }
    } // PsdImage::readResourceBlock

Project: exiv2
File: psdimage.cpp

    void PsdImage::writeMetadata()
    {
        if (io_->open() != 0)
        {
            throw Error(kerDataSourceOpenFailed, io_->path(), strError());
        }
        IoCloser closer(*io_);
        BasicIo::AutoPtr tempIo(new MemIo);
        assert (tempIo.get() != 0);

        doWriteMetadata(*tempIo); // may throw
        io_->close();
        io_->transfer(*tempIo); // may throw

    } // PsdImage::writeMetadata

Project: exiv2
File: psdimage.cpp

    void PsdImage::doWriteMetadata(BasicIo& outIo)
    {
        if (!io_->isopen()) throw Error(kerInputDataReadFailed);
        if (!outIo.isopen()) throw Error(kerImageWriteFailed);

#ifdef DEBUG
        std::cout << "Exiv2::PsdImage::doWriteMetadata: Writing PSD file " << io_->path() << "\n";
        std::cout << "Exiv2::PsdImage::doWriteMetadata: tmp file created " << outIo.path() << "\n";
#endif

        // Ensure that this is the correct image type
        if (!isPsdType(*io_, true)) {
            if (io_->error() || io_->eof()) throw Error(kerInputDataReadFailed);
            throw Error(kerNoImageInInputData);
        }

        io_->seek(0, BasicIo::beg);    // rewind

        DataBuf lbuf(4096);
        byte buf[8];

        // Get Photoshop header from original file
        byte psd_head[26];
        if (io_->read(psd_head, 26) != 26) throw Error(kerNotAnImage, "Photoshop");

        // Write Photoshop header data out to new PSD file
        if (outIo.write(psd_head, 26) != 26) throw Error(kerImageWriteFailed);

        // Read colorDataLength from original PSD
        if (io_->read(buf, 4) != 4) throw Error(kerNotAnImage, "Photoshop");

        uint32_t colorDataLength = getULong(buf, bigEndian);

        // Write colorDataLength
        ul2Data(buf, colorDataLength, bigEndian);
        if (outIo.write(buf, 4) != 4) throw Error(kerImageWriteFailed);
#ifdef DEBUG
        std::cerr << std::dec << "colorDataLength: " << colorDataLength << "\n";
#endif
        // Copy colorData
        uint32_t readTotal = 0;
        long toRead = 0;
        while (readTotal < colorDataLength) {
            toRead =   static_cast<long>(colorDataLength - readTotal) < lbuf.size_
                     ? static_cast<long>(colorDataLength - readTotal) : lbuf.size_;
            if (io_->read(lbuf.pData_, toRead) != toRead) throw Error(kerNotAnImage, "Photoshop");
            readTotal += toRead;
            if (outIo.write(lbuf.pData_, toRead) != toRead) throw Error(kerImageWriteFailed);
        }
        if (outIo.error()) throw Error(kerImageWriteFailed);

        uint32_t resLenOffset = io_->tell();  // remember for later update

        // Read length of all resource blocks from original PSD
        if (io_->read(buf, 4) != 4) throw Error(kerNotAnImage, "Photoshop");

        uint32_t oldResLength = getULong(buf, bigEndian);
        uint32_t newResLength = 0;

        // Write oldResLength (will be updated later)
        ul2Data(buf, oldResLength, bigEndian);
        if (outIo.write(buf, 4) != 4) throw Error(kerImageWriteFailed);

#ifdef DEBUG
        std::cerr << std::dec << "oldResLength: " << oldResLength << "\n";
#endif

        // Iterate over original resource blocks.
        // Replace or insert IPTC, EXIF and XMP
        // Original resource blocks assumed to be sorted ASC

        bool iptcDone = false;
        bool exifDone = false;
        bool xmpDone = false;
        while (oldResLength > 0) {
            if (io_->read(buf, 8) != 8) throw Error(kerNotAnImage, "Photoshop");

            // read resource type and ID
            uint32_t resourceType = getULong(buf, bigEndian);

            if (!Photoshop::isIrb(buf, 4))
            {
                throw Error(kerNotAnImage, "Photoshop"); // bad resource type
            }
            uint16_t resourceId = getUShort(buf + 4, bigEndian);
            uint32_t resourceNameLength = buf[6];
            uint32_t adjResourceNameLen = resourceNameLength & ~1;
            unsigned char resourceNameFirstChar = buf[7];

            // read rest of resource name, plus any padding
            DataBuf resName(256);
            if (   io_->read(resName.pData_, adjResourceNameLen)
                != static_cast<long>(adjResourceNameLen)) throw Error(kerNotAnImage, "Photoshop");

            // read resource size (actual length w/o padding!)
            if (io_->read(buf, 4) != 4) throw Error(kerNotAnImage, "Photoshop");

            uint32_t resourceSize = getULong(buf, bigEndian);
            uint32_t pResourceSize = (resourceSize + 1) & ~1;    // padded resource size
            uint32_t curOffset = io_->tell();

            // Write IPTC_NAA resource block
            if ((resourceId == kPhotoshopResourceID_IPTC_NAA  ||
                 resourceId >  kPhotoshopResourceID_IPTC_NAA) && iptcDone == false) {
                newResLength += writeIptcData(iptcData_, outIo);
                iptcDone = true;
            }

            // Write ExifInfo resource block
            else if ((resourceId == kPhotoshopResourceID_ExifInfo  ||
                      resourceId >  kPhotoshopResourceID_ExifInfo) && exifDone == false) {
                newResLength += writeExifData(exifData_, outIo);
                exifDone = true;
            }

            // Write XMPpacket resource block
            else if ((resourceId == kPhotoshopResourceID_XMPPacket  ||
                      resourceId >  kPhotoshopResourceID_XMPPacket) && xmpDone == false) {
                newResLength += writeXmpData(xmpData_, outIo);
                xmpDone = true;
            }

            // Copy all other resource blocks
            if (   resourceId != kPhotoshopResourceID_IPTC_NAA
                && resourceId != kPhotoshopResourceID_ExifInfo
                && resourceId != kPhotoshopResourceID_XMPPacket) {
#ifdef DEBUG
                std::cerr << std::hex << "copy : resourceType: " << resourceType << "\n";
                std::cerr << std::hex << "copy : resourceId: " << resourceId << "\n";
                std::cerr << std::dec;
#endif
                // Copy resource block to new PSD file
                ul2Data(buf, resourceType, bigEndian);
                if (outIo.write(buf, 4) != 4) throw Error(kerImageWriteFailed);
                us2Data(buf, resourceId, bigEndian);
                if (outIo.write(buf, 2) != 2) throw Error(kerImageWriteFailed);
                // Write resource name as Pascal string
                buf[0] = resourceNameLength & 0x00ff;
                if (outIo.write(buf, 1) != 1) throw Error(kerImageWriteFailed);
                buf[0] = resourceNameFirstChar;
                if (outIo.write(buf, 1) != 1) throw Error(kerImageWriteFailed);
                if (   outIo.write(resName.pData_, adjResourceNameLen)
                    != static_cast<long>(adjResourceNameLen)) throw Error(kerImageWriteFailed);
                ul2Data(buf, resourceSize, bigEndian);
                if (outIo.write(buf, 4) != 4) throw Error(kerImageWriteFailed);

                readTotal = 0;
                toRead = 0;
                while (readTotal < pResourceSize) {
                    toRead =   static_cast<long>(pResourceSize - readTotal) < lbuf.size_
                             ? static_cast<long>(pResourceSize - readTotal) : lbuf.size_;
                    if (io_->read(lbuf.pData_, toRead) != toRead) {
                        throw Error(kerNotAnImage, "Photoshop");
                    }
                    readTotal += toRead;
                    if (outIo.write(lbuf.pData_, toRead) != toRead) throw Error(kerImageWriteFailed);
                }
                if (outIo.error()) throw Error(kerImageWriteFailed);
                newResLength += pResourceSize + adjResourceNameLen + 12;
            }

            io_->seek(curOffset + pResourceSize, BasicIo::beg);
            oldResLength -= (12 + adjResourceNameLen + pResourceSize);
        }

        // Append IPTC_NAA resource block, if not yet written
        if (iptcDone == false) {
            newResLength += writeIptcData(iptcData_, outIo);
            iptcDone = true;
        }

        // Append ExifInfo resource block, if not yet written
        if (exifDone == false) {
            newResLength += writeExifData(exifData_, outIo);
            exifDone = true;
        }

        // Append XmpPacket resource block, if not yet written
        if (xmpDone == false) {
            newResLength += writeXmpData(xmpData_, outIo);
            xmpDone = true;
        }

        // Populate the fake data, only make sense for remoteio, httpio and sshio.
        // it avoids allocating memory for parts of the file that contain image-date.
        io_->populateFakeData();

        // Copy remaining data
        long readSize = 0;
        while ((readSize=io_->read(lbuf.pData_, lbuf.size_))) {
            if (outIo.write(lbuf.pData_, readSize) != readSize) throw Error(kerImageWriteFailed);
        }
        if (outIo.error()) throw Error(kerImageWriteFailed);

        // Update length of resources
#ifdef DEBUG
        std::cerr << "newResLength: " << newResLength << "\n";
#endif
        outIo.seek(resLenOffset, BasicIo::beg);
        ul2Data(buf, newResLength, bigEndian);
        if (outIo.write(buf, 4) != 4) throw Error(kerImageWriteFailed);

    } // PsdImage::doWriteMetadata

Project: exiv2
File: psdimage.cpp

    Image::AutoPtr newPsdInstance(BasicIo::AutoPtr io, bool /*create*/)
    {
        Image::AutoPtr image(new PsdImage(io));
        if (!image->good())
        {
            image.reset();
        }
        return image;
    }