#include "bigtiffimage.hpp" #include "safe_op.hpp" #include "exif.hpp" #include "error.hpp" #include "image_int.hpp" #include "enforce.hpp" #include #include #include namespace Exiv2 { namespace { struct Header { enum Format { StandardTiff, BigTiff, }; Header(): byteOrder_(invalidByteOrder), version_(-1), data_size_(0), dir_offset_(0) {} Header(const ByteOrder& order, int v, int size, uint64_t offset): byteOrder_(order), version_(v), data_size_(size), dir_offset_(offset) { } bool isValid() const { return version_ != -1; } ByteOrder byteOrder() const { assert(isValid()); return byteOrder_; } int version() const { assert(isValid()); return version_; } Format format() const { assert(isValid()); return version_ == 0x2A? StandardTiff: BigTiff; } int dataSize() const { assert(isValid()); return data_size_; } uint64_t dirOffset() const { assert(isValid()); return dir_offset_; } private: ByteOrder byteOrder_; int version_; // 42 or 43 - regular tiff or big tiff int data_size_; // 4 or 8 uint64_t dir_offset_; }; Header readHeader(BasicIo& io) { byte header[2] = {0, 0}; io.read(header, 2); ByteOrder byteOrder = invalidByteOrder; if (header[0] == 'I' && header[1] == 'I') byteOrder = littleEndian; else if (header[0] == 'M' && header[1] == 'M') byteOrder = bigEndian; if (byteOrder == invalidByteOrder) return Header(); byte version[2] = {0, 0}; io.read(version, 2); const uint16_t magic = getUShort(version, byteOrder); if (magic != 0x2A && magic != 0x2B) return Header(); Header result; if (magic == 0x2A) { byte buffer[4]; int read = io.read(buffer, 4); if (read < 4) throw Exiv2::Error(kerCorruptedMetadata); const uint32_t offset = getULong(buffer, byteOrder); result = Header(byteOrder, magic, 4, offset); } else { byte buffer[8] = {0, 0, 0, 0, 0, 0, 0, 0}; int read = io.read(buffer, 2); if (read < 2) throw Exiv2::Error(kerCorruptedMetadata); const int size = getUShort(buffer, byteOrder); if (size == 8) { read = io.read(buffer, 2); // null if (read < 2) throw Exiv2::Error(kerCorruptedMetadata); read = io.read(buffer, 8); if (read < 8) throw Exiv2::Error(kerCorruptedMetadata); const uint64_t offset = getULongLong(buffer, byteOrder); if (offset >= io.size()) throw Exiv2::Error(kerCorruptedMetadata); result = Header(byteOrder, magic, size, offset); } else throw Exiv2::Error(kerCorruptedMetadata); } return result; } class BigTiffImage: public Image { public: BigTiffImage(BasicIo::AutoPtr io): Image(ImageType::bigtiff, mdExif, io), header_(), dataSize_(0), doSwap_(false) { header_ = readHeader(Image::io()); assert(header_.isValid()); doSwap_ = (isLittleEndianPlatform() && header_.byteOrder() == bigEndian) || (isBigEndianPlatform() && header_.byteOrder() == littleEndian); dataSize_ = header_.format() == Header::StandardTiff? 4 : 8; } virtual ~BigTiffImage() {} // overrides void readMetadata() { } void writeMetadata() { } std::string mimeType() const { return std::string(); } void printStructure(std::ostream& os, PrintStructureOption option, int depth) { printIFD(os, option, header_.dirOffset(), depth - 1); } private: Header header_; int dataSize_; bool doSwap_; void printIFD(std::ostream& out, PrintStructureOption option, uint64_t dir_offset, int depth) { BasicIo& io = Image::io(); // Fix for https://github.com/Exiv2/exiv2/issues/712 // A malicious file can cause a very deep recursion, leading to // stack exhaustion. // Note: 200 is an arbitrarily chosen cut-off value. The value // of depth determines the amount of indentation inserted by the // pretty-printer. The output starts to become unreadable as // soon as the indentation exceeds 80 characters or so. That's // why 200 ought to be a reasonable cut-off. if (depth > 200) { out << Internal::indent(depth) << "Maximum indentation depth exceeded." << std::endl; return; } depth++; bool bFirst = true; // buffer bool bPrint = true; do { // Read top of directory io.seek(dir_offset, BasicIo::beg); const uint64_t entries = readData(header_.format() == Header::StandardTiff? 2: 8); const bool tooBig = entries > 500; if ( bFirst && bPrint ) { out << Internal::indent(depth) << "STRUCTURE OF BIGTIFF FILE " << io.path() << std::endl; if (tooBig) out << Internal::indent(depth) << "entries = " << entries << std::endl; } if (tooBig) break; // Read the dictionary for ( uint64_t i = 0; i < entries; i ++ ) { if ( bFirst && bPrint ) out << Internal::indent(depth) << " address | tag | " << " type | count | offset | value\n"; bFirst = false; const uint16_t tag = (uint16_t) readData(2); const uint16_t type = (uint16_t) readData(2); const uint64_t count = readData(dataSize_); const DataBuf data = io.read(dataSize_); // Read data as raw value. what should be done about it will be decided depending on type std::string sp = "" ; // output spacer //prepare to print the value // TODO: figure out what's going on with kount const uint64_t kount = isStringType(type)? (count > 32 ? 32 : count) // restrict long arrays : count > 5 ? 5 : count ; const uint32_t pad = isStringType(type) ? 1 : 0; const uint32_t size = isStringType(type) ? 1 : is2ByteType(type) ? 2 : is4ByteType(type) ? 4 : is8ByteType(type) ? 8 : 1; // #55 and #56 memory allocation crash test/data/POC8 // size * count > std::numeric_limits::max() // => // size > std::numeric_limits::max() / count // (don't perform that check when count == 0 => will cause a division by zero exception) if (count != 0) { if (size > std::numeric_limits::max() / count) { throw Error(kerInvalidMalloc); // we got number bigger than 2^64 } } // more than we can handle if (size * count > std::numeric_limits::max() - pad) throw Error(kerInvalidMalloc); // again more than 2^64 const uint64_t allocate = size*count + pad; if ( allocate > io.size() ) { throw Error(kerInvalidMalloc); } DataBuf buf(static_cast(allocate)); const uint64_t offset = header_.format() == Header::StandardTiff? byteSwap4(data, 0, doSwap_): byteSwap8(data, 0, doSwap_); // big data? Use 'data' as pointer to real data const bool usePointer = (size_t) count*size > (size_t) dataSize_; if ( usePointer ) // read into buffer { size_t restore = io.tell(); // save io.seek(offset, BasicIo::beg); // position io.read(buf.pData_, (long) count * size); // read io.seek(restore, BasicIo::beg); // restore } else // use 'data' as data :) std::memcpy(buf.pData_, data.pData_, (size_t) count * size); // copy data if ( bPrint ) { const uint64_t entrySize = header_.format() == Header::StandardTiff? 12: 20; const uint64_t address = dir_offset + 2 + i * entrySize; out << Internal::indent(depth) << Internal::stringFormat("%8u | %#06x %-25s |%10s |%9u |", static_cast(address), tag, tagName(tag).c_str(), typeName(type), count) <<(usePointer ? Internal::stringFormat("%10u | ",(size_t)offset) : Internal::stringFormat("%10s | ","")) ; if ( isShortType(type) ) { for ( size_t k = 0 ; k < kount ; k++ ) { out << sp << byteSwap2(buf, k*size, doSwap_); sp = " "; } } else if ( isLongType(type) ) { for ( size_t k = 0 ; k < kount ; k++ ) { out << sp << byteSwap4(buf, k*size, doSwap_); sp = " "; } } else if ( isLongLongType(type) ) { for ( size_t k = 0 ; k < kount ; k++ ) { out << sp << byteSwap8(buf, k*size, doSwap_); sp = " "; } } else if ( isRationalType(type) ) { for ( size_t k = 0 ; k < kount ; k++ ) { uint32_t a = byteSwap4(buf, k*size+0, doSwap_); uint32_t b = byteSwap4(buf, k*size+4, doSwap_); out << sp << a << "/" << b; sp = " "; } } else if ( isStringType(type) ) out << sp << Internal::binaryToString(makeSlice(buf, 0, static_cast(kount))); sp = kount == count ? "" : " ..."; out << sp << std::endl; if ( option == kpsRecursive && (tag == 0x8769 /* ExifTag */ || tag == 0x014a/*SubIFDs*/ || type == tiffIfd || type == tiffIfd8) ) { for ( size_t k = 0 ; k < count ; k++ ) { const size_t restore = io.tell(); const uint64_t ifdOffset = type == tiffIfd8? byteSwap8(buf, k*size, doSwap_): byteSwap4(buf, k*size, doSwap_); printIFD(out, option, ifdOffset, depth); io.seek(restore, BasicIo::beg); } } else if ( option == kpsRecursive && tag == 0x83bb /* IPTCNAA */ ) { if (Safe::add(count, offset) > io.size()) { throw Error(kerCorruptedMetadata); } const size_t restore = io.tell(); io.seek(offset, BasicIo::beg); // position std::vector bytes((size_t)count) ; // allocate memory // TODO: once we have C++11 use bytes.data() const long read_bytes = io.read(&bytes[0], static_cast(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 long jump= 10 ; byte bytes[20] ; const char* chars = (const char*) &bytes[0] ; io.seek(dir_offset, BasicIo::beg); // position io.read(bytes,jump ) ; // read bytes[jump]=0 ; if ( ::strcmp("Nikon",chars) == 0 ) { // tag is an embedded tiff std::vector nikon_bytes((size_t)(count - jump)); io.read(&nikon_bytes.at(0), (long)nikon_bytes.size()); MemIo memIo(&nikon_bytes.at(0), (long)count - jump); // create a file std::cerr << "Nikon makernote" << std::endl; // printTiffStructure(memIo,out,option,depth); // TODO: fix it } else { // tag is an IFD io.seek(0, BasicIo::beg); // position std::cerr << "makernote" << std::endl; printIFD(out,option,offset,depth); } io.seek(restore,BasicIo::beg); // restore } } } const uint64_t nextDirOffset = readData(dataSize_); dir_offset = tooBig ? 0 : nextDirOffset; out.flush(); } while (dir_offset != 0); if ( bPrint ) out << Internal::indent(depth) << "END " << io.path() << std::endl; depth--; } uint64_t readData(int size) const { const DataBuf data = Image::io().read(size); enforce(data.size_ != 0, kerCorruptedMetadata); uint64_t result = 0; if (data.size_ == 1) {} else if (data.size_ == 2) result = byteSwap2(data, 0, doSwap_); else if (data.size_ == 4) result = byteSwap4(data, 0, doSwap_); else if (data.size_ == 8) result = byteSwap8(data, 0, doSwap_); else throw Exiv2::Error(kerCorruptedMetadata); return result; } }; } Image::AutoPtr newBigTiffInstance(BasicIo::AutoPtr io, bool) { return Image::AutoPtr(new BigTiffImage(io)); } bool isBigTiffType(BasicIo& io, bool advance) { const long pos = io.tell(); const Header header = readHeader(io); const bool valid = header.isValid(); if (valid == false || advance == false) io.seek(pos, BasicIo::beg); return valid; } }