Commit message: None
CVE: CVE-2017-18238
CWE: CWE-835
Vulnerability description: An issue was discovered in Exempi before 2.4.4. The TradQT_Manager::ParseCachedBoxes function in XMPFiles/source/FormatSupport/QuickTime_Support.cpp allows remote attackers to cause a denial of service (infinite loop) via crafted XMP data in a .qt file.

Project: exempi

bool ConvertFromMacLang ( const std::string & macValue, XMP_Uns16 macLang, std::string * utf8Value )
{
	utf8Value->erase();
	if ( ! IsMacLangKnown ( macLang ) ) return false;
	
	#if XMP_MacBuild
		XMP_Uns16 macScript = GetMacScript ( macLang );
		ReconcileUtils::MacEncodingToUTF8 ( macScript, macLang, (XMP_Uns8*)macValue.c_str(), macValue.size(), utf8Value );
	#elif XMP_UNIXBuild
		MacRomanToUTF8 ( macValue, utf8Value );
	#elif XMP_WinBuild
		UINT winCP = GetWinCP ( macLang );
		ReconcileUtils::WinEncodingToUTF8 ( winCP, (XMP_Uns8*)macValue.c_str(), macValue.size(), utf8Value );
    #elif XMP_iOSBuild
        XMP_Uns32 iosEncCF = GetIOSEncodingCF(macLang);
        ReconcileUtils::IOSConvertEncoding(iosEncCF, kCFStringEncodingUTF8, (XMP_Uns8*)macValue.c_str(), macValue.size(), utf8Value);
#endif
	
	return true;

}	// ConvertFromMacLang

Project: exempi

bool ConvertToMacLang ( const std::string & utf8Value, XMP_Uns16 macLang, std::string * macValue )
{
	macValue->erase();
	if ( macLang == kNoMacLang ) macLang = 0;	// *** Zero is English, ought to use the "active" OS lang.
	if ( ! IsMacLangKnown ( macLang ) ) return false;

	#if XMP_MacBuild
		XMP_Uns16 macScript = GetMacScript ( macLang );
		ReconcileUtils::UTF8ToMacEncoding ( macScript, macLang, (XMP_Uns8*)utf8Value.c_str(), utf8Value.size(), macValue );
	#elif XMP_UNIXBuild
		UTF8ToMacRoman ( utf8Value, macValue );
	#elif XMP_WinBuild
		UINT winCP = GetWinCP ( macLang );
		ReconcileUtils::UTF8ToWinEncoding ( winCP, (XMP_Uns8*)utf8Value.c_str(), utf8Value.size(), macValue );
    #elif XMP_iOSBuild
        XMP_Uns32 iosEncCF = GetIOSEncodingCF(macLang);
        ReconcileUtils::IOSConvertEncoding(kCFStringEncodingUTF8, iosEncCF, (XMP_Uns8*)utf8Value.c_str(), utf8Value.size(), macValue);
    #endif
	
	return true;

}	// ConvertToMacLang

Project: exempi

void TradQT_Manager::ExportSimpleXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr prop,
									   bool createWithZeroLang /* = false */  )
{
	std::string xmpValue, macValue;

	InfoMapPos infoPos = this->parsedBoxes.find ( id );
	bool qtFound = (infoPos != this->parsedBoxes.end()) && (! infoPos->second.values.empty());

	bool xmpFound = xmp.GetProperty ( ns, prop, &xmpValue, 0 );
	if ( (! xmpFound) || (xmpValue.empty()) ) {
		if ( qtFound ) {
			this->parsedBoxes.erase ( infoPos );
			this->changed = true;
		}
		return;
	}
	
	XMP_Assert ( xmpFound );
	if ( ! qtFound ) {
		if ( ! createWithZeroLang ) return;
		infoPos = this->parsedBoxes.insert ( this->parsedBoxes.end(),
											 InfoMap::value_type ( id, ParsedBoxInfo ( id ) ) );
		ValueVector * newValues = &infoPos->second.values;
		newValues->push_back ( ValueInfo() );
		ValueInfo * newValue = &newValues->back();
		newValue->macLang = 0;	// Happens to be langEnglish.
		newValue->xmpLang = kMacToXMPLang_0_94[0];
		this->changed = infoPos->second.changed = true;
	}
		
	ValueInfo * qtItem = &infoPos->second.values[0];	// ! Use the first QT entry.
	if ( ! IsMacLangKnown ( qtItem->macLang ) ) return;
	
	bool convertOK = ConvertToMacLang ( xmpValue, qtItem->macLang, &macValue );
	if ( convertOK && (macValue != qtItem->macValue) ) {
		qtItem->macValue = macValue;
		this->changed = infoPos->second.changed = true;
	}
	
}	// TradQT_Manager::ExportSimpleXMP

Project: exempi

static XMP_Uns32 GetIOSEncodingCF ( XMP_Uns16 macLang )
{
	XMP_Uns32 encCF = kCFStringEncodingInvalidId;
	
	if ( macLang <= 94 ) encCF = kMacToIOSEncodingCF_0_94[macLang];
	
	if ( encCF == kCFStringEncodingInvalidId || !CFStringIsEncodingAvailable(encCF)) {
		XMP_Uns16 macScript = GetMacScript ( macLang );
		if ( macScript != kNoMacScript ) encCF = kMacScriptToIOSEncodingCF[macScript];
	}
	
	return encCF;
	
}	// GetIOSEncodingCF

Project: exempi

static XMP_Uns16 GetMacLang ( std::string * xmpLang )
{
	if ( *xmpLang == "" ) return kNoMacLang;
	
	size_t hyphenPos = xmpLang->find ( '-' );	// Make sure the XMP language is "generic".
	if ( hyphenPos != std::string::npos ) xmpLang->erase ( hyphenPos );
	
	for ( XMP_Uns16 i = 0; i <= 94; ++i ) {	// Using std::map would be faster.
		if ( *xmpLang == kMacToXMPLang_0_94[i] ) return i;
	}
	
	for ( XMP_Uns16 i = 128; i <= 151; ++i ) {	// Using std::map would be faster.
		if ( *xmpLang == kMacToXMPLang_128_151[i-128] ) return i;
	}
	
	return kNoMacLang;
	
}	// GetMacLang

Project: exempi

static XMP_Uns16 GetMacScript ( XMP_Uns16 macLang )
{
	XMP_Uns16 macScript = kNoMacScript;
	
	if ( macLang <= 94 ) {
		macScript = kMacLangToScript_0_94[macLang];
	} else if ( (128 <= macLang) && (macLang <= 151) ) {
		macScript = kMacLangToScript_128_151[macLang-128];
	}
	
	return macScript;
	
}	// GetMacScript

Project: exempi

static UINT GetWinCP ( XMP_Uns16 macLang )
{
	UINT winCP = 0;
	
	if ( macLang <= 94 ) winCP = kMacToWinCP_0_94[macLang];
	
	if ( winCP == 0 ) {
		XMP_Uns16 macScript = GetMacScript ( macLang );
		if ( macScript != kNoMacScript ) winCP = kMacScriptToWinCP[macScript];
	}
	
	return winCP;
	
}	// GetWinCP

Project: exempi

static XMP_StringPtr GetXMPLang ( XMP_Uns16 macLang )
{
	XMP_StringPtr xmpLang = "";
	
	if ( macLang <= 94 ) {
		xmpLang = kMacToXMPLang_0_94[macLang];
	} else if ( (128 <= macLang) && (macLang <= 151) ) {
		xmpLang = kMacToXMPLang_128_151[macLang-128];
	}
	
	return xmpLang;
	
}	// GetXMPLang

Project: exempi

bool TradQT_Manager::ImportLangAltXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) const
{

	try {

		InfoMapCPos infoPos = this->parsedBoxes.find ( id );
		if ( infoPos == this->parsedBoxes.end() ) return false;
		if ( infoPos->second.values.empty() ) return false;	// Quit now if there are no values.
		
		XMP_OptionBits flags;
		bool xmpExists = xmp->GetProperty ( ns, langArray, 0, &flags );
		if ( ! xmpExists ) {
			xmp->SetProperty ( ns, langArray, 0, kXMP_PropArrayIsAltText );
		} else if ( ! XMP_ArrayIsAltText ( flags ) ) {
			XMP_Throw ( "TradQT_Manager::ImportLangAltXMP - XMP array must be AltText", kXMPErr_BadParam );
		}
	
		
		bool haveMappings = false;
		const ValueVector & qtValues = infoPos->second.values;
		
		for ( size_t i = 0, limit = qtValues.size(); i < limit; ++i ) {
			const ValueInfo & qtItem = qtValues[i];
			if ( *qtItem.xmpLang == 0 ) continue;	// Only do known mappings in the loop.
			haveMappings |= this->ImportLangItem ( qtItem, xmp, ns, langArray );
		}
		
		if ( ! haveMappings ) {
			haveMappings = this->ImportLangItem ( qtValues[0], xmp, ns, langArray );	// ! No xmpLang implies "x-default".
		}
	
		return haveMappings;
	
	} catch ( ... ) {

		return false;	// Don't let one failure abort other imports.	
	
	}

}	// TradQT_Manager::ImportLangAltXMP

Project: exempi

bool TradQT_Manager::ImportSimpleXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr prop ) const
{

	try {

		InfoMapCPos infoPos = this->parsedBoxes.find ( id );
		if ( infoPos == this->parsedBoxes.end() ) return false;
		if ( infoPos->second.values.empty() ) return false;
		
		std::string xmpValue, tempValue;
		XMP_OptionBits flags;
		bool xmpExists = xmp->GetProperty ( ns, prop, &xmpValue, &flags );
		if ( xmpExists && (! XMP_PropIsSimple ( flags )) ) {
			XMP_Throw ( "TradQT_Manager::ImportSimpleXMP - XMP property must be simple", kXMPErr_BadParam );
		}
		
		bool convertOK;
		const ValueInfo & qtItem = infoPos->second.values[0];	// ! Use the first QT entry.
	
		if ( xmpExists ) {
			convertOK = ConvertToMacLang ( xmpValue, qtItem.macLang, &tempValue );
			if ( ! convertOK ) return false;	// throw?
			if ( tempValue == qtItem.macValue ) return false;	// QT value matches back converted XMP value.
		}
	
		convertOK = ConvertFromMacLang ( qtItem.macValue, qtItem.macLang, &tempValue );
		if ( ! convertOK ) return false;	// throw?
		xmp->SetProperty ( ns, prop, tempValue.c_str() );
		return true;
	
	} catch ( ... ) {

		return false;	// Don't let one failure abort other imports.	
	
	}
		
}	// TradQT_Manager::ImportSimpleXMP

Project: exempi

static inline bool IsMacLangKnown ( XMP_Uns16 macLang )
{
	XMP_Uns16 macScript = GetMacScript ( macLang );
	if ( macScript == kNoMacScript ) return false;

	#if XMP_UNIXBuild
		if ( macScript != smRoman ) return false;
	#elif XMP_WinBuild
		if ( GetWinCP(macLang) == 0 ) return false;
	#endif
	
	return true;

}	// IsMacLangKnown

Project: exempi

static void MacRomanToUTF8 ( const std::string & macRoman, std::string * utf8 )
{
	utf8->erase();
	
	for ( XMP_Uns8* chPtr = (XMP_Uns8*)macRoman.c_str(); *chPtr != 0; ++chPtr ) {	// ! Don't trust that char is unsigned.
		if ( *chPtr < 0x80 ) {
			(*utf8) += (char)*chPtr;
		} else {
			(*utf8) += kMacRomanUTF8[(*chPtr)-0x80];
		}
	}

}	// MacRomanToUTF8

Project: exempi

static void UTF8ToMacRoman ( const std::string & utf8, std::string * macRoman )
{
	macRoman->erase();
	bool inNonMRSpan = false;
	
	for ( const XMP_Uns8 * chPtr = (XMP_Uns8*)utf8.c_str(); *chPtr != 0; ++chPtr ) {	// ! Don't trust that char is unsigned.
		if ( *chPtr < 0x80 ) {
			(*macRoman) += (char)*chPtr;
			inNonMRSpan = false;
		} else {
			XMP_Uns32 cp = GetCodePoint ( &chPtr );
			--chPtr;	// Make room for the loop increment.
			XMP_Uns8  mr;
			for ( mr = 0; (mr < 0x80) && (cp != kMacRomanCP[mr]); ++mr ) {};	// Using std::map would be faster.
			if ( mr < 0x80 ) {
				(*macRoman) += (char)(mr+0x80);
				inNonMRSpan = false;
			} else if ( ! inNonMRSpan ) {
				(*macRoman) += '?';
				inNonMRSpan = true;
			}
		}
	}

}	// UTF8ToMacRoman

Project: exempi

void TradQT_Manager::UpdateChangedBoxes ( MOOV_Manager * moovMgr )
{
	MOOV_Manager::BoxInfo udtaInfo;
	MOOV_Manager::BoxRef  udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo );
	XMP_Assert ( (udtaRef != 0) || (udtaInfo.childCount == 0) );
	
	if ( udtaRef != 0 ) {	// Might not have been a moov/udta box in the parse.

	
		for ( XMP_Uns32 ordinal = udtaInfo.childCount; ordinal > 0; --ordinal ) {	// ! Go backwards because of deletions.
	
			MOOV_Manager::BoxInfo currInfo;
			MOOV_Manager::BoxRef  currRef = moovMgr->GetNthChild ( udtaRef, (ordinal-1), &currInfo );
			if ( currRef == 0 ) break;	// Sanity check, should not happen.
			if ( (currInfo.boxType >> 24) != 0xA9 ) continue;
			if ( currInfo.contentSize < 2+2+1 ) continue;	// These were skipped by ParseCachedBoxes.
			
			InfoMapPos infoPos = this->parsedBoxes.find ( currInfo.boxType );
			if ( infoPos == this->parsedBoxes.end() ) moovMgr->DeleteNthChild ( udtaRef, (ordinal-1) );
		
		}
	
	}
	
	
	InfoMapCPos infoPos = this->parsedBoxes.begin();
	InfoMapCPos infoEnd = this->parsedBoxes.end();
	
	for ( ; infoPos != infoEnd; ++infoPos ) {

		ParsedBoxInfo * qtItem = (ParsedBoxInfo*) &infoPos->second;
		if ( ! qtItem->changed ) continue;
		qtItem->changed = false;
		
		XMP_Uns32 qtTotalSize = 0;	// Total size of the QT values, ignoring empty values.
		for ( size_t i = 0, limit = qtItem->values.size(); i < limit; ++i ) {
			if ( ! qtItem->values[i].macValue.empty() ) {
				if ( qtItem->values[i].macValue.size() > 0xFFFF ) qtItem->values[i].macValue.erase ( 0xFFFF );
				qtTotalSize += (XMP_Uns32)(2+2 + qtItem->values[i].macValue.size());
			}
		}
		
		if ( udtaRef == 0 ) {	// Might not have been a moov/udta box in the parse.
			moovMgr->SetBox ( "moov/udta", 0, 0 );
			udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo );
			XMP_Assert ( udtaRef != 0 );
		}
		
		if ( qtTotalSize == 0 ) {
		
			moovMgr->DeleteTypeChild ( udtaRef, qtItem->id );
		
		} else {
		
			
			RawDataBlock fullValue;
			fullValue.assign ( qtTotalSize, 0 );
			XMP_Uns8 * valuePtr = &fullValue[0];
		
			for ( size_t i = 0, limit = qtItem->values.size(); i < limit; ++i ) {
				XMP_Assert ( qtItem->values[i].macValue.size() <= 0xFFFF );
				XMP_Uns16 textSize = (XMP_Uns16)qtItem->values[i].macValue.size();
				if ( textSize == 0 ) continue;
				PutUns16BE ( textSize, valuePtr );									valuePtr += 2;
				PutUns16BE ( qtItem->values[i].macLang, valuePtr );					valuePtr += 2;
				memcpy ( valuePtr, qtItem->values[i].macValue.c_str(), textSize );	valuePtr += textSize;
			}
			

			MOOV_Manager::BoxInfo itemInfo;
			MOOV_Manager::BoxRef  itemRef = moovMgr->GetTypeChild ( udtaRef, qtItem->id, &itemInfo );
			
			if ( itemRef != 0 ) {
				moovMgr->SetBox ( itemRef, &fullValue[0], qtTotalSize );
			} else {
				moovMgr->AddChildBox ( udtaRef, qtItem->id, &fullValue[0], qtTotalSize );
			}
			
		}

	}

}	// TradQT_Manager::UpdateChangedBoxes