php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #76337 segfault when opcache enabled + extension use zend_register_class_alias
Submitted: 2018-05-13 23:45 UTC Modified: 2018-05-16 16:36 UTC
From: xKhorasan+php at gmail dot com Assigned:
Status: Closed Package: Reproducible crash
PHP Version: 7.2.5 OS: Linux (CentOS6)
Private report: No CVE-ID: None
 [2018-05-13 23:45 UTC] xKhorasan+php at gmail dot com
Description:
------------
configure option: --with-config-file-path=/etc/ --with-config-file-scan-dir=/etc/php.d/
change to php.ini: added lines below
> zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20170718/opcache.so
> opcache.enable_cli=1
> extension=couchbase.so

steps to reproduce:
1. download this file: https://gist.github.com/xKerman/a987581d3d137644460dd15c1d25c612
2: run command: docker build -t php/bug-report .
3: run command in docker container:
```
$ docker run --rm -it php/bug-report bash
[root@236b3ee9e1ec /]# php -v
```

gdb backtrace:
```
[root@236b3ee9e1ec /]# ulimit -c unlimited
[root@236b3ee9e1ec /]# php -v
[cb,WARN] (pcbc/ext L:426) igbinary serializer is not found
PHP 7.2.5 (cli) (built: May 11 2018 21:40:50) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.5, Copyright (c) 1999-2018, by Zend Technologies
Segmentation fault (core dumped)

[root@236b3ee9e1ec /]# gdb php -c core
(gdb) bt
#0  zend_string_release (ht=0x2fbbb50) at /php-7.2.5/Zend/zend_string.h:289
#1  zend_hash_destroy (ht=0x2fbbb50) at /php-7.2.5/Zend/zend_hash.c:1247
#2  0x0000000000726f91 in zend_shutdown () at /php-7.2.5/Zend/zend.c:911
#3  0x00000000006c581a in php_module_shutdown () at /php-7.2.5/main/main.c:2453
#4  0x00000000007d7225 in main (argc=2, argv=0x2fbb7a0)
    at /php-7.2.5/sapi/cli/php_cli.c:1419

(gdb) frame 2
#2  0x0000000000726f91 in zend_shutdown () at /php-7.2.5/Zend/zend.c:911
911             zend_hash_destroy(GLOBAL_CLASS_TABLE);
```


Some extesion, e.g. couchbase, calls `zend_register_class_alias()`.
https://github.com/couchbase/php-couchbase/blob/48d7f6626461c0941c4637dd93810f7556b9a387/src/couchbase/search/facet.c#L29

`zend_register_class_alias()` add alias name to `CG(class_table)`, bug does not intern the class alias name string.
https://github.com/php/php-src/blob/php-7.2.5/Zend/zend_API.c#L2774-L2795

Compared to `zend_regiser_class_alias()`, other `CG(class_table)` update operations, like `zend_register_internal_class()`, does intern string class name.
https://github.com/php/php-src/blob/php-7.2.5/Zend/zend_API.c#L2720

So, `CG(class_table)` contains interned class name string and not interned clas name string, if extension use `zend_resiger_class_alias()`.
And it seems that this causes segmentation fault on `zend_shutdown()`.

Expected result:
----------------
`php -v` return exit code 0

Actual result:
--------------
segmentation fault occur:

```
[root@236b3ee9e1ec /]# php -v
[cb,WARN] (pcbc/ext L:426) igbinary serializer is not found
PHP 7.2.5 (cli) (built: May 11 2018 21:40:50) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.5, Copyright (c) 1999-2018, by Zend Technologies
Segmentation fault
```

Patches

bug76337.patch (last revision 2018-05-13 23:47 UTC by xKhorasan+php at gmail dot com)

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2018-05-14 21:52 UTC] xKhorasan+php at gmail dot com
Note that this does not occur in PHP 7.1 (confirmed with 7.1.17)
 [2018-05-15 22:02 UTC] xKhorasan+php at gmail dot com
-Package: opcache +Package: Reproducible crash
 [2018-05-15 22:02 UTC] xKhorasan+php at gmail dot com
Confirmed that this still occur in PHP 7.2.6 rc1 ( https://downloads.php.net/~pollita/ ).
 [2018-05-15 22:17 UTC] xKhorasan+php at gmail dot com
created pull request in https://github.com/php/php-src/pull/3241
(cannot add the pull request with "Add a Pull Request")
 [2018-05-16 16:36 UTC] xKhorasan+php at gmail dot com
Detail explanation:

When PHP start running, `accel_use_shm_interned_strings()` is called and interned strings are copied to opcache shared memory space.
https://github.com/php/php-src/blob/php-7.2.6RC1/ext/opcache/ZendAccelerator.c#L642
In this operation, the entry key in `CG(class_table)` are copied to the opcache shared memory space.
Each address of class name string changes to point to shared memory space.
```
(gdb) source .gdbinit
(gdb) b accel_use_shm_interned_strings
(gdb) run -v
Starting program: /usr/local/bin/php -v
[Thread debugging using libthread_db enabled]

Breakpoint 1, accel_use_shm_interned_strings ()
    at /php-7.2.6RC1/ext/opcache/ZendAccelerator.c:643
643     {

(gdb) print_zstr compiler_globals->class_table->arData[155]->key
string(14) "_zendtestclass"
(gdb) p compiler_globals->class_table->arData[155]->key
$1 = (zend_string *) 0x1219f80

(gdb) print_zstr compiler_globals->class_table->arData[157]->key
string(19) "_zendtestclassalias"
(gdb) p compiler_globals->class_table->arData[157]->key
$2 = (zend_string *) 0x121a710

(gdb) fin
(gdb) p compiler_globals->class_table->arData[155]->key
$3 = (zend_string *) 0x7fffee783ac0
(gdb) p compiler_globals->class_table->arData[157]->key
$4 = (zend_string *) 0x7fffee783bd8
```


on PHP shutdown, `accel_shutdown()` is called, and in the function, `accel_use_permanent_interned_strings()` is called.
https://github.com/php/php-src/blob/php-7.2.6RC1/ext/opcache/ZendAccelerator.c#L2796
In this operation, interned strings are replaced using `accel_replace_string_by_process_permanent()`.
https://github.com/php/php-src/blob/php-7.2.6RC1/ext/opcache/ZendAccelerator.c#L629
So each entry key in `CG(class_table)` is replaced by original interned string.
However, since the class name created by `zend_regiser_class_alias()` is not an interned string,
(see: https://github.com/php/php-src/blob/php-7.2.6RC1/Zend/zend_API.c#L2774-L2795 )
its address still points to the shared memory, not an original interned string.
```
(gdb) b accel_shutdown
(gdb) c
(gdb) b accel_reset_pcre_cache
(gdb) c
(gdb) p compiler_globals->class_table->arData[155]->key
$20 = (zend_string *) 0x1219f80
(gdb) p compiler_globals->class_table->arData[157]->key
$21 = (zend_string *) 0x7fffee783bd8
```


After `accel_use_permanent_interned_strings()`, `zend_shared_alloc_shutdown()` is called, and the shared memory space is removed.
So the class alias name in `CG(class_table)`, still points to the shared memory, becomes a dangling pointer.
```
(gdb) b zend_shared_alloc_shutdown
(gdb) print_zstr compiler_globals->class_table->arData[157]->key
string(19) "_zendtestclassalias"

(gdb) n
(gdb) fin
(gdb) print_zstr compiler_globals->class_table->arData[157]->key
Cannot access memory at address 0x7fffee783be8
```


After that, `zend_hash_destroy(GLOBAL_CLASS_TABLE)` is called in `zend_shutdown()`,
But as noted adove, `CG(class_table)` contains dangling pointer, so this operation cause segmentation fault.
```
(gdb) c
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
zend_hash_destroy (ht=0x10ffbb0) at /php-7.2.6RC1/Zend/zend_hash.c:1247
1247                                                    zend_string_release(p->key);

(gdb) bt
#0  zend_hash_destroy (ht=0x10ffbb0) at /php-7.2.6RC1/Zend/zend_hash.c:1247
#1  0x00000000007279f1 in zend_shutdown () at /php-7.2.6RC1/Zend/zend.c:911
#2  0x00000000006c626a in php_module_shutdown () at /php-7.2.6RC1/main/main.c:2453
#3  0x00000000007d7cb5 in main (argc=2, argv=0x10ff800)
    at /php-7.2.6RC1/sapi/cli/php_cli.c:1419

(gdb) frame 1
#1  0x00000000007279f1 in zend_shutdown () at /php-7.2.6RC1/Zend/zend.c:911
911             zend_hash_destroy(GLOBAL_CLASS_TABLE);
```
 [2018-05-20 11:34 UTC] ab@php.net
Automatic comment on behalf of xKhorasan@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=5681f6523bb36d6b49ab802ddba75e53d8d45268
Log: Fixed bug #76337
 [2018-05-20 11:34 UTC] ab@php.net
-Status: Open +Status: Closed
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 12:01:29 2024 UTC