HexBrain

Internet Bug Bounty · Report #175315

Illegal write access through Locale methods

Validstatus
lowseverity
$500bounty
1upvotes
13 Oct 2019disclosed

How it was found

Upstream Bug

https://bugs.php.net/bug.php?id=73248

Summary

The functions locale_get_all_variants and locale_get_primary_language do not validate the length of the locale name supplied, this causes an integer overflow inside ulocimp_getLanguage, since it uses int32_t type as index, and strings with length bigger than 0xffffffff cause an illegal write access.

This bug can be mitigated from PHP, by checking the input length similar to bug67397-patch https://bugs.php.net/patch-display.php?bug_id=67397&patch=bug67397-patch&revision=latest

Source code: https://github.com/php/php-src/blob/master/ext/intl/locale/locale_methods.c#L381

static void get_icu_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS)
{

	const char* loc_name        	= NULL;
	size_t         loc_name_len    	= 0;

	zend_string*   tag_value		= NULL;
	char*       empty_result	= "";

	int         result    		= 0;
	char*       msg        		= NULL;

	UErrorCode  status          	= U_ZERO_ERROR;

	intl_error_reset( NULL );

	if(zend_parse_parameters( ZEND_NUM_ARGS(), "s",
	&loc_name ,&loc_name_len ) == FAILURE) {
		spprintf(&msg , 0, "locale_get_%s : unable to parse input params", tag_name );
		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,  msg , 1 );
		efree(msg);

		RETURN_FALSE;
    }

	if(loc_name_len == 0) {
		loc_name = intl_locale_get_default();
	}
 
        // Here check that loc_name_len is not greater than 0xffffffff

	/* Call ICU get */
	tag_value = get_icu_value_internal( loc_name , tag_name , &result ,0);
...

https://github.com/php/php-src/blob/master/ext/intl/locale/locale_methods.c#L1143

PHP_FUNCTION(locale_get_all_variants)
{
	const char*  	loc_name        = NULL;
	size_t    		loc_name_len    = 0;

	int	result		= 0;
	char*	token		= NULL;
	zend_string*	variant		= NULL;
	char*	saved_ptr	= NULL;

	intl_error_reset( NULL );

	if(zend_parse_parameters( ZEND_NUM_ARGS(), "s",
	&loc_name, &loc_name_len ) == FAILURE)
	{
		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
	     "locale_parse: unable to parse input params", 0 );

		RETURN_FALSE;
	}

	if(loc_name_len == 0) {
		loc_name = intl_locale_get_default();
	}

        // Here check that loc_name_len is not greater than 0xffffffff

	array_init( return_value );

	/* If the locale is grandfathered, stop, no variants */
	if( findOffset( LOC_GRANDFATHERED , loc_name ) >=  0 ){
		/* ("Grandfathered Tag. No variants."); */
	}
	else {
	/* Call ICU variant */
		variant = get_icu_value_internal( loc_name , LOC_VARIANT_TAG , &result ,0);

GDB output

LD_LIBRARY_PATH=/home/operac/icu58/lib USE_ZEND_ALLOC=0 ASAN_OPTIONS=detect_leaks=0 gdb -q --args /home/operac/build4/bin/php -dextension=/home/operac/build4/lib/php/20151012-debug/intl.so -n poc.php
No symbol table is loaded.  Use the "file" command.
Breakpoint 1 (__asan_report_error) pending.
Reading symbols from /home/operac/build4/bin/php...done.
gdb-peda$ r
Starting program: /home/operac/build4/bin/php -dextension=/home/operac/build4/lib/php/20151012-debug/intl.so -n poc.php
...
Stopped reason: SIGSEGV
0x00007fffee3e8ed5 in ulocimp_getLanguage (localeID=0x7ffe6c3f8800 '#' <repeats 200 times>..., language=0x616000026798 '#' <repeats 200 times>..., languageCapacity=0x200, pEnd=0x0) at uloc.cpp:1244
1244                language[i]=(char)uprv_tolower(*localeID);
gdb-peda$ p/d i
$1 = -2147483648    // negative index

Patch

http://git.php.net/?p=php-src.git;a=commit;h=d3eb58332af433982f1e2ae9095fb087974a95f2

Fixed for PHP 5.6.27, PHP 7.0.12

http://php.net/ChangeLog-5.php http://php.net/ChangeLog-7.php#7.0.12

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original