Blame SOURCES/exiv2-CVE-2021-32617.patch

e4b251
From c261fbaa2567687eec6a595d3016212fd6ae648d Mon Sep 17 00:00:00 2001
e4b251
From: Kevin Backhouse <kevinbackhouse@github.com>
e4b251
Date: Sun, 16 May 2021 15:05:08 +0100
e4b251
Subject: [PATCH] Fix quadratic complexity performance bug.
e4b251
e4b251
---
e4b251
 xmpsdk/src/XMPMeta-Parse.cpp | 57 +++++++++++++++++++++++-------------
e4b251
 1 file changed, 36 insertions(+), 21 deletions(-)
e4b251
e4b251
diff --git a/xmpsdk/src/XMPMeta-Parse.cpp b/xmpsdk/src/XMPMeta-Parse.cpp
e4b251
index 9f66fe8..f9c37d7 100644
e4b251
--- a/xmpsdk/src/XMPMeta-Parse.cpp
e4b251
+++ b/xmpsdk/src/XMPMeta-Parse.cpp
e4b251
@@ -976,12 +976,26 @@ ProcessUTF8Portion ( XMLParserAdapter * xmlParser,
e4b251
 {
e4b251
 	const XMP_Uns8 * bufEnd = buffer + length;
e4b251
 	
e4b251
-	const XMP_Uns8 * spanStart = buffer;
e4b251
 	const XMP_Uns8 * spanEnd;
e4b251
-		
e4b251
-	for ( spanEnd = spanStart; spanEnd < bufEnd; ++spanEnd ) {
e4b251
 
e4b251
-		if ( (0x20 <= *spanEnd) && (*spanEnd <= 0x7E) && (*spanEnd != '&') ) continue;	// A regular ASCII character.
e4b251
+	// `buffer` is copied into this std::string. If `buffer` only
e4b251
+	// contains valid UTF-8 and no escape characters, then the copy
e4b251
+	// will be identical to the original, but invalid characters are
e4b251
+	// replaced - usually with a space character.  This std::string was
e4b251
+	// added as a performance fix for:
e4b251
+	// https://github.com/Exiv2/exiv2/security/advisories/GHSA-w8mv-g8qq-36mj
e4b251
+	// Previously, the code was repeatedly calling
e4b251
+	// `xmlParser->ParseBuffer()`, which turned out to have quadratic
e4b251
+	// complexity, because expat kept reparsing the entire string from
e4b251
+	// the beginning.
e4b251
+	std::string copy;
e4b251
+
e4b251
+	for ( spanEnd = buffer; spanEnd < bufEnd; ++spanEnd ) {
e4b251
+
e4b251
+		if ( (0x20 <= *spanEnd) && (*spanEnd <= 0x7E) && (*spanEnd != '&') ) {
e4b251
+			copy.push_back(*spanEnd);
e4b251
+			continue;	// A regular ASCII character.
e4b251
+		}
e4b251
 
e4b251
 		if ( *spanEnd >= 0x80 ) {
e4b251
 		
e4b251
@@ -992,21 +1006,20 @@ ProcessUTF8Portion ( XMLParserAdapter * xmlParser,
e4b251
 			if ( uniLen > 0 ) {
e4b251
 
e4b251
 				// A valid UTF-8 character, keep it as-is.
e4b251
+				copy.append((const char*)spanEnd, uniLen);
e4b251
 				spanEnd += uniLen - 1;	// ! The loop increment will put back the +1.
e4b251
 
e4b251
 			} else if ( (uniLen < 0) && (! last) ) {
e4b251
 
e4b251
 				// Have a partial UTF-8 character at the end of the buffer and more input coming.
e4b251
-				xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false );
e4b251
+				xmlParser->ParseBuffer ( copy.c_str(), copy.size(), false );
e4b251
 				return (spanEnd - buffer);
e4b251
 
e4b251
 			} else {
e4b251
 
e4b251
 				// Not a valid UTF-8 sequence. Replace the first byte with the Latin-1 equivalent.
e4b251
-				xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false );
e4b251
 				const char * replacement = kReplaceLatin1 [ *spanEnd - 0x80 ];
e4b251
-				xmlParser->ParseBuffer ( replacement, strlen ( replacement ), false );
e4b251
-				spanStart = spanEnd + 1;	// ! The loop increment will do "spanEnd = spanStart".
e4b251
+				copy.append ( replacement );
e4b251
 
e4b251
 			}
e4b251
 		
e4b251
@@ -1014,11 +1027,12 @@ ProcessUTF8Portion ( XMLParserAdapter * xmlParser,
e4b251
 
e4b251
 			// Replace ASCII controls other than tab, LF, and CR with a space.
e4b251
 
e4b251
-			if ( (*spanEnd == kTab) || (*spanEnd == kLF) || (*spanEnd == kCR) ) continue;
e4b251
+			if ( (*spanEnd == kTab) || (*spanEnd == kLF) || (*spanEnd == kCR) ) {
e4b251
+				copy.push_back(*spanEnd);
e4b251
+				continue;
e4b251
+			}
e4b251
 
e4b251
-			xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false );
e4b251
-			xmlParser->ParseBuffer ( " ", 1, false );
e4b251
-			spanStart = spanEnd + 1;	// ! The loop increment will do "spanEnd = spanStart".
e4b251
+			copy.push_back(' ');
e4b251
 		
e4b251
 		} else {
e4b251
 		
e4b251
@@ -1030,18 +1044,21 @@ ProcessUTF8Portion ( XMLParserAdapter * xmlParser,
e4b251
 			if ( escLen < 0 ) {
e4b251
 
e4b251
 				// Have a partial numeric escape in this buffer, wait for more input.
e4b251
-				if ( last ) continue;	// No more buffers, not an escape, absorb as normal input.
e4b251
-				xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false );
e4b251
+				if ( last ) {
e4b251
+					copy.push_back('&';;
e4b251
+					continue;	// No more buffers, not an escape, absorb as normal input.
e4b251
+				}
e4b251
+				xmlParser->ParseBuffer ( copy.c_str(), copy.size(), false );
e4b251
 				return (spanEnd - buffer);
e4b251
 
e4b251
 			} else if ( escLen > 0 ) {
e4b251
 
e4b251
 				// Have a complete numeric escape to replace.
e4b251
-				xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false );
e4b251
-				xmlParser->ParseBuffer ( " ", 1, false );
e4b251
-				spanStart = spanEnd + escLen;
e4b251
-				spanEnd = spanStart - 1;	// ! The loop continuation will increment spanEnd!
e4b251
+				copy.push_back(' ');
e4b251
+				spanEnd = spanEnd + escLen - 1;	// ! The loop continuation will increment spanEnd!
e4b251
 
e4b251
+			} else {
e4b251
+				copy.push_back('&';;
e4b251
 			}
e4b251
 
e4b251
 		}
e4b251
@@ -1050,8 +1067,8 @@ ProcessUTF8Portion ( XMLParserAdapter * xmlParser,
e4b251
 	
e4b251
 	XMP_Assert ( spanEnd == bufEnd );
e4b251
 
e4b251
-	if ( spanStart < bufEnd ) xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false );
e4b251
-	if ( last ) xmlParser->ParseBuffer ( " ", 1, true );
e4b251
+	copy.push_back(' ');
e4b251
+	xmlParser->ParseBuffer ( copy.c_str(), copy.size(), true );
e4b251
 	
e4b251
 	return length;
e4b251