Bug #80929 [PATCH]: Method name corruption related to repeated calls to call_user_func_array
| From: | twosee@php.net | Date: | Thu, 15 Apr 2021 08:26:36 +0000 |
| Subject: | Bug #80929 [PATCH]: Method name corruption related to repeated calls to call_user_func_array | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-233434@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=80929&edit=1
ID: 80929
Patch added by: twosee@php.net
Reported by: edudobay at gmail dot com
Summary: Method name corruption related to repeated calls to
call_user_func_array
Status: Verified
Type: Bug
Package: Reproducible crash
Operating System: Linux
PHP Version: 7.4.16
Assigned To: twosee
Block user comment: N
Private report: N
New Comment:
The following pull request has been associated:
Patch Name: Fixed bug #80929
On GitHub: https://github.com/php/php-src/pull/6867
Patch: https://github.com/php/php-src/pull/6867.patch
Previous Comments:
------------------------------------------------------------------------
[2021-04-06 09:01:10] cmb@php.net
FWIW, I cannot reproduce this with current PHP-7.4 on Windows.
------------------------------------------------------------------------
[2021-04-02 23:31:25] edudobay at gmail dot com
Description:
------------
A Closure::fromCallable (1) is created from an array callable with an object (2) that implements
__call and a method name (3) that doesn't exist on the target object. That __call method calls
non-namespaced call_user_func_array on another array callable (4).
Starting on the third or fourth time the Closure (1) is called, the method name it forwards to (2)
is changed to a random string â in many cases a VERY LONG string (gigabytes, terabytes or more)
that will terminate the program with a segmentation fault or an out-of-memory error.
Above is an outline of the scenario I've built to reproduce this. I tried my best to trim the
test program to an absolute minimum, though the final version still has 45 lines of code. There
seems to be a complex relationship between the elements that trigger the bug.
If \call_user_func_array is called in its fully-qualified form, the script runs to completion but
occasionally (5~6% of the time) terminates with a segmentation fault status code (139).
Case-changing behavior: when the fully-qualified \call_user_func_array is used, the method name
changes to lowercase starting with the fourth call. Not sure if this is a bug or an internal VM
optimization that might be related to the bug.
PHP 7.2.34: affected
PHP 7.3.27: affected
PHP 7.4.16: affected
PHP 8.0.3: differently affected. Does not seem to mutate the argument to very long strings, but
still can have mutations depending on what is error_logged.
The behavior could be reproduced with official Docker images (see the Dockerfile) and with Arch
Linux builds (official build for 8.0.3, and AUR build with --enable-debug for 7.4.16).
Test script:
---------------
// See full script at https://github.com/edudobay/php-bug-method-name-corruption/blob/main/demo.php
// (1)
$listener = Closure::fromCallable([$this, 'someMethod']);
// (2)
public function __call(string $name, array $arguments) {
// (4)
call_user_func_array([$this->subscriber, $name], $arguments);
}
Expected result:
----------------
0
__call name=(length=18)
__call name=handleDefaultEvent
1
__call name=(length=18)
__call name=handleDefaultEvent
(... similar output suppressed ...)
9
__call name=(length=18)
__call name=handleDefaultEvent
Actual result:
--------------
0
__call name=(length=18)
__call name=handleDefaultEvent
1
__call name=(length=18)
__call name=handleDefaultEvent
2
__call name=(length=18)
__call name=__call name=(length=18)
PHP Fatal error: Uncaught TypeError: call_user_func_array() expects parameter 1 to be a valid
callback, class 'App\DefaultListener' does not have a method '__call
name=(length=18)' in /app/demo.php:47
Stack trace:
#0 [internal function]: App\SubscriberProxy->__call()
#1 /app/demo.php(55): App\SubscriberProxy->gettraceasstring()
#2 /app/demo.php(66): App\SubscriberProxy->dispatch()
#3 {main}
thrown in /app/demo.php on line 47
Running the same with USE_ZEND_ALLOC=0 yields the following assertion error instead of the uncaught
TypeError:
php74: (...)/php-7.4.16/Zend/zend_hash.c:965: _zend_hash_index_add_or_update_i: Assertion
`(zend_gc_refcount(&(ht)->gc) == 1) || ((ht)->u.flags & (1<<6))' failed.
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=80929&edit=1