Bug #80929 [Csd]: Method name corruption related to repeated calls to call_user_func_array
Edit report at https://bugs.php.net/bug.php?id=80929&edit=1
ID: 80929
Updated 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: Closed
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:
Thank you for the detailed code example so that I can easily reproduce this bug, The cause of the
bug is that the PHP kernel incorrectly released the memory of the function name. (BTW, in this
example, more than three calls will trigger the memory error, it has nothing to do with how to call,
the error always occurs, but the program does not always crash immediately)
Now, it works well in PHP-7.4.19 ~ master :)
Previous Comments:
------------------------------------------------------------------------
[2021-04-16 02:00:23] git@php.net
Automatic comment on behalf of twose
Revision: https://github.com/php/php-src/commit/c0b1bdcdc39971700badabd0fd7285ae2f40e479
Log: Fixed bug #80929
------------------------------------------------------------------------
[2021-04-15 08:26:36] twosee@php.net
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
------------------------------------------------------------------------
[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
Thread (6 messages)