Commit message: Check in Image::printIFDStructure if seek and reads are OK
CVE: CVE-2018-18915
CWE: CWE-835
Vulnerability description: There is an infinite loop in the Exiv2::Image::printIFDStructure function of image.cpp in Exiv2 0.27-RC1. A crafted input will lead to a remote denial of service attack.

Project: exiv2
File: image.cpp

    const char* Image::typeName(uint16_t tag) const
    {
        //! List of TIFF image tags
        const char* result = NULL;
        switch (tag ) {
            case Exiv2::unsignedByte     : result = "BYTE"      ; break;
            case Exiv2::asciiString      : result = "ASCII"     ; break;
            case Exiv2::unsignedShort    : result = "SHORT"     ; break;
            case Exiv2::unsignedLong     : result = "LONG"      ; break;
            case Exiv2::unsignedRational : result = "RATIONAL"  ; break;
            case Exiv2::signedByte       : result = "SBYTE"     ; break;
            case Exiv2::undefined        : result = "UNDEFINED" ; break;
            case Exiv2::signedShort      : result = "SSHORT"    ; break;
            case Exiv2::signedLong       : result = "SLONG"     ; break;
            case Exiv2::signedRational   : result = "SRATIONAL" ; break;
            case Exiv2::tiffFloat        : result = "FLOAT"     ; break;
            case Exiv2::tiffDouble       : result = "DOUBLE"    ; break;
            case Exiv2::tiffIfd          : result = "IFD"       ; break;
            default                      : result = "unknown"   ; break;
        }
        return result;
    }

Project: exiv2
File: image.cpp

    Image::AutoPtr ImageFactory::open(const std::string& path, bool useCurl)
    {
        Image::AutoPtr image = open(ImageFactory::createIo(path, useCurl)); // may throw
        if (image.get() == 0) throw Error(kerFileContainsUnknownImageType, path);
        return image;
    }

Project: exiv2
File: image.cpp

    Image::AutoPtr ImageFactory::create(int type,
                                        BasicIo::AutoPtr io)
    {
        // BasicIo instance does not need to be open
        const Registry* r = find(registry, type);
        if (0 != r) {
            return r->newInstance_(io, true);
        }
        return Image::AutoPtr();
    } // ImageFactory::create

Project: exiv2
File: image.cpp

    int ImageFactory::getType(BasicIo& io)
    {
        if (io.open() != 0) return ImageType::none;
        IoCloser closer(io);
        for (unsigned int i = 0; registry[i].imageType_ != ImageType::none; ++i) {
            if (registry[i].isThisType_(io, false)) {
                return registry[i].imageType_;
            }
        }
        return ImageType::none;
    } // ImageFactory::getType

Project: exiv2
File: image.cpp

    void append(Blob& blob, const byte* buf, uint32_t len)
    {
        if (len != 0) {
            assert(buf != 0);
            Blob::size_type size = blob.size();
            if (blob.capacity() - size < len) {
                blob.reserve(size + 65536);
            }
            blob.resize(size + len);
            std::memcpy(&blob[size], buf, len);
        }
    } // append

Project: exiv2
File: image.cpp

    int ImageFactory::getType(const std::wstring& wpath)
    {
        FileIo fileIo(wpath);
        return getType(fileIo);
    }

Project: exiv2
File: image.cpp

    Image::AutoPtr ImageFactory::create(int type,
                                        const std::wstring& wpath)
    {
        std::auto_ptr<FileIo> fileIo(new FileIo(wpath));
        // Create or overwrite the file, then close it
        if (fileIo->open("w+b") != 0) {
            throw WError(kerFileOpenFailed, wpath, "w+b", strError().c_str());
        }
        fileIo->close();
        BasicIo::AutoPtr io(fileIo);
        Image::AutoPtr image = create(type, io);
        if (image.get() == 0) throw Error(kerUnsupportedImageType, type);
        return image;
    }

Project: exiv2
File: image.cpp

    int ImageFactory::getType(const byte* data, long size)
    {
        MemIo memIo(data, size);
        return getType(memIo);
    }

Project: exiv2
File: image.cpp

    Image::AutoPtr ImageFactory::open(BasicIo::AutoPtr io)
    {
        if (io->open() != 0) {
            throw Error(kerDataSourceOpenFailed, io->path(), strError());
        }
        for (unsigned int i = 0; registry[i].imageType_ != ImageType::none; ++i) {
            if (registry[i].isThisType_(*io, false)) {
                return registry[i].newInstance_(io, false);
            }
        }
        return Image::AutoPtr();
    } // ImageFactory::open

Project: exiv2
File: image.cpp

    BasicIo::AutoPtr ImageFactory::createIo(const std::wstring& wpath, bool useCurl)
    {
        Protocol fProt = fileProtocol(wpath);
#if EXV_USE_SSH == 1
        if (fProt == pSsh || fProt == pSftp) {
            return BasicIo::AutoPtr(new SshIo(wpath));
        }
#endif
#if EXV_USE_CURL == 1
        if (useCurl && (fProt == pHttp || fProt == pHttps || fProt == pFtp)) {
            return BasicIo::AutoPtr(new CurlIo(wpath));
        }
#endif
        if (fProt == pHttp)
            return BasicIo::AutoPtr(new HttpIo(wpath));
        if (fProt == pFileUri)
            return BasicIo::AutoPtr(new FileIo(pathOfFileUrl(wpath)));
        if (fProt == pStdin || fProt == pDataUri)
            return BasicIo::AutoPtr(new XPathIo(wpath)); // may throw
        return BasicIo::AutoPtr(new FileIo(wpath));
    } // ImageFactory::createIo

Project: exiv2
File: image.cpp

    int ImageFactory::getType(const std::string& path)
    {
        FileIo fileIo(path);
        return getType(fileIo);
    }

Project: exiv2
File: image.cpp

    bool ImageFactory::checkType(int type, BasicIo& io, bool advance)
    {
        const Registry* r = find(registry, type);
        if (0 != r) {
            return r->isThisType_(io, advance);
        }
        return false;
    } // ImageFactory::checkType

Project: exiv2
File: image.cpp

    AccessMode ImageFactory::checkMode(int type, MetadataId metadataId)
    {
        const Registry* r = find(registry, type);
        if (!r) throw Error(kerUnsupportedImageType, type);
        AccessMode am = amNone;
        switch (metadataId) {
        case mdNone:
            break;
        case mdExif:
            am = r->exifSupport_;
            break;
        case mdIptc:
            am = r->iptcSupport_;
            break;
        case mdXmp:
            am = r->xmpSupport_;
            break;
        case mdComment:
            am = r->commentSupport_;
            break;
        case mdIccProfile: break;

        // no default: let the compiler complain
        }
        return am;
    }

Project: exiv2
File: image.cpp

    std::string& Image::xmpPacket()
    {
        return xmpPacket_;
    }

Project: exiv2
File: image.cpp

    BasicIo::AutoPtr ImageFactory::createIo(const std::string& path, bool useCurl)
    {
        Protocol fProt = fileProtocol(path);

#ifdef EXV_USE_SSH
        if (fProt == pSsh || fProt == pSftp) {
            return BasicIo::AutoPtr(new SshIo(path)); // may throw
        }
#endif

#ifdef EXV_USE_CURL
        if (useCurl && (fProt == pHttp || fProt == pHttps || fProt == pFtp)) {
            return BasicIo::AutoPtr(new CurlIo(path)); // may throw
        }
#endif

        if (fProt == pHttp)
            return BasicIo::AutoPtr(new HttpIo(path)); // may throw
        if (fProt == pFileUri)
            return BasicIo::AutoPtr(new FileIo(pathOfFileUrl(path)));
        if (fProt == pStdin || fProt == pDataUri)
            return BasicIo::AutoPtr(new XPathIo(path)); // may throw

        return BasicIo::AutoPtr(new FileIo(path));

        (void)(useCurl);
    } // ImageFactory::createIo

Project: exiv2
File: image.cpp

    Image::AutoPtr ImageFactory::open(const std::wstring& wpath, bool useCurl)
    {
        Image::AutoPtr image = open(ImageFactory::createIo(wpath, useCurl)); // may throw
        if (image.get() == 0) throw WError(kerFileContainsUnknownImageType, wpath);
        return image;
    }

Project: exiv2
File: image.cpp

    Image::Image(int              imageType,
                 uint16_t         supportedMetadata,
                 BasicIo::AutoPtr io)
        : io_(io),
          pixelWidth_(0),
          pixelHeight_(0),
          imageType_(imageType),
          supportedMetadata_(supportedMetadata),
#ifdef EXV_HAVE_XMP_TOOLKIT
          writeXmpFromPacket_(false),
#else
          writeXmpFromPacket_(true),
#endif
          byteOrder_(invalidByteOrder),
          tags_(),
          init_(true)
    {
    }

Project: exiv2
File: image.cpp

    void Image::printIFDStructure(BasicIo& io, std::ostream& out, Exiv2::PrintStructureOption option,uint32_t start,bool bSwap,char c,int depth)
    {
        depth++;
        bool bFirst  = true  ;

        // buffer
        const size_t dirSize = 32;
        DataBuf  dir(dirSize);
        bool bPrint = option == kpsBasic || option == kpsRecursive;

        do {
            // Read top of directory
            const int seekSuccess = !io.seek(start,BasicIo::beg);
            const long bytesRead = io.read(dir.pData_, 2);
            if (!seekSuccess || bytesRead == 0) {
                throw Error(kerCorruptedMetadata);
            }
            uint16_t   dirLength = byteSwap2(dir,0,bSwap);

            bool tooBig = dirLength > 500;
            if ( tooBig ) throw Error(kerTiffDirectoryTooLarge);

            if ( bFirst && bPrint ) {
                out << Internal::indent(depth) << Internal::stringFormat("STRUCTURE OF TIFF FILE (%c%c): ",c,c) << io.path() << std::endl;
                if ( tooBig ) out << Internal::indent(depth) << "dirLength = " << dirLength << std::endl;
            }

            // Read the dictionary
            for ( int i = 0 ; i < dirLength ; i ++ ) {
                if ( bFirst && bPrint ) {
                    out << Internal::indent(depth)
                        << " address |    tag                              |     "
                        << " type |    count |    offset | value\n";
                }
                bFirst = false;

                io.read(dir.pData_, 12);
                uint16_t tag    = byteSwap2(dir,0,bSwap);
                uint16_t type   = byteSwap2(dir,2,bSwap);
                uint32_t count  = byteSwap4(dir,4,bSwap);
                uint32_t offset = byteSwap4(dir,8,bSwap);

                // Break for unknown tag types else we may segfault.
                if ( !typeValid(type) ) {
                    std::cerr << "invalid type value detected in Image::printIFDStructure:  " << type << std::endl;
                    start = 0; // break from do loop
                    throw Error(kerInvalidTypeValue);
                }

                std::string sp  = "" ; // output spacer

                //prepare to print the value
                uint32_t kount  = isPrintXMP(tag,option) ? count // haul in all the data
                                : isPrintICC(tag,option) ? count // ditto
                                : isStringType(type)     ? (count > 32 ? 32 : count) // restrict long arrays
                                : count > 5              ? 5
                                : count
                                ;
                uint32_t pad    = isStringType(type) ? 1 : 0;
                uint32_t size   = isStringType(type) ? 1
                                : is2ByteType(type)  ? 2
                                : is4ByteType(type)  ? 4
                                : is8ByteType(type)  ? 8
                                : 1
                                ;

                // if ( offset > io.size() ) offset = 0; // Denial of service?

                // #55 and #56 memory allocation crash test/data/POC8
                long long allocate = (long long) size*count + pad+20;
                if ( allocate > (long long) io.size() ) {
                    throw Error(kerInvalidMalloc);
                }
                DataBuf  buf((long)allocate);  // allocate a buffer
                std::memset(buf.pData_, 0, buf.size_);
                std::memcpy(buf.pData_,dir.pData_+8,4);  // copy dir[8:11] into buffer (short strings)
                const bool bOffsetIsPointer = count*size > 4;

                if ( bOffsetIsPointer ) {         // read into buffer
                    size_t   restore = io.tell();  // save
                    io.seek(offset,BasicIo::beg);  // position
                    io.read(buf.pData_,count*size);// read
                    io.seek(restore,BasicIo::beg); // restore
                }

                if ( bPrint ) {
                    const uint32_t address = start + 2 + i*12 ;
                    const std::string offsetString = bOffsetIsPointer?
                        Internal::stringFormat("%10u", offset):
                        "";

                    out << Internal::indent(depth)
                    << Internal::stringFormat("%8u | %#06x %-28s |%10s |%9u |%10s | "
                                              ,address,tag,tagName(tag).c_str(),typeName(type),count,offsetString.c_str());
                    if ( isShortType(type) ){
                        for ( size_t k = 0 ; k < kount ; k++ ) {
                            out << sp << byteSwap2(buf,k*size,bSwap);
                            sp = " ";
                        }
                    } else if ( isLongType(type) ){
                        for ( size_t k = 0 ; k < kount ; k++ ) {
                            out << sp << byteSwap4(buf,k*size,bSwap);
                            sp = " ";
                        }

                    } else if ( isRationalType(type) ){
                        for ( size_t k = 0 ; k < kount ; k++ ) {
                            uint32_t a = byteSwap4(buf,k*size+0,bSwap);
                            uint32_t b = byteSwap4(buf,k*size+4,bSwap);
                            out << sp << a << "/" << b;
                            sp = " ";
                        }
                    } else if ( isStringType(type) ) {
                        out << sp << Internal::binaryToString(makeSlice(buf, 0, kount));
                    }

                    sp = kount == count ? "" : " ...";
                    out << sp << std::endl;

                    if ( option == kpsRecursive && (tag == 0x8769 /* ExifTag */ || tag == 0x014a/*SubIFDs*/  || type == tiffIfd) ) {
                        for ( size_t k = 0 ; k < count ; k++ ) {
                            size_t   restore = io.tell();
                            uint32_t offset = byteSwap4(buf,k*size,bSwap);
                            printIFDStructure(io,out,option,offset,bSwap,c,depth);
                            io.seek(restore,BasicIo::beg);
                        }
                    } else if ( option == kpsRecursive && tag == 0x83bb /* IPTCNAA */ ) {

                        if (static_cast<size_t>(Safe::add(count, offset)) > io.size()) {
                            throw Error(kerCorruptedMetadata);
                        }

                        const size_t restore = io.tell();
                        io.seek(offset, BasicIo::beg);  // position
                        std::vector<byte> bytes(count) ;  // allocate memory
                        // TODO: once we have C++11 use bytes.data()
                        const long read_bytes = io.read(&bytes[0], count);
                        io.seek(restore, BasicIo::beg);
                        // TODO: once we have C++11 use bytes.data()
                        IptcData::printStructure(out, makeSliceUntil(&bytes[0], read_bytes), depth);

                    }  else if ( option == kpsRecursive && tag == 0x927c /* MakerNote */ && count > 10) {
                        size_t   restore = io.tell();  // save

                        uint32_t jump= 10           ;
                        byte     bytes[20]          ;
                        const char* chars = (const char*) &bytes[0] ;
                        io.seek(offset,BasicIo::beg);  // position
                        io.read(bytes,jump    )     ;  // read
                        bytes[jump]=0               ;
                        if ( ::strcmp("Nikon",chars) == 0 ) {
                            // tag is an embedded tiff
                            byte* bytes=new byte[count-jump] ;  // allocate memory
                            io.read(bytes,count-jump)        ;  // read
                            MemIo memIo(bytes,count-jump)    ;  // create a file
                            printTiffStructure(memIo,out,option,depth);
                            delete[] bytes                   ;  // free
                        } else {
                            // tag is an IFD
                            io.seek(0,BasicIo::beg);  // position
                            printIFDStructure(io,out,option,offset,bSwap,c,depth);
                        }

                        io.seek(restore,BasicIo::beg); // restore
                    }
                }

                if ( isPrintXMP(tag,option) ) {
                    buf.pData_[count]=0;
                    out << (char*) buf.pData_;
                }
                if ( isPrintICC(tag,option) ) {
                    out.write((const char*)buf.pData_,count);
                }
            }
            if ( start ) {
                io.read(dir.pData_, 4);
                start = tooBig ? 0 : byteSwap4(dir,0,bSwap);
            }
        } while (start) ;

        if ( bPrint ) {
            out << Internal::indent(depth) << "END " << io.path() << std::endl;
        }
        out.flush();
        depth--;
    }

Project: exiv2
File: image.cpp

        bool operator==(const int& imageType) const { return imageType == imageType_; }

Project: exiv2
File: image.cpp

    Image::AutoPtr ImageFactory::create(int type,
                                        const std::string& path)
    {
        std::auto_ptr<FileIo> fileIo(new FileIo(path));
        // Create or overwrite the file, then close it
        if (fileIo->open("w+b") != 0) {
            throw Error(kerFileOpenFailed, path, "w+b", strError());
        }
        fileIo->close();
        BasicIo::AutoPtr io(fileIo);
        Image::AutoPtr image = create(type, io);
        if (image.get() == 0) throw Error(kerUnsupportedImageType, type);
        return image;
    }

Project: exiv2
File: image.cpp

    Image::AutoPtr ImageFactory::create(int type)
    {
        BasicIo::AutoPtr io(new MemIo);
        Image::AutoPtr image = create(type, io);
        if (image.get() == 0) throw Error(kerUnsupportedImageType, type);
        return image;
    }

Project: exiv2
File: image.cpp

    Image::AutoPtr ImageFactory::open(const byte* data, long size)
    {
        BasicIo::AutoPtr io(new MemIo(data, size));
        Image::AutoPtr image = open(io); // may throw
        if (image.get() == 0) throw Error(kerMemoryContainsUnknownImageType);
        return image;
    }