Bug #80929 [Opn->Ver]: Method name corruption related to repeated calls to call_user_func_array

From: Date: Tue, 13 Apr 2021 07:47:59 +0000
Subject: Bug #80929 [Opn->Ver]: 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-233377@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
 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:             Open
+Status:             Verified
 Type:               Bug
 Package:            Reproducible crash
 Operating System:   Linux
 PHP Version:        7.4.16
 Block user comment: N
 Private report:     N



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


Thread (6 messages)

« previous php.bugs (#233377) next »