Commit message: UriQuery.c: Fix out-of-bounds-write in ComposeQuery and ...Ex

Reported by Google Autofuzz team
CVE: CVE-2018-19198
CWE: CWE-787
Vulnerability description: An issue was discovered in uriparser before 0.9.0. UriQuery.c allows an out-of-bounds write via a uriComposeQuery* or uriComposeQueryEx* function because the '&' character is mishandled in certain contexts.

Project: uriparser
File: UriQuery.c

int URI_FUNC(ComposeQueryEngine)(URI_CHAR * dest,
		const URI_TYPE(QueryList) * queryList,
		int maxChars, int * charsWritten, int * charsRequired,
		UriBool spaceToPlus, UriBool normalizeBreaks) {
	UriBool firstItem = URI_TRUE;
	int ampersandLen = 0;  /* increased to 1 from second item on */
	URI_CHAR * write = dest;

	/* Subtract terminator */
	if (dest == NULL) {
		*charsRequired = 0;
	} else {
		maxChars--;
	}
			
	while (queryList != NULL) {
		const URI_CHAR * const key = queryList->key;
		const URI_CHAR * const value = queryList->value;
		const int worstCase = (normalizeBreaks == URI_TRUE ? 6 : 3);
		const int keyLen = (key == NULL) ? 0 : (int)URI_STRLEN(key);
		const int keyRequiredChars = worstCase * keyLen;
		const int valueLen = (value == NULL) ? 0 : (int)URI_STRLEN(value);
		const int valueRequiredChars = worstCase * valueLen;

		if (dest == NULL) {
			if (firstItem == URI_TRUE) {
				ampersandLen = 1;
				firstItem = URI_FALSE;
			}

			(*charsRequired) += ampersandLen + keyRequiredChars + ((value == NULL)
						? 0
						: 1 + valueRequiredChars);
		} else {
			URI_CHAR * afterKey;

			if ((write - dest) + ampersandLen + keyRequiredChars > maxChars) {
				return URI_ERROR_OUTPUT_TOO_LARGE;
			}
 
 			/* Copy key */
 			if (firstItem == URI_TRUE) {
				ampersandLen = 1;
 				firstItem = URI_FALSE;
 			} else {
 				write[0] = _UT('&');
				write++;
			}
			afterKey = URI_FUNC(EscapeEx)(key, key + keyLen,
					write, spaceToPlus, normalizeBreaks);
			write += (afterKey - write);

			if (value != NULL) {
				URI_CHAR * afterValue;

				if ((write - dest) + 1 + valueRequiredChars > maxChars) {
					return URI_ERROR_OUTPUT_TOO_LARGE;
				}

				/* Copy value */
				write[0] = _UT('=');
				write++;
				afterValue = URI_FUNC(EscapeEx)(value, value + valueLen,
						write, spaceToPlus, normalizeBreaks);
				write += (afterValue - write);
			}
		}

		queryList = queryList->next;
	}

	if (dest != NULL) {
		write[0] = _UT('\0');
		if (charsWritten != NULL) {
			*charsWritten = (int)(write - dest) + 1; /* .. for terminator */
		}
	}

	return URI_SUCCESS;
}

Project: uriparser
File: test.cpp

	UriSuite() {
		TEST_ADD(UriSuite::testDistinction)
		TEST_ADD(UriSuite::testIpFour)
		TEST_ADD(UriSuite::testIpSixPass)
		TEST_ADD(UriSuite::testIpSixFail)
		TEST_ADD(UriSuite::testUri)
		TEST_ADD(UriSuite::testUriUserInfoHostPort1)
		TEST_ADD(UriSuite::testUriUserInfoHostPort2)
		TEST_ADD(UriSuite::testUriUserInfoHostPort22_Bug1948038)
		TEST_ADD(UriSuite::testUriUserInfoHostPort23_Bug3510198_1)
		TEST_ADD(UriSuite::testUriUserInfoHostPort23_Bug3510198_2)
		TEST_ADD(UriSuite::testUriUserInfoHostPort23_Bug3510198_3)
		TEST_ADD(UriSuite::testUriUserInfoHostPort23_Bug3510198_4)
		TEST_ADD(UriSuite::testUriUserInfoHostPort23_Bug3510198_related_1)
		TEST_ADD(UriSuite::testUriUserInfoHostPort23_Bug3510198_related_12)
		TEST_ADD(UriSuite::testUriUserInfoHostPort23_Bug3510198_related_2)
		TEST_ADD(UriSuite::testUriUserInfoHostPort3)
		TEST_ADD(UriSuite::testUriUserInfoHostPort4)
		TEST_ADD(UriSuite::testUriUserInfoHostPort5)
		TEST_ADD(UriSuite::testUriUserInfoHostPort6)
		TEST_ADD(UriSuite::testUriHostRegname)
		TEST_ADD(UriSuite::testUriHostIpFour1)
		TEST_ADD(UriSuite::testUriHostIpFour2)
		TEST_ADD(UriSuite::testUriHostIpSix1)
		TEST_ADD(UriSuite::testUriHostIpSix2)
		TEST_ADD(UriSuite::testUriHostIpFuture)
		TEST_ADD(UriSuite::testUriHostEmpty)
		TEST_ADD(UriSuite::testUriComponents)
		TEST_ADD(UriSuite::testUriComponents_Bug20070701)
		TEST_ADD(UriSuite::testEscaping)
		TEST_ADD(UriSuite::testUnescaping)
		TEST_ADD(UriSuite::testTrailingSlash)
		TEST_ADD(UriSuite::testAddBase)
		TEST_ADD(UriSuite::testToString)
		TEST_ADD(UriSuite::testToString_Bug1950126)
		TEST_ADD(UriSuite::testToStringCharsRequired)
		TEST_ADD(UriSuite::testToStringCharsRequired)
		TEST_ADD(UriSuite::testNormalizeSyntaxMaskRequired)
		TEST_ADD(UriSuite::testNormalizeSyntax)
		TEST_ADD(UriSuite::testNormalizeSyntaxComponents)
		TEST_ADD(UriSuite::testNormalizeCrash_Bug20080224)
		TEST_ADD(UriSuite::testFilenameUriConversion)
		TEST_ADD(UriSuite::testCrash_FreeUriMembers_Bug20080116)
		TEST_ADD(UriSuite::testCrash_Report2418192)
		TEST_ADD(UriSuite::testPervertedQueryString);
		TEST_ADD(UriSuite::testQueryStringEndingInEqualSign_NonBug32);
		TEST_ADD(UriSuite::testCrash_MakeOwner_Bug20080207)
 		TEST_ADD(UriSuite::testQueryList)
 		TEST_ADD(UriSuite::testQueryListPair)
 		TEST_ADD(UriSuite::testQueryDissection_Bug3590761)
		TEST_ADD(UriSuite::testQueryCompositionMathWrite_GoogleAutofuzz113244572)
 		TEST_ADD(UriSuite::testFreeCrash_Bug20080827)
 		TEST_ADD(UriSuite::testParseInvalid_Bug16)
 		TEST_ADD(UriSuite::testRangeComparison)
		TEST_ADD(UriSuite::testRangeComparison_RemoveBaseUri_Issue19)
		TEST_ADD(UriSuite::testEquals)
		TEST_ADD(UriSuite::testHostTextTermination_Issue15)
	}