CVE-2016-5093 (C, published 2016-08-07T10:59Z)
Description: value: 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.
Severity: HIGH (CVSS v3 base score: 8.6)
Weakness: CWE-125 \u2014 Out-of-bounds Read
The product reads data past the end, or before the beginning, of the intended buffer.
Repository: https://github.com/php/php-src
Files changed:
ext/intl/locale/locale_methods.c
ext/intl/tests/bug72241.phpt
Commit message: Fix bug #72241: get_icu_value_internal out-of-bounds read


Vulnerable code:
	"cel-gaulish",		"en-GB-oed",		"i-ami", 		
	"i-bnn",		"i-default",		"i-enochian",	
	"i-mingo",		"i-pwn", 		"i-tao", 
	"zh-min-nan", 		"zh-wuu", 		"zh-xiang",	
*  This is in sync with the array LOC_GRANDFATHERED	 
	"nv", 			"nb",			"nn",			
{ 
* returns the position of next token for lookup 
* strtokr equivalent search for token in reverse direction 
	
* returns the position of a singleton if present 
	
		
	int   len=0;	
	zend_alter_ini_entry(LOCALE_INI_NAME, sizeof(LOCALE_INI_NAME), locale_name, len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);	
* Gets the value from ICU 
		singletonPos = getSingletonPos( loc_name );	
		mod_loc_name = estrdup(loc_name );	
static void get_icu_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS) 
/* {{{ proto static string Locale::getScript($locale) 
 * gets the script for the $locale 
/* {{{ proto static string locale_get_script($locale) 
 * gets the script for the $locale 
PHP_FUNCTION( locale_get_script ) 
/* {{{ proto static string Locale::getRegion($locale) 
 * gets the region for the $locale 
/* {{{ proto static string locale_get_region($locale) 
 * gets the region for the $locale 
PHP_FUNCTION( locale_get_region ) 
/* {{{ proto static string Locale::getPrimaryLanguage($locale) 
 * gets the primary language for the $locale 
/* {{{ proto static string locale_get_primary_language($locale) 
 * gets the primary language for the $locale 
PHP_FUNCTION(locale_get_primary_language ) 
 * common code shared by display_xyz functions to  get the value from ICU 
static void get_icu_disp_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS) 
		&loc_name, &loc_name_len , 
	
PHP_FUNCTION(locale_get_display_name) 
PHP_FUNCTION(locale_get_display_language) 
PHP_FUNCTION(locale_get_display_script) 
PHP_FUNCTION(locale_get_display_region) 
PHP_FUNCTION(locale_get_display_variant) 
 */ 
/* 
	ICU expects the buffer to be allocated  before calling the function 
	and so the buffer size has been explicitly specified 
	ICU uloc.h #define 	ULOC_KEYWORD_AND_VALUES_CAPACITY   100 
			} 
 /* {{{ proto static string Locale::canonicalize($locale) 
 * @return string the canonicalized locale 
 /* {{{ proto static string locale_canonicalize(Locale $loc, string $locale) 
/* {{{ append_key_value 
* returns 1 if successful , -1 if not found , 
		if(strcmp(key_name, LOC_LANG_TAG) != 0 && 
/* {{{ append_multiple_key_values 
* used for 'variant','extlang','private' 
* returns 1 if successful , -1 if not found , 
		for( i=0 ; i< max_value; i++ ){  
			snprintf( cur_key_name , 30, "%s%d", key_name , i);	
* returns 0  if locale_compose needs to be aborted 
/* {{{ proto static string Locale::composeLocale($array) 
* Creates a locale by combining the parts of locale-ID passed	
/* {{{ proto static string compose_locale($array) 
* Creates a locale by combining the parts of locale-ID passed	
	result = append_key_value(loc_name, hash_arr,  LOC_GRANDFATHERED_LANG_TAG);	
	result = append_key_value(loc_name, hash_arr , LOC_LANG_TAG);	
	result = append_key_value(loc_name, hash_arr , LOC_SCRIPT_TAG); 
	
	result = append_multiple_key_values( loc_name, hash_arr , LOC_VARIANT_TAG TSRMLS_CC); 
	int 	len =0; 
		mod_loc_name = loc_name ; 
			if( singletonPos!=-1){ 
				if( (*(mod_loc_name+singletonPos)=='x') || (*(mod_loc_name+singletonPos)=='X') ){		
	
	if( (strcmp(key_name , LOC_PRIVATE_TAG)==0) || 
			token = php_strtok_r( key_value , DELIMITER ,&last_ptr);	
			sprintf( cur_key_name , "%s%d", key_name , cnt++);	
				sprintf( cur_key_name , "%s%d", key_name , cnt++);	
		efree(key_value);	
/* {{{ proto static array Locale::parseLocale($locale) 
/* {{{ proto static array parse_locale($locale) 
	
	if( findOffset( LOC_GRANDFATHERED , loc_name ) >=  0 ){ 
	else {	
			token = php_strtok_r( variant , DELIMITER , &saved_ptr);	
			
* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm 
* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm 
	zend_bool 	boolCanonical 	= 0;	
	
		&lang_tag, &lang_tag_len , &loc_range , &loc_range_len , 
			intl_error_set( NULL, status, 
			intl_error_set( NULL, status, 
	
			if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){ 
		
		
			if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){ 
	for( i=0; i< arr_size; i++ ){ 
* returns the lookup result to lookup_loc_range_src_php 
		
		} 
		cur_arr_len++ ; 
		for(i=0; i<cur_arr_len; i++) { 
			result = strToMatch(lang_tag, cur_arr[i*2]);	
	} 
	result = strToMatch(loc_range, cur_loc_range);	
		for(i=0; i< cur_arr_len; i++){ 
			if(cur_arr[i*2] != NULL && strlen(cur_arr[i*2]) == saved_pos && strncmp(cur_loc_range, cur_arr[i*2], saved_pos) == 0) {	
/* {{{ proto string Locale::lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]]) 
* range 
* range 
	} 
	
	
	len = uloc_acceptLanguageFromHTTP(resultLocale, INTL_MAX_LOCALE_LEN, 

Fixed code:
	"cel-gaulish",		"en-GB-oed",		"i-ami",
	"i-bnn",		"i-default",		"i-enochian",
	"i-mingo",		"i-pwn", 		"i-tao",
	"zh-min-nan", 		"zh-wuu", 		"zh-xiang",
*  This is in sync with the array LOC_GRANDFATHERED
	"nv", 			"nb",			"nn",
{
* returns the position of next token for lookup
* strtokr equivalent search for token in reverse direction

* returns the position of a singleton if present


	int   len=0;
	zend_alter_ini_entry(LOCALE_INI_NAME, sizeof(LOCALE_INI_NAME), locale_name, len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
* Gets the value from ICU
		singletonPos = getSingletonPos( loc_name );
		mod_loc_name = estrdup(loc_name );
				buflen++; /* add space for \0 */
static void get_icu_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS)
/* {{{ proto static string Locale::getScript($locale)
 * gets the script for the $locale
/* {{{ proto static string locale_get_script($locale)
 * gets the script for the $locale
PHP_FUNCTION( locale_get_script )
/* {{{ proto static string Locale::getRegion($locale)
 * gets the region for the $locale
/* {{{ proto static string locale_get_region($locale)
 * gets the region for the $locale
PHP_FUNCTION( locale_get_region )
/* {{{ proto static string Locale::getPrimaryLanguage($locale)
 * gets the primary language for the $locale
/* {{{ proto static string locale_get_primary_language($locale)
 * gets the primary language for the $locale
PHP_FUNCTION(locale_get_primary_language )
 * common code shared by display_xyz functions to  get the value from ICU
static void get_icu_disp_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS)
		&loc_name, &loc_name_len ,

PHP_FUNCTION(locale_get_display_name)
PHP_FUNCTION(locale_get_display_language)
PHP_FUNCTION(locale_get_display_script)
PHP_FUNCTION(locale_get_display_region)
PHP_FUNCTION(locale_get_display_variant)
 */
/*
	ICU expects the buffer to be allocated  before calling the function
	and so the buffer size has been explicitly specified
	ICU uloc.h #define 	ULOC_KEYWORD_AND_VALUES_CAPACITY   100
			}
 /* {{{ proto static string Locale::canonicalize($locale)
 * @return string the canonicalized locale
 /* {{{ proto static string locale_canonicalize(Locale $loc, string $locale)
/* {{{ append_key_value
* returns 1 if successful , -1 if not found ,
		if(strcmp(key_name, LOC_LANG_TAG) != 0 &&
/* {{{ append_multiple_key_values
* used for 'variant','extlang','private'
* returns 1 if successful , -1 if not found ,
		for( i=0 ; i< max_value; i++ ){
			snprintf( cur_key_name , 30, "%s%d", key_name , i);
* returns 0  if locale_compose needs to be aborted
/* {{{ proto static string Locale::composeLocale($array)
* Creates a locale by combining the parts of locale-ID passed
/* {{{ proto static string compose_locale($array)
* Creates a locale by combining the parts of locale-ID passed
	result = append_key_value(loc_name, hash_arr,  LOC_GRANDFATHERED_LANG_TAG);
	result = append_key_value(loc_name, hash_arr , LOC_LANG_TAG);
	result = append_key_value(loc_name, hash_arr , LOC_SCRIPT_TAG);

	result = append_multiple_key_values( loc_name, hash_arr , LOC_VARIANT_TAG TSRMLS_CC);
	int 	len =0;
		mod_loc_name = loc_name ;
			if( singletonPos!=-1){
				if( (*(mod_loc_name+singletonPos)=='x') || (*(mod_loc_name+singletonPos)=='X') ){

	if( (strcmp(key_name , LOC_PRIVATE_TAG)==0) ||
			token = php_strtok_r( key_value , DELIMITER ,&last_ptr);
			sprintf( cur_key_name , "%s%d", key_name , cnt++);
				sprintf( cur_key_name , "%s%d", key_name , cnt++);
		efree(key_value);
/* {{{ proto static array Locale::parseLocale($locale)
/* {{{ proto static array parse_locale($locale)

	if( findOffset( LOC_GRANDFATHERED , loc_name ) >=  0 ){
	else {
			token = php_strtok_r( variant , DELIMITER , &saved_ptr);

* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm
* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm
	zend_bool 	boolCanonical 	= 0;

		&lang_tag, &lang_tag_len , &loc_range , &loc_range_len ,
			intl_error_set( NULL, status,
			intl_error_set( NULL, status,

			if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){


			if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){
	for( i=0; i< arr_size; i++ ){
* returns the lookup result to lookup_loc_range_src_php

		}
		cur_arr_len++ ;
		for(i=0; i<cur_arr_len; i++) {
			result = strToMatch(lang_tag, cur_arr[i*2]);
	}
	result = strToMatch(loc_range, cur_loc_range);
		for(i=0; i< cur_arr_len; i++){
			if(cur_arr[i*2] != NULL && strlen(cur_arr[i*2]) == saved_pos && strncmp(cur_loc_range, cur_arr[i*2], saved_pos) == 0) {
/* {{{ proto string Locale::lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]])
* range
* range
	}


	len = uloc_acceptLanguageFromHTTP(resultLocale, INTL_MAX_LOCALE_LEN,
--TEST--
Bug #72241: get_icu_value_internal out-of-bounds read
--SKIPIF--
<?php if( !extension_loaded( 'intl' ) ) print 'skip'; ?>
--FILE--
<?php
$var1=str_repeat("A", 1000);
$out = locale_get_primary_language($var1);
echo strlen($out) . PHP_EOL;
echo unpack('H*', $out)[1] . PHP_EOL;
--EXPECT--
1000
61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161