Bug #76047 [Opn->Ver]: Reproducible crash in zend_string_alloc
| From: | nikic@php.net | Date: | Thu, 30 Jan 2020 19:26:55 +0000 |
| Subject: | Bug #76047 [Opn->Ver]: Reproducible crash in zend_string_alloc | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-225261@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=76047&edit=1
ID: 76047
Updated by: nikic@php.net
Reported by: kenashkov at gmail dot com
Summary: Reproducible crash in zend_string_alloc
-Status: Open
+Status: Verified
Type: Bug
Package: Reproducible crash
Operating System: CentOS Linux release 7.0.1406 (C
PHP Version: 7.2.3
Block user comment: N
Private report: N
New Comment:
Reduced test case:
<?php
class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace();
}
}
function trigger_uaf($arg) {
$arg = str_shuffle(str_repeat('A', 79));
$vuln = new Vuln();
$vuln->a = $arg;
}
trigger_uaf('x');
Valgrind:
==19523== Invalid read of size 4
==19523== at 0x95ACE4: zend_gc_addref (zend_types.h:1035)
==19523== by 0x95AE04: zval_addref_p (zend_types.h:1070)
==19523== by 0x963E3B: debug_backtrace_get_args (zend_builtin_functions.c:2156)
==19523== by 0x9654C8: zend_fetch_debug_backtrace (zend_builtin_functions.c:2551)
==19523== by 0x96CA84: zend_default_exception_new_ex (zend_exceptions.c:215)
==19523== by 0x96CD2B: zend_default_exception_new (zend_exceptions.c:246)
==19523== by 0x944F5E: _object_and_properties_init (zend_API.c:1417)
==19523== by 0x944FCC: object_init_ex (zend_API.c:1431)
==19523== by 0x9C097F: ZEND_NEW_SPEC_CONST_UNUSED_HANDLER (zend_vm_execute.h:9225)
==19523== by 0xA1797F: execute_ex (zend_vm_execute.h:54571)
==19523== by 0x924E0A: zend_call_function (zend_execute_API.c:812)
==19523== by 0x98E5EA: zend_objects_destroy_object (zend_objects.c:179)
==19523== Address 0x124f1d40 is 0 bytes inside a block of size 104 free'd
==19523== at 0x4C30D3B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==19523== by 0x902F35: _efree_custom (zend_alloc.c:2425)
==19523== by 0x903080: _efree (zend_alloc.c:2545)
==19523== by 0x939CFC: zend_string_destroy (zend_variables.c:67)
==19523== by 0x939BFB: rc_dtor_func (zend_variables.c:57)
==19523== by 0x939B7E: i_zval_ptr_dtor (zend_variables.h:44)
==19523== by 0x939D90: zval_ptr_dtor (zend_variables.c:84)
==19523== by 0x992DB7: zend_std_unset_property (zend_object_handlers.c:1127)
==19523== by 0x9F4460: ZEND_UNSET_OBJ_SPEC_UNUSED_CONST_HANDLER (zend_vm_execute.h:32155)
==19523== by 0xA19883: execute_ex (zend_vm_execute.h:56539)
==19523== by 0x924E0A: zend_call_function (zend_execute_API.c:812)
==19523== by 0x98E5EA: zend_objects_destroy_object (zend_objects.c:179)
==19523== Block was alloc'd at
==19523== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==19523== by 0x903F80: __zend_malloc (zend_alloc.c:2975)
==19523== by 0x902EC8: _malloc_custom (zend_alloc.c:2416)
==19523== by 0x903006: _emalloc (zend_alloc.c:2535)
==19523== by 0x78B0ED: zend_string_alloc (zend_string.h:133)
==19523== by 0x78B235: zend_string_init (zend_string.h:155)
==19523== by 0x7A9CAF: zif_str_shuffle (string.c:6096)
==19523== by 0x9B10AC: ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER (zend_vm_execute.h:1314)
==19523== by 0xA16DF9: execute_ex (zend_vm_execute.h:53797)
==19523== by 0xA1AF38: zend_execute (zend_vm_execute.h:57913)
==19523== by 0x93E39D: zend_execute_scripts (zend.c:1665)
==19523== by 0x89FE54: php_execute_script (main.c:2617)
Previous Comments:
------------------------------------------------------------------------
[2020-01-30 18:22:21] theodore at phpexperts dot pro
Heads up! Someone posted an active exploit against this bug on GitHub in late October 2019 and then
posted it on reddit today, 2019-01-29.
This bug needs to be fixed ASAP since it affects PHP 7.0-7.4 and people are already exploiting it in
the wild.
The person who uploaded the exploit, without notifying you guys, really needs to be publicly shamed.
This is reprehensible and illegal in the United States.
* https://github.com/mm0r1/exploits/tree/master/php7-backtrace-bypass
* https://www.reddit.com/r/PHP/comments/ew83rx/php_7074_disable_functions_bypass_0day_poc/
------------------------------------------------------------------------
[2018-03-14 10:21:15] kenashkov at gmail dot com
As suggested by Niki, I can confirm that the bug is related to an exception in a destructor. The
exception is only created (not thrown) for the purpose of preserving the backtrace for other use if
need arises (it may be thrown at a much later stage)... And as suggested it is extremely probable
that the generated backtrace at the exception creation contains references to destroyed arguments.
We still dont have a short reproduction case.
Is there are way to still create an exception in this case?
------------------------------------------------------------------------
[2018-03-04 12:22:27] kenashkov at gmail dot com
After some testing I found that it matters if $some_string passed as an argument or not even if
after that it is initialized. It also matters the refcount - if it is 2 it fails, 1 passes.
This partial code produces crash:
function f1($some_string) {
$object = some_singleton_class::get_instance();
$some_string = date('Y-m-d');//needs to be a function here so that refcount is 2, having
just plain string is refcount=1 and doesnt produce the crash
$object->overloaded_property = $some_string;
}
f1('bla');
While this does not:
function f1() {
$object = some_singleton_class::get_instance();
$some_string = date('Y-m-d');//needs to be a function here so that refcount is 2
$object->overloaded_property = $some_string;
}
f1();
Of course there are plenty other things around that which Im trying to remove. But I was wondering
can the above provide some clue as to what may be happening.
------------------------------------------------------------------------
[2018-03-04 11:49:21] nikic@php.net
Based on the valgrind trace, I'd say that what happens is that a destructor throws an
exception, which captures a backtrace, which contains the function arguments, some of which have
already been destroyed by that point.
------------------------------------------------------------------------
[2018-03-04 09:50:21] kenashkov at gmail dot com
Description:
------------
Reproducible crash on every execution. The script is too big and I cant yet isolate a small
reproducible example but I did narrow down a little change in the code that prevents the crash. Just
assigning the value of a string variable to something (not even using this new var at all) goes
around the problem:
-------
$object->overloaded_property = $some_string;//produces a crash
-------
$something = $some_string;//no longer crashes
$object->overloaded_property = $some_string;
-------
//and again a crash if $something is unset
$something = $some_string;
unset($something);//this triggers the crash again
$object->overloaded_property = $some_string;
------
Maybe it has to do with the refcount. Other modifications of the code resulted in crashes in
different places. I have given below the original bt (crash in PDOStatement->fetchAll()) but
after modifications I got a crash in the file() function.
PHP is compiled with:
'./configure' '--prefix=/web/php7.2'
'--with-apxs2=/web/apache2.4-php7.2/bin/apxs' '--enable-bcmath'
'--enable-calendar' '--enable-dbase' '--enable-exif'
'--enable-ftp' '--enable-libxml' '--enable-mbstring'
'--enable-pdo' '--enable-soap' '--enable-sockets'
'--enable-zip' '--with-bz2' '--with-curl' '--with-gd'
'--with-mcrypt' '--with-mhash' '--with-mime-magic'
'--with-mysql=mysqlnd' '--with-openssl' '--with-pdo-sqlite=shared'
'--with-pgsql' '--with-sqlite=shared' '--with-tidy'
'--with-xsl' '--with-zlib' '--with-zlib-dir'
'--with-pdo-mysql=mysqlnd' '--with-pdo-pgsql' '--with-jpeg-dir'
'--with-png-dir' '--with-xpm-dir' '--with-ttf'
'--with-freetype-dir' '--with-t1lib' '--enable-gd-native-ttf'
'--with-mysqli=mysqlnd' '--with-imap-ssl' '--with-kerberos'
'--with-gettext' '--enable-sysvsem' '--enable-sysvshm'
'--enable-sysvmsg' '--enable-pcntl' '--enable-opcache'
'--enable-debug'
No opcache or xdebug enabled. Redis is the only PECL extension used by the code but I modified it to
remove the need for it and the crashes are reproducible.
Test script:
---------------
I dont have a short one yet. I post this in the hope that someone may give me a hint what else to
test, look for or trace.
Actual result:
--------------
First segfault without any code modifications:
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fffd47e8700 (LWP 5004)]
0x00007ffff2b796da in zend_mm_alloc_small (heap=0x7fffd3200040, size=232, bin_num=15,
__zend_filename=0x7ffff341fa18 "/web/php-7.2.3/Zend/zend_string.h", __zend_lineno=134,
__zend_orig_filename=0x0, __zend_orig_lineno=0)
at /web/php-7.2.3/Zend/zend_alloc.c:1273
1273 heap->free_slot[bin_num] = p->next_free_slot;
Missing separate debuginfos, use: debuginfo-install libtidy-0.99.0-31.20091203.el7.x86_64
(gdb) bt
#0 0x00007ffff2b796da in zend_mm_alloc_small (heap=0x7fffd3200040, size=232, bin_num=15,
__zend_filename=0x7ffff341fa18 "/web/php-7.2.3/Zend/zend_string.h", __zend_lineno=134,
__zend_orig_filename=0x0, __zend_orig_lineno=0)
at /web/php-7.2.3/Zend/zend_alloc.c:1273
#1 0x00007ffff2b7996d in zend_mm_alloc_heap (heap=0x7fffd3200040, size=232,
__zend_filename=0x7ffff341fa18 "/web/php-7.2.3/Zend/zend_string.h", __zend_lineno=134,
__zend_orig_filename=0x0, __zend_orig_lineno=0)
at /web/php-7.2.3/Zend/zend_alloc.c:1344
#2 0x00007ffff2b7c44d in _emalloc (size=200, __zend_filename=0x7ffff341fa18
"/web/php-7.2.3/Zend/zend_string.h", __zend_lineno=134, __zend_orig_filename=0x0,
__zend_orig_lineno=0) at /web/php-7.2.3/Zend/zend_alloc.c:2433
#3 0x00007ffff2af218a in zend_string_alloc (len=172, persistent=0) at
/web/php-7.2.3/Zend/zend_string.h:134
#4 0x00007ffff2af21f3 in zend_string_init (
str=0x7fffd18de779
"KcGAXUg7vPDIT5DN7eu1wkT7eBz79ilDqpZfFhjgGruEl0hMPb+HhqJ70JxckyokN1ntznk98g6ZIP1fYB+b3lhh3E7w25mmwVhcM947/jFz4U437B95yPw/5Wv7wbsl6iCRLKmIOxH1agonv+pHrSa0nahmHuMt5p/kzvOv71E=\025XXXXXXXXXXXXXXXXXXXXX",
len=172,
persistent=0) at /web/php-7.2.3/Zend/zend_string.h:170
#5 0x00007ffff2af5193 in ps_fetch_string (zv=0x7fffd264f7a8, field=0x7fffa1dd0488, pack_len=0,
row=0x7fffd47e5c20) at /web/php-7.2.3/ext/mysqlnd/mysqlnd_ps_codec.c:347
#6 0x00007ffff2a7fa13 in php_mysqlnd_rowp_read_binary_protocol (row_buffer=0x7fffd212cb08,
fields=0x7fffd264f728, field_count=82, fields_metadata=0x7fffa1dd0008, as_int_or_float=0
'\000', stats=0x7fffd317de10)
at /web/php-7.2.3/ext/mysqlnd/mysqlnd_wireprotocol.c:1581
#7 0x00007ffff2adeb5b in mysqlnd_stmt_fetch_row_buffered (result=0x7fffa1d15e88,
param=0x7fffa1d749a8, flags=0, fetched_anything=0x7fffd47e5f2f "") at
/web/php-7.2.3/ext/mysqlnd/mysqlnd_ps.c:784
#8 0x00007ffff2ab5b85 in mysqlnd_mysqlnd_res_fetch_row_pub (result=0x7fffa1d15e88,
param=0x7fffa1d749a8, flags=0, fetched_anything=0x7fffd47e5f2f "") at
/web/php-7.2.3/ext/mysqlnd/mysqlnd_result.c:1275
#9 0x00007ffff2ae40c1 in mysqlnd_mysqlnd_stmt_fetch_pub (s=0x7fffa1d749a8,
fetched_anything=0x7fffd47e5f2f "") at /web/php-7.2.3/ext/mysqlnd/mysqlnd_ps.c:1234
#10 0x00007ffff2803abe in pdo_mysql_stmt_fetch (stmt=0x7fffa1db2700, ori=PDO_FETCH_ORI_NEXT,
offset=0) at /web/php-7.2.3/ext/pdo_mysql/mysql_statement.c:623
#11 0x00007ffff27f1ec6 in do_fetch_common (stmt=0x7fffa1db2700, ori=PDO_FETCH_ORI_NEXT, offset=0,
do_bind=1) at /web/php-7.2.3/ext/pdo/pdo_stmt.c:671
#12 0x00007ffff27f254e in do_fetch (stmt=0x7fffa1db2700, do_bind=1, return_value=0x7fffd47e62a0,
how=PDO_FETCH_ASSOC, ori=PDO_FETCH_ORI_NEXT, offset=0, return_all=0x0) at
/web/php-7.2.3/ext/pdo/pdo_stmt.c:828
#13 0x00007ffff27f55d2 in zim_PDOStatement_fetchAll (execute_data=0x7fffd322c010,
return_value=0x7fffd322be50) at /web/php-7.2.3/ext/pdo/pdo_stmt.c:1505
#14 0x00007ffff2c2b799 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at
/web/php-7.2.3/Zend/zend_vm_execute.h:1032
#15 0x00007ffff2cb245c in execute_ex (ex=0x7fffd32249b0) at
/web/php-7.2.3/Zend/zend_vm_execute.h:59752
#16 0x00007ffff2ba3a74 in zend_call_function (fci=0x7fffd47e65c0, fci_cache=0x7fffd47e6590) at
/web/php-7.2.3/Zend/zend_execute_API.c:819
#17 0x00007ffff29604aa in zif_call_user_func_array (execute_data=0x7fffd3224940,
return_value=0x7fffd47e6690) at /web/php-7.2.3/ext/standard/basic_functions.c:4905
#18 0x00007ffff2c2a4b3 in ZEND_DO_FCALL_BY_NAME_SPEC_RETVAL_UNUSED_HANDLER () at
/web/php-7.2.3/Zend/zend_vm_execute.h:738
#19 0x00007ffff2cb2435 in execute_ex (ex=0x7fffd3221030) at
/web/php-7.2.3/Zend/zend_vm_execute.h:59743
#20 0x00007ffff2cb78fe in zend_execute (op_array=0x7fffd3278100, return_value=0x0) at
/web/php-7.2.3/Zend/zend_vm_execute.h:63760
#21 0x00007ffff2bc0254 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at
/web/php-7.2.3/Zend/zend.c:1496
#22 0x00007ffff2b00eae in php_execute_script (primary_file=0x7fffd47e7a90) at
/web/php-7.2.3/main/main.c:2590
#23 0x00007ffff2cba763 in php_handler (r=0x7fffcc014c50) at
/web/php-7.2.3/sapi/apache2handler/sapi_apache2.c:701
#24 0x0000000000452680 in ap_run_handler (r=r@entry=0x7fffcc014c50) at config.c:170
#25 0x0000000000452bc9 in ap_invoke_handler (r=r@entry=0x7fffcc014c50) at config.c:434
#26 0x0000000000466d6c in ap_internal_redirect (new_uri=<optimized out>, r=<optimized
out>) at http_request.c:765
#27 0x00007ffff385ce5c in handler_redirect (r=0x7fffcc002970) at mod_rewrite.c:5254
#28 0x0000000000452680 in ap_run_handler (r=r@entry=0x7fffcc002970) at config.c:170
#29 0x0000000000452bc9 in ap_invoke_handler (r=r@entry=0x7fffcc002970) at config.c:434
#30 0x00000000004679ba in ap_process_async_request (r=r@entry=0x7fffcc002970) at http_request.c:436
#31 0x0000000000463fb1 in ap_process_http_async_connection (c=0x7fffe0039600) at http_core.c:154
#32 ap_process_http_connection (c=0x7fffe0039600) at http_core.c:248
#33 0x000000000045c090 in ap_run_process_connection (c=c@entry=0x7fffe0039600) at connection.c:42
#34 0x000000000046fcdc in process_socket (thd=thd@entry=0x6d3e38, p=<optimized out>,
sock=<optimized out>, cs=<optimized out>, my_child_num=my_child_num@entry=0,
my_thread_num=my_thread_num@entry=24) at event.c:1021
#35 0x0000000000470709 in worker_thread (thd=0x6d3e38, dummy=<optimized out>) at event.c:2014
#36 0x00007ffff6c97dc5 in start_thread (arg=0x7fffd47e8700) at pthread_create.c:308
#37 0x00007ffff67c0ced in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:113
At frame 4 I just replaced with XXX some sensitive data in the string.
Here is also the valgrind output:
https://pastebin.com/DW32bATf
The code runs correctly without any modifications when running with valgrind and:
USE_ZEND_ALLOC=0
ZEND_DONT_UNLOAD_MODULES=1
So I presume is an issue with the zend memory manager.
And here is another BT of a crash (in the file() function, the provided path is a valid and readable
one) I got after some modifications:
https://pastebin.com/0Q7dA1MV
A bug that could be related to this one is:
https://bugs.php.net/bug.php?id=69326
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=76047&edit=1