Commit message: Fix bug #72241: get_icu_value_internal out-of-bounds read
CVE: CVE-2016-5093
CWE: CWE-125
Vulnerability description: The get_icu_value_internal function in ext/intl/locale/locale_methods.c in PHP before 5.5.36, 5.6.x before 5.6.22, and 7.x before 7.0.7 does not ensure the presence of a '\0' character, which allows remote attackers to cause a denial of service (out-of-bounds read) or possibly have unspecified other impact via a crafted locale_get_primary_language call.

Project: php-src
File: locale_methods.c

PHP_FUNCTION(locale_accept_from_http)
{
	UEnumeration *available;
	char *http_accept = NULL;
	int http_accept_len;
	UErrorCode status = 0;
	int len;
	char resultLocale[INTL_MAX_LOCALE_LEN+1];
	UAcceptResult outResult;

	if(zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &http_accept, &http_accept_len) == FAILURE)
	{
		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
 		"locale_accept_from_http: unable to parse input parameters", 0 TSRMLS_CC );
 		RETURN_FALSE;
 	}
 	available = ures_openAvailableLocales(NULL, &status);
 	INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to retrieve locale list");
	len = uloc_acceptLanguageFromHTTP(resultLocale, INTL_MAX_LOCALE_LEN, 
 						&outResult, http_accept, available, &status);
 	uenum_close(available);
 	INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to find acceptable locale");
	if (len < 0 || outResult == ULOC_ACCEPT_FAILED) {
		RETURN_FALSE;
	}
	RETURN_STRINGL(resultLocale, len, 1);
}

Project: php-src
File: locale_methods.c

static char* get_icu_value_internal( const char* loc_name , char* tag_name, int* result , int fromParseLocale)
{
	char*		tag_value	= NULL;
	int32_t     	tag_value_len   = 512;

	int		singletonPos   	= 0;
	char*       	mod_loc_name	= NULL;
	int 		grOffset	= 0;

	int32_t     	buflen          = 512;
	UErrorCode  	status          = U_ZERO_ERROR;


	if( strcmp(tag_name, LOC_CANONICALIZE_TAG) != 0 ){
		/* Handle  grandfathered languages */
		grOffset =  findOffset( LOC_GRANDFATHERED , loc_name );
		if( grOffset >= 0 ){
			if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
				return estrdup(loc_name);
			} else {
				/* Since Grandfathered , no value , do nothing , retutn NULL */
				return NULL;
			}
		}

	if( fromParseLocale==1 ){
		/* Handle singletons */
		if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
			if( strlen(loc_name)>1 && (isIDPrefix(loc_name) == 1) ){
				return estrdup(loc_name);
 			}
 		}
 
		singletonPos = getSingletonPos( loc_name );	
 		if( singletonPos == 0){
 			/* singleton at start of script, region , variant etc.
 			 * or invalid singleton at start of language */
			return NULL;
		} else if( singletonPos > 0 ){
			/* singleton at some position except at start
			 * strip off the singleton and rest of the loc_name */
			mod_loc_name = estrndup ( loc_name , singletonPos-1);
		}
	} /* end of if fromParse */

 	} /* end of if != LOC_CANONICAL_TAG */
 
 	if( mod_loc_name == NULL){
		mod_loc_name = estrdup(loc_name );	
 	}
 
 	/* Proceed to ICU */
    do{
		tag_value = erealloc( tag_value , buflen  );
		tag_value_len = buflen;

		if( strcmp(tag_name , LOC_SCRIPT_TAG)==0 ){
			buflen = uloc_getScript ( mod_loc_name ,tag_value , tag_value_len , &status);
		}
		if( strcmp(tag_name , LOC_LANG_TAG )==0 ){
			buflen = uloc_getLanguage ( mod_loc_name ,tag_value , tag_value_len , &status);
		}
		if( strcmp(tag_name , LOC_REGION_TAG)==0 ){
			buflen = uloc_getCountry ( mod_loc_name ,tag_value , tag_value_len , &status);
		}
		if( strcmp(tag_name , LOC_VARIANT_TAG)==0 ){
			buflen = uloc_getVariant ( mod_loc_name ,tag_value , tag_value_len , &status);
		}
		if( strcmp(tag_name , LOC_CANONICALIZE_TAG)==0 ){
			buflen = uloc_canonicalize ( mod_loc_name ,tag_value , tag_value_len , &status);
		}

 		if( U_FAILURE( status ) ) {
 			if( status == U_BUFFER_OVERFLOW_ERROR ) {
 				status = U_ZERO_ERROR;
 				continue;
 			}
 
			/* Error in retriving data */
			*result = 0;
			if( tag_value ){
				efree( tag_value );
			}
			if( mod_loc_name ){
				efree( mod_loc_name);
			}
			return NULL;
		}
	} while( buflen > tag_value_len );

	if(  buflen ==0 ){
		/* No value found */
		*result = -1;
		if( tag_value ){
			efree( tag_value );
		}
		if( mod_loc_name ){
			efree( mod_loc_name);
		}
		return NULL;
	} else {
		*result = 1;
	}

	if( mod_loc_name ){
		efree( mod_loc_name);
	}
	return tag_value;
}