Bug #80929 [Csd]: Method name corruption related to repeated calls to call_user_func_array

From: Date: Fri, 16 Apr 2021 02:10:27 +0000
Subject: Bug #80929 [Csd]: 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-233450@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:             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)

« previous php.bugs (#233450) next »