php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #78502 Incorrect stack size calculation for indirectly recursive function call
Submitted: 2019-09-06 06:12 UTC Modified: 2019-09-06 09:28 UTC
From: phofstetter at sensational dot ch Assigned: nikic (profile)
Status: Closed Package: Scripting Engine problem
PHP Version: 7.4.0RC1 OS: macos 10.14, Debian 10
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: phofstetter at sensational dot ch
New email:
PHP Version: OS:

 

 [2019-09-06 06:12 UTC] phofstetter at sensational dot ch
Description:
------------
The test script attached to this bug report will cause a segmenation fault, however, unfortunately, it doesn't happen when a debubgger is attached(??)

pilif@celes:~| ⇒  php --version
PHP 7.4.0RC1 (cli) (built: Sep  6 2019 07:35:43) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0-dev, Copyright (c) Zend Technologies
pilif@celes:~| ⇒  php segv.php
[1]    73522 segmentation fault  php segv.php
pilif@celes:~| ⇒

Test script:
---------------
<?php

$tree = [
    'name' => 'a',
    'quant' => 1,
    'children' => [
        ['name' => 'b', 'quant' => 1],
        ['name' => 'c', 'quant' => 1, 'children' => [
            ['name' => 'd', 'quant' => 1],
        ]],
    ],
];

function tree_map($tree, $recursive_attr, closure $callback){
    if(isset($tree[$recursive_attr])){
        $tree[$recursive_attr] = array_map(function($c) use($recursive_attr, $callback){
            return tree_map($c, $recursive_attr, $callback);
        }, $tree[$recursive_attr]);
    }
    return $callback($tree);
}

tree_map($tree, 'children', function ($node) {});

Expected result:
----------------
no segfault

Actual result:
--------------
segfault. My attempts at getting a proper backtrace were foiled by this not crashing when e debugger is attached.

However, a slightly more complicated version of the script produced the following trace in lldb:

pilif@celes:~| ⇒  lldb php
(lldb) target create "php"
Current executable set to 'php' (x86_64).
(lldb) run segv.php
Process 96419 launched: '/usr/local/bin/php' (x86_64)
Process 96419 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x00000001003c2a3c php`zend_leave_helper_SPEC + 60
php`zend_leave_helper_SPEC:
->  0x1003c2a3c <+60>: decl   (%rdi)
    0x1003c2a3e <+62>: je     0x1003c2a66               ; <+102>
    0x1003c2a40 <+64>: movl   0x4(%rdi), %eax
    0x1003c2a43 <+67>: cmpl   $0xa, %eax
Target 0: (php) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
  * frame #0: 0x00000001003c2a3c php`zend_leave_helper_SPEC + 60
    frame #1: 0x000000010037f4ff php`execute_ex + 35
    frame #2: 0x0000000100335102 php`zend_call_function + 1230
    frame #3: 0x0000000100256cc0 php`zif_array_map + 448
    frame #4: 0x00000001003b0365 php`ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER + 80
    frame #5: 0x000000010037f4ff php`execute_ex + 35
    frame #6: 0x000000010037f6b7 php`zend_execute + 352
    frame #7: 0x0000000100342981 php`zend_execute_scripts + 338
    frame #8: 0x00000001002eb7bc php`php_execute_script + 482
    frame #9: 0x00000001003c86fb php`do_cli + 3893
    frame #10: 0x00000001003c7659 php`main + 1229
    frame #11: 0x00007fff644192a5 libdyld.dylib`start + 1
(lldb)

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2019-09-06 06:33 UTC] phofstetter at sensational dot ch
-Operating System: macos 10.14 +Operating System: macos 10.14, Debian 10
 [2019-09-06 06:33 UTC] phofstetter at sensational dot ch
Also happens on Debian 10
 [2019-09-06 06:41 UTC] phofstetter at sensational dot ch
On Debian 10, the attached test script also crashes with a debugger attached. Here's the backtrace:

(gdb) run segv.php
Starting program: /opt/php/7.4/bin/php segv.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x0000555555a881fb in zend_call_function (fci=fci@entry=0x7fffffffa8a0, fci_cache=fci_cache@entry=0x7fffffffa880)
    at /home/crazyhat/downloads/php-7.4.0RC1/Zend/zend_execute_API.c:681
681     } else if (EG(current_execute_data)->func &&
(gdb) bt
#0  0x0000555555a881fb in zend_call_function (fci=fci@entry=0x7fffffffa8a0, fci_cache=fci_cache@entry=0x7fffffffa880)
    at /home/crazyhat/downloads/php-7.4.0RC1/Zend/zend_execute_API.c:681
#1  0x00005555559c6428 in zif_array_map (execute_data=<optimized out>, return_value=0x7ffff4013350)
    at /home/crazyhat/downloads/php-7.4.0RC1/ext/standard/array.c:6203
#2  0x0000555555b10820 in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER () at /home/crazyhat/downloads/php-7.4.0RC1/Zend/zend_vm_execute.h:1323
#3  execute_ex (ex=0x7fffffffa8a0) at /home/crazyhat/downloads/php-7.4.0RC1/Zend/zend_vm_execute.h:53461
#4  0x0000555555a88826 in zend_call_function (fci=fci@entry=0x7fffffffaaf0, fci_cache=<optimized out>, fci_cache@entry=0x7fffffffaad0)
    at /home/crazyhat/downloads/php-7.4.0RC1/Zend/zend_execute_API.c:816
#5  0x00005555559c6428 in zif_array_map (execute_data=<optimized out>, return_value=0x7ffff4013170)
    at /home/crazyhat/downloads/php-7.4.0RC1/ext/standard/array.c:6203
#6  0x0000555555b10820 in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER () at /home/crazyhat/downloads/php-7.4.0RC1/Zend/zend_vm_execute.h:1323
#7  execute_ex (ex=0x7fffffffa8a0) at /home/crazyhat/downloads/php-7.4.0RC1/Zend/zend_vm_execute.h:53461
#8  0x0000555555b16a63 in zend_execute (op_array=0x7ffff407c2a0, return_value=<optimized out>)
    at /home/crazyhat/downloads/php-7.4.0RC1/Zend/zend_vm_execute.h:57561
#9  0x0000555555a964c4 in zend_execute_scripts (type=type@entry=8, retval=0x7ffff40130b0, retval@entry=0x0, file_count=file_count@entry=3)
    at /home/crazyhat/downloads/php-7.4.0RC1/Zend/zend.c:1663
#10 0x0000555555a39fa0 in php_execute_script (primary_file=<optimized out>) at /home/crazyhat/downloads/php-7.4.0RC1/main/main.c:2619
#11 0x0000555555b18a66 in do_cli (argc=2, argv=0x55555677a5d0) at /home/crazyhat/downloads/php-7.4.0RC1/sapi/cli/php_cli.c:961
#12 0x0000555555794239 in main (argc=2, argv=0x55555677a5d0) at /home/crazyhat/downloads/php-7.4.0RC1/sapi/cli/php_cli.c:1352
 [2019-09-06 06:45 UTC] phofstetter at sensational dot ch
one last update: This happens independently of whether opcache is loaded or not.
 [2019-09-06 08:05 UTC] nikic@php.net
-Status: Open +Status: Verified
 [2019-09-06 08:05 UTC] nikic@php.net
Looks like an icall retval slot ends up pointing into the execute_data frame and clobbers func.
 [2019-09-06 08:46 UTC] phofstetter at sensational dot ch
I have bisected the issue and found

b36dbdd1dd431d1a21fdb6f2508c7c41b682466c

to be the culprit. I tried to naïvely just revert that one commit, but of course other changes happened since then and I'm definitely not good enough to deal with the internals at this level in order to fix the ensuing conflicts.
 [2019-09-06 08:53 UTC] nikic@php.net
I think the issue is an incorrect calculation of the VM stack size for one of the tree_map calls:

L3 (17):    INIT_FCALL 3 176 string("tree_map")

The size is 11*16, which it should be 14*16.
 [2019-09-06 09:00 UTC] nikic@php.net
-Status: Verified +Status: Assigned -Assigned To: +Assigned To: nikic
 [2019-09-06 09:00 UTC] nikic@php.net
@phofstetter: That commit is indeed relevant, because it changed the registration of the function to happen when we start to compile the function, rather than once it has been fully compiled. That means that stack size calculations end up being performed on a partially compiled function if it is called recursively.
 [2019-09-06 09:28 UTC] nikic@php.net
-Summary: Segmentation Fault in array_map +Summary: Incorrect stack size calculation for indirectly recursive function call -Package: Reproducible crash +Package: Scripting Engine problem
 [2019-09-06 09:33 UTC] nikic@php.net
Automatic comment on behalf of nikita.ppv@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=e81751ceacf79e2e21b48a12dcbe38c16f98b7da
Log: Fixed bug #78502
 [2019-09-06 09:33 UTC] nikic@php.net
-Status: Assigned +Status: Closed
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Sat Dec 14 05:01:23 2019 UTC