Bug #76047 [Ver]: Reproducible crash in zend_string_alloc
| From: | stas@php.net | Date: | Thu, 30 Jan 2020 20:50:41 +0000 |
| Subject: | Bug #76047 [Ver]: Reproducible crash in zend_string_alloc | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-225262@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: stas@php.net
Reported by: kenashkov at gmail dot com
Summary: Reproducible crash in zend_string_alloc
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:
Could you please specify what you mean by "exploiting it in the wild"? what exactly is
being exploited? I see that the specific code can trigger UAF, but there's no security issue
there, it's just a regular crash. Are you saying there's some way to remotely exploit this
problem on an arbitrary (or common) PHP code? If so, could you refer to a specific example of that?
Previous Comments:
------------------------------------------------------------------------
[2020-01-30 19:26:55] nikic@php.net
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)
------------------------------------------------------------------------
[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.
------------------------------------------------------------------------
The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at
https://bugs.php.net/bug.php?id=76047
--
Edit this bug report at https://bugs.php.net/bug.php?id=76047&edit=1