php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #74015 ReflectionClass::getInterfaceNames wrong entry with random memory location
Submitted: 2017-01-30 13:41 UTC Modified: 2017-05-15 15:00 UTC
Votes:2
Avg. Score:4.5 ± 0.5
Reproduced:2 of 2 (100.0%)
Same Version:1 (50.0%)
Same OS:2 (100.0%)
From: uli dot hecht at gmail dot com Assigned: ab (profile)
Status: Closed Package: Reflection related
PHP Version: 7.1.1 OS: Windows
Private report: No CVE-ID: None
 [2017-01-30 13:41 UTC] uli dot hecht at gmail dot com
Description:
------------
This bug only appears on my Windows system and is very hard to reproduce. I can only reproduce it with Symfony-projects, however all of my Symfony projects are affected. It seems that the problem doesn't occur, as long as Opcache is disabled:

After loading the page in the browser about 5 to 10 times, entries of the result array of ReflectionClass::getInterfaceNames() contain unexpected data. As soon as the error appears, getInterfaceNames() will return the same (wrong) data for the same interface on every succeeding call, no matter which class is reflected.

For instance, today "\Serializable" has been affected. Instead of "\Serializable", getInterfaceNames() returned an entry containing "#[^/\\]*$#" everytime for a Symfony class implementing that interface. I defined an own class  implementing \Serialiazable (see test script) to check this, and got the same result:

    array(1) { [0]=> string(10) "#[^/\\]*$#" }

I'm running PHP as Apache 2.4 module. The error disappears, as soon as I restart the HTTP server. Then I have another 5 to 10 reloads until the problem re-appears with other interfaces being affected and other wrong random values being returned for those affected interfaces.

In 95% of the cases an "OutOfMemoryException" is thrown, because the string length is a few Gigabytes, making it difficult to determine the affected interface. For example, at the moment I'm writing this, "\IteratorAggregate" is the affected interface and I'm getting this output:

array(2) { [0]=> string(4294967296) "
Fatal error: Allowed memory size of 536870912 bytes exhausted (tried to allocate 4294971392 bytes) in C:\Daten\Projekte\2016\dev\xxxxx-xxxxx\src\web\app_dev.php on line 12

(I just added this information in case somebody is having the same problems. In the beginning I always googled for OutOfMemoryException related topics)

Interestingly, in the "\Serializable"-case before, I could find the string of the corrupt entry ("#[^/\\]*$#") in another Symfony class variable that has nothing to do with the problem at all. So it seems that the corrupt entries are pointing to a random location in the memory.

Note: The test script (obviously) depends on the affected interface. To find out which interfaces are affected, I have to modify Symfony's DebugClassLoader temporarily. Symfony crashes here, when processing the results of getInterfaceNames(). As soon as the error occurs, the test script can be executed independently from the Symfony project in a separate PHP file.

This problem is really annoying, because I have to restart my HTTP server every few page reloads.

My specifications:

- Windows 7 x64
- Apache 2.4.23 win64 VC14 (from apachelounge.com)
- PHP 7.1.1 x64 TS
- Configure line: cscript /nologo configure.js "--enable-snapshot-build" "--enable-debug-pack" "--with-pdo-oci=c:\php-sdk\oracle\x64\instantclient_12_1\sdk,shared" "--with-oci8-12c=c:\php-sdk\oracle\x64\instantclient_12_1\sdk,shared" "--enable-object-out-dir=../obj/" "--enable-com-dotnet=shared" "--with-mcrypt=static" "--without-analyzer" "--with-pgo"

I'm having this problem for about a year now, so I can say that also older PHP7 versions are affected.

php.ini changes (My php.ini is based on php.ini-development):

- I'm using opcache with following php.ini settings:
    opcache.enable = 1
    opcache.enable_cli = 0
    opcache.memory_consumption = 128
    opcache.interned_strings_buffer = 8
    opcache.max_accelerated_files = 4000

- realpath_cache_size = 2M

- I increased limits (memory, upload file size, execution time, etc.)

- I enabled these extensions: bz2, curl, fileinfo, gs2, intl, mbstring, mysqli, openssl, pdo_mysql, soap, sockets, redis (only sometimes), opcache

Test script:
---------------
/* \Serializable being an affected interface */

    class DEBUGTEST implements \Serializable {
        public function serialize(){}
        public function unserialize($serialized){}
    }
    $drefl = new \ReflectionClass('\DEBUGTEST');
    $y = $drefl->getInterfaceNames();
    var_dump($y);
    die();

/* \IteratorAggregate being an affected interface */

    class DEBUGTEST implements \IteratorAggregate {
        public function getIterator() {}
    }
    $drefl = new \ReflectionClass('\DEBUGTEST');
    $y = $drefl->getInterfaceNames();
    var_dump($y);
    die();


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2017-01-31 01:20 UTC] ab@php.net
-Status: Open +Status: Feedback
 [2017-01-31 01:20 UTC] ab@php.net
Thanks for the report. From what I understood, some Symfony based project is needed, besides the code snippets you've posted. In that case, it'd be much better you to create a repo with the complete reproducer, a test project that can cause the bug behavior. Also, do you use some other zend_extensions besides Opcache at the same time?

Thanks.
 [2017-02-01 12:03 UTC] uli dot hecht at gmai dot com
It takes a while for me to provide you a complete reproducer, that's why I want to give a little feedback:

At the moment I'm putting a lot of effort into this, since I cannot provide you the full code base of my clients. Therfore I started to reduce the code step by step as much as possible while continuously checking, whether the removed code and components have any effect on the problem. I installed the ReloadEvery Firefox addon, which reloads a page every few seconds. Everytime I change something, I enable this addon for an hour / until the error appears.

I already found two conditions to successfully reproduce the issue:
1. Symfony's "Assetic" bundle is in use
2. At least 2 assets (e.g. CSS files) are loaded simultaneously via Assetic, i.e. there are two separate HTTP requests for those assets.

I'll comment again when I my reproducer test project is finished.

No, I'm not using any other zend_extension. There are no other changes in php.ini besides the one I listed in the description.
 [2017-02-01 14:38 UTC] uli dot hecht at gmail dot com
Finally I could reproduce the problem without Symfony. The actual problem is simultaneous HTTP / PHP requests, that's why the error occured when loading assets generated by PHP.

Here is a little test script to reproduce the error: http://pastebin.com/vrguZM92
File name is "test1.php". It has to be invoked by a web browser via Apache2.

You have to reload this script in the browser again and again for about 1 minute. I suggest the Firefox addon "ReloadEvery".

I noticed that it takes much longer for the error to appear, when using this test script, compared to my full Symfony application. But the error with getInterfaceNames() is the same.

It looks like some non-thread-safe code is causing this, because I couldn't reproduce the error with non-concurrent requests at all.
 [2017-02-02 10:31 UTC] uli dot hecht at gmail dot com
-Status: Feedback +Status: Open -Package: opcache +Package: Reflection related
 [2017-02-02 10:31 UTC] uli dot hecht at gmail dot com
Today I reproduced the error with opcache disabled. Therefore I changed the package to "Reflection related".
Because of that I disabled all extensions, and against expectations the error still appears*.
The strange thing is that today it already appears after less than 10 browser refreshs, while I had to refresh consistently for at least a minute yesterday. No matter, if opcache is enabled or not. I didn't even restart my system (it was in standby mode, though).

*I had to replace the openssl_random_pseudo_bytes() call in the test script by uniqid().
 [2017-02-05 16:36 UTC] ab@php.net
-Status: Open +Status: Verified
 [2017-03-08 17:45 UTC] ab@php.net
-Assigned To: +Assigned To: ab
 [2017-03-08 17:45 UTC] ab@php.net
Could you please check the latest master snapshots?

Thanks.
 [2017-03-23 19:11 UTC] jeffrey dot haley at gmail dot com
Was able to reproduce this issue running under 7.0.17 with Windows 7 and the latest 7.0 snapshot master as of today 3/23/17 (php-7.0-ts-windows-vc14-x64-r1517fdb.

Environment
- Windows 7 x64
- Apache 2.4.23 win64 VC14 (from apachelounge.com)
- php-7.0.17-Win32-VC14-x64 (also tried 7.0-ts-windows-vc14-x64-r1517fdb)

------------------------------------------------------------------
test.php
------------------------------------------------------------------
// Test data
class DUMMY implements \Serializable, \IteratorAggregate, \Countable, \ArrayAccess {
    public function serialize() {}
    public function unserialize($serialized) {}
    public function getIterator() {}
    public function offsetExists($offset) {}
    public function offsetGet($offset) {}
    public function offsetSet($offset, $value) {}
    public function offsetUnset($offset) {}
    public function count() {}
}
 
// Provoke error
$cls = new \ReflectionClass('\DUMMY');
foreach ($cls->getInterfaceNames() as $interfaceName)
{
    class_implements($interfaceName); // Failure here
}


Occurs under load as described, I simulated this with the Apache benchmark tool:
   ab -c 100 -n 10000 "http://localhost/test.php"

Following error occurs in the error log:
   PHP Fatal error:  Allowed memory size of 1073741824 bytes exhausted (tried to allocate 8318823012549873016 bytes) in D:\development\test.php on line 23
 [2017-03-27 05:42 UTC] uli dot hecht at gmail dot com
I tried with the snapshot of March, 14th (7.1.4-dev) and can still reproduce this. I'm also using Apache benchmark now, thanks Jeffrey for the great hint - didn't even know about this tool.
 [2017-03-27 18:35 UTC] ab@php.net
-Status: Verified +Status: Feedback
 [2017-03-27 18:35 UTC] ab@php.net
I was asking explicitly about master,something like here http://windows.php.net/downloads/snaps/master/r5096a42/ . Please note, that latest were also to fetch from the windows.php.net page. We've recently switched to vc15, so you should be aware to fetch some of the previous vc14 master snap after the date i've asked 2017-03-08.

Thanks.
 [2017-03-29 12:20 UTC] ab@php.net
-Status: Feedback +Status: Closed
 [2017-03-29 12:20 UTC] ab@php.net
The fix for this bug has been committed.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.

 For Windows:

http://windows.php.net/snapshots/
 
Thank you for the report, and for helping us make PHP better.

Please check master snapshots.
 [2017-05-10 06:16 UTC] uli dot hecht at gmail dot com
Sorry, when you asked to check the latest master snapshot, I thought I'll get it here: http://windows.php.net/snapshots/ . Your linked snapshot works. However, the current stable PHP 7.1.5 still has this problem. Are there any further steps required by me? After you closed the bug, I thought everything is done and the next stable release will contain the fix.
 [2017-05-15 15:00 UTC] ab@php.net
Thanks for checking. Unfortunately there's no way for these fixes to land in lower PHP versions, as the changes are breaching. In between, there are already vc15 Apache builds provided by apachelounge.com, which are usable with our current master builds. Thus looking forward to PHP 7.2, that's it.

Thanks.
 
PHP Copyright © 2001-2022 The PHP Group
All rights reserved.
Last updated: Thu May 26 10:05:45 2022 UTC