Commit message: Avoid negative integer overflow when `iccOffset > chunkLength`.

This fixes #790.

(cherry picked from commit 6fa2e31206127bd8bcac0269311f3775a8d6ea21)
CVE: CVE-2019-13109
CWE: CWE-190
Vulnerability description: An integer overflow in Exiv2 through 0.27.1 allows an attacker to cause a denial of service (SIGSEGV) via a crafted PNG image file, because PngImage::readMetadata mishandles a chunkLength - iccOffset subtraction.

Project: exiv2
File: pngimage.cpp

    void PngImage::printStructure(std::ostream& out, PrintStructureOption option, int depth)
    {
        if (io_->open() != 0) {
            throw Error(kerDataSourceOpenFailed, io_->path(), strError());
        }
        if (!isPngType(*io_, true)) {
            throw Error(kerNotAnImage, "PNG");
        }

        char chType[5];
        chType[0] = 0;
        chType[4] = 0;

        if (option == kpsBasic || option == kpsXMP || option == kpsIccProfile || option == kpsRecursive) {
            const std::string xmpKey = "XML:com.adobe.xmp";
            const std::string exifKey = "Raw profile type exif";
            const std::string app1Key = "Raw profile type APP1";
            const std::string iptcKey = "Raw profile type iptc";
            const std::string iccKey = "icc";
            const std::string softKey = "Software";
            const std::string commKey = "Comment";
            const std::string descKey = "Description";

            bool bPrint = option == kpsBasic || option == kpsRecursive;
            if (bPrint) {
                out << "STRUCTURE OF PNG FILE: " << io_->path() << std::endl;
                out << " address | chunk |  length | data                           | checksum" << std::endl;
            }

            const long imgSize = (long)io_->size();
            DataBuf cheaderBuf(8);

            while (!io_->eof() && ::strcmp(chType, "IEND")) {
                size_t address = io_->tell();

                std::memset(cheaderBuf.pData_, 0x0, cheaderBuf.size_);
                long bufRead = io_->read(cheaderBuf.pData_, cheaderBuf.size_);
                if (io_->error())
                    throw Error(kerFailedToReadImageData);
                if (bufRead != cheaderBuf.size_)
                    throw Error(kerInputDataReadFailed);

                // Decode chunk data length.
                uint32_t dataOffset = Exiv2::getULong(cheaderBuf.pData_, Exiv2::bigEndian);
                for (int i = 4; i < 8; i++) {
                    chType[i - 4] = cheaderBuf.pData_[i];
                }

                // test that we haven't hit EOF, or wanting to read excessive data
                long restore = io_->tell();
                if (restore == -1 || dataOffset > uint32_t(0x7FFFFFFF) ||
                    static_cast<long>(dataOffset) > imgSize - restore) {
                    throw Exiv2::Error(kerFailedToReadImageData);
                }

                DataBuf buff(dataOffset);
                io_->read(buff.pData_, dataOffset);
                io_->seek(restore, BasicIo::beg);

                // format output
                const int iMax = 30;
                const uint32_t blen = dataOffset > iMax ? iMax : dataOffset;
                std::string dataString = "";
                // if blen == 0 => slice construction fails
                if (blen > 0) {
                    std::stringstream ss;
                    ss << Internal::binaryToString(makeSlice(buff, 0, blen));
                    dataString = ss.str();
                }
                while (dataString.size() < iMax)
                    dataString += ' ';
                dataString = dataString.substr(0, iMax);

                if (bPrint) {
                    io_->seek(dataOffset, BasicIo::cur);  // jump to checksum
                    byte checksum[4];
                    io_->read(checksum, 4);
                    io_->seek(restore, BasicIo::beg);  // restore file pointer

                    out << Internal::stringFormat("%8d | %-5s |%8d | ", (uint32_t)address, chType, dataOffset)
                        << dataString
                        << Internal::stringFormat(" | 0x%02x%02x%02x%02x", checksum[0], checksum[1], checksum[2],
                                                  checksum[3])
                        << std::endl;
                }

                // chunk type
                bool tEXt = std::strcmp(chType, "tEXt") == 0;
                bool zTXt = std::strcmp(chType, "zTXt") == 0;
                bool iCCP = std::strcmp(chType, "iCCP") == 0;
                bool iTXt = std::strcmp(chType, "iTXt") == 0;

                // for XMP, ICC etc: read and format data
                bool bXMP = option == kpsXMP && findi(dataString, xmpKey) == 0;
                bool bICC = option == kpsIccProfile && findi(dataString, iccKey) == 0;
                bool bExif =
                    option == kpsRecursive && (findi(dataString, exifKey) == 0 || findi(dataString, app1Key) == 0);
                bool bIptc = option == kpsRecursive && findi(dataString, iptcKey) == 0;
                bool bSoft = option == kpsRecursive && findi(dataString, softKey) == 0;
                bool bComm = option == kpsRecursive && findi(dataString, commKey) == 0;
                bool bDesc = option == kpsRecursive && findi(dataString, descKey) == 0;
                bool bDump = bXMP || bICC || bExif || bIptc || bSoft || bComm || bDesc;

                if (bDump) {
                    DataBuf dataBuf;
                    byte* data = new byte[dataOffset + 1];
                    data[dataOffset] = 0;
                    io_->read(data, dataOffset);
                    io_->seek(restore, BasicIo::beg);
                    uint32_t name_l = (uint32_t)std::strlen((const char*)data) + 1;  // leading string length
                    uint32_t start = name_l;

                    // decode the chunk
                    bool bGood = false;
                    if (tEXt) {
                        bGood = tEXtToDataBuf(data + name_l, dataOffset - name_l, dataBuf);
                    }
                    if (zTXt || iCCP) {
                        bGood = zlibToDataBuf(data + name_l + 1, dataOffset - name_l - 1,
                                              dataBuf);  // +1 = 'compressed' flag
                    }
                    if (iTXt) {
                        bGood = (start + 3) < dataOffset;  // good if not a nul chunk
                    }

                    // format is content dependent
                    if (bGood) {
                        bool bLF = false;
                        if (bXMP) {
                            while (!data[start] && start < dataOffset)
                                start++;          // skip leading nul bytes
                            out << data + start;  // output the xmp
                        }

                        if (bExif || bIptc) {
                            DataBuf parsedBuf = PngChunk::readRawProfile(dataBuf, tEXt);
#if DEBUG
                            std::cerr << Exiv2::Internal::binaryToString(parsedBuf.pData_,
                                                                         parsedBuf.size_ > 50 ? 50 : parsedBuf.size_, 0)
                                      << std::endl;
#endif
                            if (parsedBuf.size_) {
                                if (bExif) {
                                    // create memio object with the data, then print the structure
                                    BasicIo::UniquePtr p =
                                        BasicIo::UniquePtr(new MemIo(parsedBuf.pData_ + 6, parsedBuf.size_ - 6));
                                    printTiffStructure(*p, out, option, depth);
                                }
                                if (bIptc) {
                                    IptcData::printStructure(out, makeSlice(parsedBuf.pData_, 0, parsedBuf.size_),
                                                             depth);
                                }
                            }
                        }

                        if (bSoft && dataBuf.size_ > 0) {
                            DataBuf s(dataBuf.size_ + 1);                     // allocate buffer with an extra byte
                            memcpy(s.pData_, dataBuf.pData_, dataBuf.size_);  // copy in the dataBuf
                            s.pData_[dataBuf.size_] = 0;                      // nul terminate it
                            const char* str = (const char*)s.pData_;          // give it name
                            out << Internal::indent(depth) << (const char*)buff.pData_ << ": " << str;
                            bLF = true;
                        }

                        if (bICC || bComm) {
                            out.write((const char*)dataBuf.pData_, dataBuf.size_);
                            bLF = bComm;
                        }

                        if (bDesc && iTXt) {
                            DataBuf decoded = PngChunk::decodeTXTChunk(buff, PngChunk::iTXt_Chunk);
                            out.write((const char*)decoded.pData_, decoded.size_);
                            bLF = true;
                        }

                        if (bLF)
                            out << std::endl;
                    }
                    delete[] data;
                }
                io_->seek(dataOffset + 4, BasicIo::cur);  // jump past checksum
                if (io_->error())
                    throw Error(kerFailedToReadImageData);
            }
        }
    }

Project: exiv2
File: pngimage.cpp

    void PngImage::readMetadata()
    {
#ifdef DEBUG
        std::cerr << "Exiv2::PngImage::readMetadata: Reading PNG file " << io_->path() << std::endl;
#endif
        if (io_->open() != 0) {
            throw Error(kerDataSourceOpenFailed, io_->path(), strError());
        }
        IoCloser closer(*io_);
        if (!isPngType(*io_, true)) {
            throw Error(kerNotAnImage, "PNG");
        }
        clearMetadata();

        const long imgSize = (long)io_->size();
        DataBuf cheaderBuf(8);  // Chunk header: 4 bytes (data size) + 4 bytes (chunk type).

        while (!io_->eof()) {
            std::memset(cheaderBuf.pData_, 0x0, cheaderBuf.size_);
            readChunk(cheaderBuf, *io_);  // Read chunk header.

            // Decode chunk data length.
            uint32_t chunkLength = Exiv2::getULong(cheaderBuf.pData_, Exiv2::bigEndian);
            long pos = io_->tell();
            if (pos == -1 || chunkLength > uint32_t(0x7FFFFFFF) || static_cast<long>(chunkLength) > imgSize - pos) {
                throw Exiv2::Error(kerFailedToReadImageData);
            }

            std::string chunkType(reinterpret_cast<char*>(cheaderBuf.pData_) + 4, 4);
#ifdef DEBUG
            std::cout << "Exiv2::PngImage::readMetadata: chunk type: " << chunkType << " length: " << chunkLength
                      << std::endl;
#endif

            /// \todo analyse remaining chunks of the standard
            // Perform a chunk triage for item that we need.
            if (chunkType == "IEND" || chunkType == "IHDR" || chunkType == "tEXt" || chunkType == "zTXt" ||
                chunkType == "iTXt" || chunkType == "iCCP") {
                DataBuf chunkData(chunkLength);
                readChunk(chunkData, *io_);  // Extract chunk data.

                if (chunkType == "IEND") {
                    return;  // Last chunk found: we stop parsing.
                } else if (chunkType == "IHDR" && chunkData.size_ >= 8) {
                    Internal::PngImageHeader header;
                    PngChunk::decodeIHDRChunk(chunkData, header);
                    pixelWidth_ = header.width;
                    pixelHeight_ = header.height;
                    /// \todo handle rest of data
                } else if (chunkType == "tEXt") {
                    PngChunk::decodeTXTChunk(this, chunkData, PngChunk::tEXt_Chunk);
                } else if (chunkType == "zTXt") {
                    PngChunk::decodeTXTChunk(this, chunkData, PngChunk::zTXt_Chunk);
                } else if (chunkType == "iTXt") {
                    PngChunk::decodeTXTChunk(this, chunkData, PngChunk::iTXt_Chunk);
                } else if (chunkType == "iCCP") {
                    // The ICC profile name can vary from 1-79 characters.
                    uint32_t iccOffset = 0;
                    while (iccOffset < 80 && iccOffset < chunkLength) {
                        if (chunkData.pData_[iccOffset++] == 0x00) {
                            break;
                        }
                    }
                    profileName_ = std::string(reinterpret_cast<char*>(chunkData.pData_), iccOffset - 1);
                    ++iccOffset;  // +1 = 'compressed' flag
                    enforce(iccOffset <= chunkLength, Exiv2::kerCorruptedMetadata);

                    zlibToDataBuf(chunkData.pData_ + iccOffset, chunkLength - iccOffset, iccProfile_);
#ifdef DEBUG
                    std::cout << "Exiv2::PngImage::readMetadata: profile name: " << profileName_ << std::endl;
                    std::cout << "Exiv2::PngImage::readMetadata: iccProfile.size_ (uncompressed) : "
                              << iccProfile_.size_ << std::endl;
#endif
                }

                // Set chunkLength to 0 in case we have read a supported chunk type. Otherwise, we need to seek the
                // file to the next chunk position.
                chunkLength = 0;
            }

            // Move to the next chunk: chunk data size + 4 CRC bytes.
#ifdef DEBUG
            std::cout << "Exiv2::PngImage::readMetadata: Seek to offset: " << chunkLength + 4 << std::endl;
#endif
            io_->seek(chunkLength + 4, BasicIo::cur);
            if (io_->error() || io_->eof()) {
                throw Error(kerFailedToReadImageData);
            }
        }
    }  // PngImage::readMetadata