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

From: Date: Thu, 15 Apr 2021 08:26:36 +0000
Subject: Bug #80929 [PATCH]: 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-233434@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 Patch added 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: Verified 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: 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 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

« previous php.bugs (#233434) next »