Commit message: None
CVE: CVE-2017-18233
CWE: CWE-190
Vulnerability description: An issue was discovered in Exempi before 2.4.4. Integer overflow in the Chunk class in XMPFiles/source/FormatSupport/RIFF.cpp allows remote attackers to cause a denial of service (infinite loop) via crafted XMP data in a .avi file.

Project: exempi

Chunk::Chunk( ContainerChunk* parent, ChunkType c, XMP_Uns32 id )
{
	this->hasChange = false;
	this->chunkType = c; // base class assumption
	this->parent = parent;
	this->id = id;
	this->oldSize = 0;
	this->newSize = 8;
	this->oldPos = 0; // inevitable for ad-hoc
	this->needSizeFix = false;

	if ( this->parent != NULL )
	{
		this->parent->children.push_back( this );
		if( this->chunkType == chunk_VALUE )
			this->parent->childmap.insert( std::make_pair( this->id, (ValueChunk*) this ) );
	}
}

Project: exempi

ContainerChunk::ContainerChunk( ContainerChunk* parent, XMP_Uns32 id, XMP_Uns32 containerType ) : Chunk( NULL /* !! */, chunk_CONTAINER, id )
{
	XMP_Enforce( parent != NULL );

	this->containerType = containerType;
	this->newSize = 12;
	this->parent = parent;

	chunkVect* siblings = &parent->children;

	siblings->push_back( this );
}

Project: exempi

ContainerChunk::ContainerChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_CONTAINER )
{
	bool repairMode = ( 0 != ( handler->parent->openFlags & kXMPFiles_OpenRepairFile ));

	try
	{
		XMP_IO* file = handler->parent->ioRef;
		XMP_Uns8 level = handler->level;

		this->containerType = XIO::ReadUns32_LE( file );

		if ( level == 0 && handler->riffChunks.size() > 0 )
		{
			XMP_Validate( handler->parent->format == kXMP_AVIFile, "only AVI may have multiple top-level chunks", kXMPErr_BadFileFormat );
			XMP_Validate( this->containerType == kType_AVIX, "all chunks beyond main chunk must be type AVIX", kXMPErr_BadFileFormat );
		}

		bool hasSubChunks = ( ( this->id == kChunk_RIFF ) ||
							  ( this->id == kChunk_LIST && this->containerType == kType_INFO ) ||
							  ( this->id == kChunk_LIST && this->containerType == kType_Tdat )
						  );
		XMP_Int64 endOfChunk = this->oldPos + this->oldSize;

		if ( (level == 0) && repairMode && (endOfChunk > handler->oldFileSize) )
		{
			endOfChunk = handler->oldFileSize; // assign actual file size
			this->oldSize = endOfChunk - this->oldPos; //reversely calculate correct oldSize
		}

		XMP_Validate( endOfChunk <= handler->oldFileSize, "offset beyond EoF", kXMPErr_BadFileFormat );

		Chunk* curChild = 0;
		if ( hasSubChunks )
		{
			handler->level++;
			while ( file->Offset() < endOfChunk )
			{
				curChild = RIFF::getChunk( this, handler );

				if ( file->Offset() % 2 == 1 )
				{
					XMP_Uns8 pad;
					file->Read ( &pad, 1 );  // Read the pad, tolerate being at EOF.

				}

				if ( (containerType== kType_INFO || containerType == kType_Tdat)
						&& ( curChild->chunkType == chunk_JUNK ) )
				{
						this->children.pop_back();
						delete curChild;
				} // for other chunks: join neighouring Junk chunks into one
				else if ( (curChild->chunkType == chunk_JUNK) && ( this->children.size() >= 2 ) )
				{
					Chunk* prevChunk = this->children.at( this->children.size() - 2 );
					if ( prevChunk->chunkType == chunk_JUNK )
					{
						prevChunk->oldSize += curChild->oldSize;
						prevChunk->newSize += curChild->newSize;
						XMP_Enforce( prevChunk->oldSize == prevChunk->newSize );
						this->children.pop_back();
						delete curChild;
					}
				}
			}
			handler->level--;
			XMP_Validate( file->Offset() == endOfChunk, "subchunks exceed outer chunk size", kXMPErr_BadFileFormat );

			if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_INFO )
				handler->listInfoChunk = this;
			if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_Tdat )
				handler->listTdatChunk = this;
		}
		else // skip non-interest container chunk
		{
			file->Seek ( (this->oldSize - 8 - 4), kXMP_SeekFromCurrent );
		} // if - else

	} // try
	catch (XMP_Error& e) {
		this->release(); // free resources
		if ( this->parent != 0)
			this->parent->children.pop_back(); // hereby taken care of, so removing myself...

		throw e;         // re-throw
	}
}

Project: exempi

JunkChunk::JunkChunk( ContainerChunk* parent, XMP_Int64 size ) : Chunk( parent, chunk_JUNK, kChunk_JUNK )
{
	XMP_Assert( size >= 8 );
	this->oldSize = size;
	this->newSize = size;
	this->hasChange = true;
}

Project: exempi

JunkChunk::JunkChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, true, chunk_JUNK )
{
	chunkType = chunk_JUNK;
}

Project: exempi

void ValueChunk::SetValue( std::string value, bool optionalNUL /* = false */ )
{
	this->newValue.assign( value );
	if ( (! optionalNUL) || ((value.size() & 1) == 1) ) {
		this->newValue.append( 1, '\0' ); // append zero termination as explicit part of string
	}
	this->newSize = this->newValue.size() + 8;
}

Project: exempi

ValueChunk::ValueChunk( ContainerChunk* parent, std::string value, XMP_Uns32 id ) : Chunk( parent, chunk_VALUE, id )
{
	this->oldValue = std::string();
	this->SetValue( value );
}

Project: exempi

ValueChunk::ValueChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_VALUE )
{
	XMP_IO* file = handler->parent->ioRef;
	XMP_Uns8 level = handler->level;


	XMP_Int32 length = (XMP_Int32) this->oldSize - 8;
	this->oldValue.reserve( length );
	this->oldValue.assign( length + 1, '\0' );
	file->ReadAll ( (void*)this->oldValue.data(), length );

	this->newValue = this->oldValue;
	this->newSize = this->oldSize;
}

Project: exempi

XMPChunk::XMPChunk( ContainerChunk* parent ) : Chunk( parent, chunk_XMP , kChunk_XMP )
{
}

Project: exempi

XMPChunk::XMPChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_XMP )
{
	chunkType = chunk_XMP;
	XMP_IO* file = handler->parent->ioRef;
	XMP_Uns8 level = handler->level;

	handler->packetInfo.offset = this->oldPos + 8;
	handler->packetInfo.length = (XMP_Int32) this->oldSize - 8;

	handler->xmpPacket.reserve ( handler->packetInfo.length );
	handler->xmpPacket.assign ( handler->packetInfo.length, ' ' );
	file->ReadAll ( (void*)handler->xmpPacket.data(), handler->packetInfo.length );

	handler->containsXMP = true; // last, after all possible failure

	handler->xmpChunk = this;
}

Project: exempi

void Chunk::changesAndSize( RIFF_MetaHandler* handler )
{
	hasChange = false; // unknown chunk ==> no change, naturally
	this->newSize = this->oldSize;
}

Project: exempi

void ContainerChunk::changesAndSize( RIFF_MetaHandler* handler )
{



	bool isRIFFContainer = (this->id == kChunk_RIFF);
	bool isLISTContainer = (this->id == kChunk_LIST);
	XMP_Enforce ( isRIFFContainer | isLISTContainer );

	XMP_Index childIndex;	// Could be local to the loops, this simplifies debuging. Need a signed type!
	Chunk * currChild;

	if ( this->children.empty() ) {
		if ( isRIFFContainer) {
			this->newSize = 12;	// Keep a minimal size container.
		} else {
			this->newSize = 0;	// Will get removed from parent in outer call.
		}
		this->hasChange = true;
		return;	// Nothing more to do without children.
	}


	for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex > 0; --childIndex ) {

		currChild = this->children[childIndex];
		if ( currChild->chunkType != chunk_JUNK ) continue;

		if ( isRIFFContainer ) {
			Chunk * prevChild = this->children[childIndex-1];
			if ( prevChild->chunkType != chunk_JUNK ) continue;
			prevChild->oldSize += currChild->oldSize;
			prevChild->newSize += currChild->newSize;
			prevChild->hasChange = true;
		}

		this->children.erase ( this->children.begin() + childIndex );
		delete currChild;
		this->hasChange = true;

	}


	for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex >= 0; --childIndex ) {

		currChild = this->children[childIndex];

		++handler->level;
		currChild->changesAndSize ( handler );
		--handler->level;

		if ( (currChild->newSize == 8) || (currChild->newSize == 0) ) {	// ! The newSIze is supposed to include the header.
			this->children.erase ( this->children.begin() + childIndex );
			delete currChild;
			this->hasChange = true;
		} else {
			this->hasChange |= currChild->hasChange;
			currChild->needSizeFix = (currChild->newSize != currChild->oldSize);
			if ( currChild->needSizeFix && (currChild->newSize > currChild->oldSize) &&
				 (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) {
				if ( currChild->oldSize != 0 ) currChild->needSizeFix = false;
			}
		}

	}


	if ( isRIFFContainer ) {

		for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) {

			currChild = this->children[childIndex];
			if ( ! currChild->needSizeFix ) continue;
			currChild->needSizeFix = false;

			XMP_Int64 sizeDiff = currChild->newSize - currChild->oldSize;	// Positive for growth.
			XMP_Uns8  padSize = (currChild->newSize & 1);	// Need a pad for odd size.


			Chunk * nextChild = 0;
			if ( childIndex+1 < (XMP_Index)this->children.size() ) nextChild = this->children[childIndex+1];

			if ( (nextChild != 0) && (nextChild->chunkType == chunk_JUNK) ) {
				if ( nextChild->newSize >= (9 + sizeDiff + padSize) ) {

					nextChild->newSize -= sizeDiff;
					nextChild->newSize -= padSize;
					nextChild->hasChange = true;
					continue;

				} else if (  nextChild->newSize == (sizeDiff + padSize)  ) {

					this->children.erase ( this->children.begin() + childIndex + 1 );
					delete nextChild;
					continue;

				}
			}


			if ( (sizeDiff + padSize) <= -9 ) {
				this->children.insert ( (this->children.begin() + childIndex + 1), new JunkChunk ( NULL, ((-sizeDiff) - padSize) ) );
				continue;
			}


			XMP_Index junkIndex;
			Chunk * junkChunk = 0;
			for ( junkIndex = 0; junkIndex < (XMP_Index)this->children.size(); ++junkIndex ) {
				junkChunk = this->children[junkIndex];
				if ( junkChunk->chunkType != chunk_JUNK ) continue;
				if ( (junkChunk->newSize >= (9 + currChild->newSize + padSize)) ||
					 (junkChunk->newSize == (currChild->newSize + padSize)) ) break;
			}

			if ( junkIndex < (XMP_Index)this->children.size() ) {


				if ( junkChunk->newSize == (currChild->newSize + padSize) ) {

					this->children[junkIndex] = currChild;
					delete junkChunk;

				} else {

					XMP_Assert ( junkChunk->newSize >= (9 + currChild->newSize + padSize) );
					junkChunk->newSize -= (currChild->newSize + padSize);
					junkChunk->hasChange = true;
					this->children.insert ( (this->children.begin() + junkIndex), currChild );
					if ( junkIndex < childIndex ) ++childIndex;	// The insertion moved the current child.

				}

				if ( currChild->oldSize != 0 ) {
					this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );	// Replace the old space with junk.
				} else {
					this->children.erase ( this->children.begin() + childIndex );	// Remove the newly created chunk's old location.
					--childIndex;	// Make the next loop iteration not skip a chunk.
				}

				continue;

			}


			bool isListInfo = (currChild->id == kChunk_LIST) && (currChild->chunkType == chunk_CONTAINER) &&
							  (((ContainerChunk*)currChild)->containerType == kType_INFO);

			if ( isListInfo && (handler->riffChunks.size() > 1) &&
				 (this->id == kChunk_RIFF) && (this != handler->lastChunk) ) {

				if ( currChild->oldSize != 0 ) {
					this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );
				} else {
					this->children.erase ( this->children.begin() + childIndex );
					--childIndex;	// Make the next loop iteration not skip a chunk.
				}

				delete currChild;
				continue;

			}


			if ( (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) continue;	// Already last.

			handler->lastChunk->children.push_back( currChild );
			if ( currChild->oldSize != 0 ) {
				this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );	// Replace the old space with junk.
			} else {
				this->children.erase ( this->children.begin() + childIndex );	// Remove the newly created chunk's old location.
				--childIndex;	// Make the next loop iteration not skip a chunk.
			}

		}

	}


	this->newSize = 12;	// Start with standard container header.
	for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) {
		currChild = this->children[childIndex];
		this->newSize += currChild->newSize;
		this->newSize += (this->newSize & 1);	// Round up if odd.
	}

	XMP_Validate ( (this->newSize <= 0xFFFFFFFFLL), "No single chunk may be above 4 GB", kXMPErr_Unimplemented );

}

Project: exempi

void XMPChunk::changesAndSize( RIFF_MetaHandler* handler )
{
	XMP_Enforce( &handler->xmpPacket != 0 );
	XMP_Enforce( handler->xmpPacket.size() > 0 );
	this->newSize = 8 + handler->xmpPacket.size();

	XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure );

	this->hasChange = true;
}

Project: exempi

void ValueChunk::changesAndSize( RIFF_MetaHandler* handler )
{
	if ( this->newValue.size() != this->oldValue.size() ) {
		this->hasChange = true;
	} else if ( strncmp ( this->oldValue.c_str(), this->newValue.c_str(), this->newValue.size() ) != 0 ) {
		this->hasChange = true;
	}
}

Project: exempi

void JunkChunk::changesAndSize( RIFF_MetaHandler* handler )
{
	this->newSize = this->oldSize; // optimization at a later stage
	XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure );
	if ( this->id == kChunk_JUNQ ) this->hasChange = true;	// Force ID change to JUNK.
}

Project: exempi

chunkVectIter ContainerChunk::getChild( Chunk* needle )
{
	chunkVectIter iter;
	for( iter = this->children.begin(); iter != this->children.end(); iter++ )
	{
		Chunk* temp1 = *iter;
		Chunk* temp2 = needle;
		if ( (*iter) == needle ) return iter;
	}
	return this->children.end();
}

Project: exempi

void ContainerChunk::release()
{
	Chunk* curChunk;
	while( ! this->children.empty() )
	{
		curChunk = this->children.back();
		delete curChunk;
		this->children.pop_back();
	}
}

Project: exempi

bool ContainerChunk::removeValue( XMP_Uns32	id )
{
	valueMap* cm = &this->childmap;
	valueMapIter iter = cm->find( id );

	if( iter == cm->end() )
		return false;  //not found

	ValueChunk* propChunk = iter->second;

	chunkVect* cv = &this->children;
	chunkVectIter cvIter;
	for (cvIter = cv->begin(); cvIter != cv->end(); ++cvIter )
	{
		if ( (*cvIter)->id == id )
			break; // found!
	}
	XMP_Validate( cvIter != cv->end(), "property not found in children vector", kXMPErr_InternalFailure );
	cv->erase( cvIter );

	cm->erase( iter );

	delete propChunk;
	return true; // found and removed
}

Project: exempi

void ContainerChunk::replaceChildWithJunk( Chunk* child, bool deleteChild )
{
	chunkVectIter iter = getChild( child );
	if ( iter == this->children.end() ) {
		throw new XMP_Error(kXMPErr_InternalFailure, "replaceChildWithJunk: childChunk not found.");
	}

	*iter = new JunkChunk ( NULL, child->oldSize );
	if ( deleteChild ) delete child;

	this->hasChange = true;
}

Project: exempi

std::string Chunk::toString(XMP_Uns8 level )
{
	char buffer[256];
	snprintf( buffer, 255, "%.4s -- "
							"oldSize: 0x%.8llX,  "
							"newSize: 0x%.8llX,  "
							"oldPos: 0x%.8llX\n",
		(char*)(&this->id), this->oldSize, this->newSize, this->oldPos );
	return std::string(buffer);
}

Project: exempi

std::string ContainerChunk::toString(XMP_Uns8 level )
{
	XMP_Int64 offset= 12; // compute offsets, just for informational purposes

	char buffer[256];
	snprintf( buffer, 255, "%.4s:%.4s, "
			"oldSize: 0x%8llX, "
			"newSize: 0x%.8llX, "
			"oldPos: 0x%.8llX\n",
			(char*)(&this->id), (char*)(&this->containerType), this->oldSize, this->newSize, this->oldPos );

	std::string r(buffer);
	chunkVectIter iter;
	for( iter = this->children.begin(); iter != this->children.end(); iter++ )
	{
		char buffer[256];
		snprintf( buffer, 250, "offset 0x%.8llX", offset );
		r += std::string ( level*4, ' ' ) + std::string( buffer ) + ":" + (*iter)->toString( level + 1 );
		offset += (*iter)->newSize;
		if ( offset % 2 == 1 )
			offset++;
	}
	return std::string(r);
}

Project: exempi

void Chunk::write( RIFF_MetaHandler* handler, XMP_IO* file , bool isMainChunk )
{
	throw new XMP_Error(kXMPErr_InternalFailure, "Chunk::write never to be called for unknown chunks.");
}

Project: exempi

void ContainerChunk::write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk )
{
	if ( isMainChunk )
		file ->Rewind();

	XMP_Int64 chunkStart = file->Offset();
	XMP_Int64 chunkEnd = chunkStart + this->newSize;
	XMP_Enforce( chunkStart % 2 == 0 );
	chunkVect *rc = &this->children;

	XMP_Int64 childStart = chunkEnd;
	for ( XMP_Int32 chunkNo = (XMP_Int32)(rc->size() -1); chunkNo >= 0; chunkNo-- )
	{
		Chunk* cur = rc->at(chunkNo);

		if ( cur->newSize % 2 == 1 )
		{
			childStart--;
			file->Seek ( childStart, kXMP_SeekFromStart  );
			XIO::WriteUns8( file, 0 );
		}

		childStart-= cur->newSize;
		file->Seek ( childStart, kXMP_SeekFromStart  );
		switch ( cur->chunkType )
		{
			case chunk_GENERAL: //COULDDO enfore no change, since not write-out-able
				if ( cur->oldPos != childStart )
					XIO::Move( file, cur->oldPos, file, childStart, cur->oldSize );
				break;
			default:
				cur->write( handler, file, false );
				break;
		} // switch

	} // for
	XMP_Enforce ( chunkStart + 12 == childStart);
	file->Seek ( chunkStart, kXMP_SeekFromStart );

	XIO::WriteUns32_LE( file, this->id );
	XIO::WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above
	XIO::WriteUns32_LE( file, this->containerType );

}

Project: exempi

void XMPChunk::write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk )
{
	XIO::WriteUns32_LE( file, kChunk_XMP );
	XIO::WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above
	file->Write ( handler->xmpPacket.data(), (XMP_Int32)handler->xmpPacket.size()  );
}

Project: exempi

void ValueChunk::write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk )
{
	XIO::WriteUns32_LE( file, this->id );
	XIO::WriteUns32_LE( file, (XMP_Uns32)this->newSize - 8 );
	file->Write ( this->newValue.data() , (XMP_Int32)this->newSize - 8  );
}

Project: exempi

void JunkChunk::write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk )
{
	XIO::WriteUns32_LE( file, kChunk_JUNK );		// write JUNK, never JUNQ
	XMP_Enforce( this->newSize < 0xFFFFFFFF );
	XMP_Enforce( this->newSize >= 8 );			// minimum size of any chunk
	XMP_Uns32 innerSize = (XMP_Uns32)this->newSize - 8;
	XIO::WriteUns32_LE( file, innerSize );

	while ( innerSize > kZeroBufferSize64K )
	{
		file->Write ( kZeroes64K , kZeroBufferSize64K  );
		innerSize -= kZeroBufferSize64K;
	}
	file->Write ( kZeroes64K , innerSize  );
}

Project: exempi

ContainerChunk::~ContainerChunk()
{
	this->release(); // free resources
}