php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #80929 Method name corruption related to repeated calls to call_user_func_array
Submitted: 2021-04-02 23:31 UTC Modified: 2021-04-16 02:10 UTC
From: edudobay at gmail dot com Assigned: twosee (profile)
Status: Closed Package: Reproducible crash
PHP Version: 7.4.16 OS: Linux
Private report: No CVE-ID: None
View Add Comment Developer Edit
Anyone can comment on a bug. Have a simpler test case? Does it work for you on a different platform? Let us know!
Just going to say 'Me too!'? Don't clutter the database with that please !
Your email address:
MUST BE VALID
Solve the problem:
34 + 31 = ?
Subscribe to this entry?

 
 [2021-04-02 23:31 UTC] 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.


Patches

Add a Patch

Pull Requests

Pull requests:

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-04-06 09:01 UTC] cmb@php.net
FWIW, I cannot reproduce this with current PHP-7.4 on Windows.
 [2021-04-13 07:47 UTC] twosee@php.net
-Status: Open +Status: Verified
 [2021-04-15 08:25 UTC] twosee@php.net
-Assigned To: +Assigned To: twosee
 [2021-04-15 08:26 UTC] 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-16 02:00 UTC] git@php.net
Automatic comment on behalf of twose
Revision: https://github.com/php/php-src/commit/c0b1bdcdc39971700badabd0fd7285ae2f40e479
Log: Fixed bug #80929
 [2021-04-16 02:00 UTC] git@php.net
-Status: Verified +Status: Closed
 [2021-04-16 02:10 UTC] twosee@php.net
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 :)
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Mar 29 09:01:28 2024 UTC