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

From: Date: Fri, 02 Apr 2021 23:31:26 +0000
Subject: Bug #80929 [NEW]: Method name corruption related to repeated calls to call_user_func_array
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-233132@lists.php.net to get a copy of this message
From:             edudobay at gmail dot com
Operating system: Linux
PHP version:      7.4.16
Package:          Reproducible crash
Bug Type:         Bug
Bug description:Method name corruption related to repeated calls to call_user_func_array

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 bug report at https://bugs.php.net/bug.php?id=80929&edit=1
-- 
Fix committed:                    https://bugs.php.net/fix.php?id=80929&r=fixed
Fixed in release:                 https://bugs.php.net/fix.php?id=80929&r=alreadyfixed
Need backtrace:                   https://bugs.php.net/fix.php?id=80929&r=needtrace
Need Reproduce Script:            https://bugs.php.net/fix.php?id=80929&r=needscript
Try newer version:                https://bugs.php.net/fix.php?id=80929&r=oldversion
Not developer issue:              https://bugs.php.net/fix.php?id=80929&r=support
Expected behavior:                https://bugs.php.net/fix.php?id=80929&r=notwrong
Not enough info:                  https://bugs.php.net/fix.php?id=80929&r=notenoughinfo
Submitted twice:                  https://bugs.php.net/fix.php?id=80929&r=submittedtwice
register_globals:                 https://bugs.php.net/fix.php?id=80929&r=globals
PHP version support discontinued: https://bugs.php.net/fix.php?id=80929&r=phptooold
Daylight Savings:                 https://bugs.php.net/fix.php?id=80929&r=dst
IIS Stability:                    https://bugs.php.net/fix.php?id=80929&r=isapi
Install GNU Sed:                  https://bugs.php.net/fix.php?id=80929&r=gnused
Floating point limitations:       https://bugs.php.net/fix.php?id=80929&r=float
No Zend Extensions:               https://bugs.php.net/fix.php?id=80929&r=nozend
MySQL Configuration Error:        https://bugs.php.net/fix.php?id=80929&r=mysqlcfg


Thread (6 messages)

« previous php.bugs (#233132) next »