php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #71261 Wrong bytecode in cache of array index named 'init' containing 0 indexed subarr
Submitted: 2016-01-01 17:48 UTC Modified: 2016-04-12 19:23 UTC
Votes:2
Avg. Score:4.0 ± 1.0
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:1 (100.0%)
From: jo at feuersee dot de Assigned: laruence (profile)
Status: Closed Package: opcache
PHP Version: 7.0.2 OS: Linux
Private report: No CVE-ID: None
 [2016-01-01 17:48 UTC] jo at feuersee dot de
Description:
------------
Let me first say that the test script below actually fails to reproduce the bug. I tried the whole day to provide a simple way to reproduce the problem, but without success.

But what happens is pretty much the execution of caller.php:
Another file is parsed via require() and assigned to a PHP variable. The problem is that the array index named 'init' seems to be incorrectly cached,  it is still an array, but index 0 always becomes 1.
Other (scalar) entries in $conf are not affected. To add some oddness:
- Only the array index called 'init' is affected. Even 'Init' does work, so it's pretty unlikely to happen.
- Only the subindex 0 is affected. Thus, a simple workaround this bug is to force the array keys to start with eg. 1
- When forcing the 1st entry to have the array key of -1, the 2nd array key (becoming the key 0 then) is affected.

The application where the bug occurs is way more complex involving ~30 files, autoloader and all the bells and whistles. I am, on the other side, sure that it's not an application bug, but a PHP issue.

That is because:
1) The application did and does work fine with older PHP versions until 5.6.15 (incl). Did not test with PHP7.0.0
2) The problem does not occur when Zend opcache is disabled
3) After altering and saving config.php (thus forcing the opcache to expire), the next execution will be perfectly fine. The next ones will always fail, until the cache is invalidated again
4) I nailed this behavior down by adding the print_r call directly next to the require call (just as in the test script), so there is no way the app is responsible (by altering the value of 'init' keys itself.

As mentioned, AFAICS it only occurs on array keys named 'init', so it's unlikely to happen. If it happends, the impact is sadly grave.

Test script:
---------------
config.php:
<?php
$conf['init'] = array(
  "SET NAMES'utf8'",
  "SHOW TABLES"
);
return $conf;
--eof

caller.php:
<?php
$conf = require 'config.php';
print_r($conf);
--eof

Expected result:
----------------
Array
(
    [init] => Array
        (
            [0] => SET NAMES 'utf8'
            [1] => SHOW TABLES
        )

)


Actual result:
--------------
Array
(
    [init] => Array
        (
            [0] => 1
            [1] => SHOW TABLES
        )

)


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-01-01 17:54 UTC] nikic@php.net
Could you check if the problem goes away if you run with opcache.optimization_level=0?
 [2016-01-01 18:03 UTC] jo at feuersee dot de
All opcache related ini settings were at default.

Changing opcache.optimization_level=0 did not help, the problem still occurs.
 [2016-01-01 18:11 UTC] nikic@php.net
Thanks, so the optimizer is not the culprit. Can you do a run with opcache.protect_memory=1 and see if it crashes (segfault or some such)? And if so, get a backtrace?
 [2016-01-01 19:35 UTC] jo at feuersee dot de
Yep, it crashes with opcache.protect_memory=1 "child pid 2844 exit signal Segmentation fault"

Unfortunately, xdebug fails to start due to API version 320151012.

Is there another convenient way to provide a backtrace?
 [2016-01-01 19:56 UTC] nikic@php.net
If your code is runnable using CLI, the easiest way to get a backtrace is through valgrind. Just run

    valgrind php ...args...

Alternatively, using gdb run "gdb --args php ...args..." and then "r" followed by "bt" in the gdb prompt.

If it doesn't run through cli it's probably easiest to get the backtrace from a core file, we have some info on that here: https://bugs.php.net/bugs-generating-backtrace.php
 [2016-01-01 21:00 UTC] jo at feuersee dot de
THX. Unfortunately, the used binaries for openSUSE Leap 42.1 (from http://download.opensuse.org/repositories/devel:/languages:/php:/php7/openSUSE_Leap_42.1/) have debug mode disabled. 

I'll check over the WE wether installing PHP7 from source is less painful than changing the app to work in CLI context.
 [2016-01-02 05:03 UTC] laruence@php.net
-Assigned To: +Assigned To: laruence
 [2016-01-02 05:03 UTC] laruence@php.net
you can also try to make it coredump with "opcache.protect_memory=1 ", and paste the backtrace here. thanks
 [2016-01-02 05:03 UTC] laruence@php.net
-Status: Assigned +Status: Feedback
 [2016-01-02 16:17 UTC] jo at feuersee dot de
-Status: Feedback +Status: Assigned
 [2016-01-02 16:17 UTC] jo at feuersee dot de
Would it be useful to provide a trace of PHP running with built-in webserver?

Running the script via CLI doesn't looks useful as I can't reproduce the bug there (the opcache is invalid after execution, thus always regenerated). And Apache2 doesn't core dump at all, can't see the problem why. This isn't my kind of cake...
 [2016-01-02 16:25 UTC] jo at feuersee dot de
jo@l33t ~/tmp> valgrind php -S localhost:8080 -t /srv/www/vhosts/www.feuersee.de/groupware/htdocs/
==20223== Memcheck, a memory error detector
==20223== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==20223== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==20223== Command: php -S localhost:8080 -t /srv/www/vhosts/www.feuersee.de/groupware/htdocs/
==20223==
PHP 7.0.1 Development Server started at Sat Jan  2 17:21:50 2016
Listening on http://localhost:8080
Document root is /srv/www/vhosts/www.feuersee.de/groupware/htdocs
Press Ctrl-C to quit.
==20223==
==20223== Process terminating with default action of signal 11 (SIGSEGV)
==20223==  Bad permissions for mapped region at address 0x16951778
==20223==    at 0x479C85: convert_to_boolean (in /usr/bin/php)
==20223==    by 0x10BCE357: pdo_mysql_set_attribute (in /usr/lib64/php7/extensions/pdo_mysql.so)
==20223==    by 0x109B43E9: pdo_dbh_attribute_set (in /usr/lib64/php7/extensions/pdo.so)
==20223==    by 0x109B2656: zim_PDO_dbh_constructor (in /usr/lib64/php7/extensions/pdo.so)
==20223==    by 0x51E9F1: ZEND_DO_FCALL_SPEC_HANDLER (in /usr/bin/php)
==20223==    by 0x51A851: execute_ex (in /usr/bin/php)
==20223==    by 0x51B1C5: zend_execute (in /usr/bin/php)
==20223==    by 0x48EC4C: zend_execute_scripts (in /usr/bin/php)
==20223==    by 0x3E208C: php_execute_script (in /usr/bin/php)
==20223==    by 0x5FDC21: php_cli_server_dispatch_script (in /usr/bin/php)
==20223==    by 0x5FEEF6: php_cli_server_dispatch (in /usr/bin/php)
==20223==    by 0x5FF81A: php_cli_server_recv_event_read_request (in /usr/bin/php)
==20223== Invalid free() / delete / delete[] / realloc()
==20223==    at 0x4C2A37C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==20223==    by 0x64AEB5B: __libc_freeres (in /lib64/libc-2.19.so)
==20223==    by 0x4A2370C: _vgnU_freeres (in /usr/lib64/valgrind/vgpreload_core-amd64-linux.so)
==20223==    by 0x2F: ???
==20223==    by 0x10BCE357: pdo_mysql_set_attribute (in /usr/lib64/php7/extensions/pdo_mysql.so)
==20223==    by 0x109B43E9: pdo_dbh_attribute_set (in /usr/lib64/php7/extensions/pdo.so)
==20223==    by 0x109B2656: zim_PDO_dbh_constructor (in /usr/lib64/php7/extensions/pdo.so)
==20223==    by 0x51E9F1: ZEND_DO_FCALL_SPEC_HANDLER (in /usr/bin/php)
==20223==    by 0x51A851: execute_ex (in /usr/bin/php)
==20223==    by 0x51B1C5: zend_execute (in /usr/bin/php)
==20223==    by 0x48EC4C: zend_execute_scripts (in /usr/bin/php)
==20223==    by 0x3E208C: php_execute_script (in /usr/bin/php)
==20223==  Address 0x67072d0 is 0 bytes inside data symbol "noai6ai_cached"
==20223==
==20223==
==20223== HEAP SUMMARY:
==20223==     in use at exit: 2,922,691 bytes in 27,842 blocks
==20223==   total heap usage: 35,880 allocs, 8,039 frees, 5,101,975 bytes allocated
==20223==
==20223== LEAK SUMMARY:
==20223==    definitely lost: 0 bytes in 0 blocks
==20223==    indirectly lost: 0 bytes in 0 blocks
==20223==      possibly lost: 2,036,653 bytes in 21,653 blocks
==20223==    still reachable: 886,038 bytes in 6,189 blocks
==20223==         suppressed: 0 bytes in 0 blocks
==20223== Rerun with --leak-check=full to see details of leaked memory
==20223==
==20223== For counts of detected and suppressed errors, rerun with: -v
==20223== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Segmentation fault
 [2016-01-02 16:43 UTC] laruence@php.net
could you please try with this quick fix?
diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
index cda07fb..a0b310e 100644
--- a/ext/pdo/pdo_dbh.c
+++ b/ext/pdo/pdo_dbh.c
@@ -374,7 +374,7 @@ static PHP_METHOD(PDO, dbh_constructor)
                dbh->driver = driver;
 options:
                if (options) {
-                   zval *attr_value;
+                 zval copy, *attr_value;
                        zend_ulong long_key;
                        zend_string *str_key = NULL;

@@ -382,7 +382,9 @@ options:
                                if (str_key) {
                                        continue;
                                }
-                           pdo_dbh_attribute_set(dbh, long_key, attr_value);
+                         ZVAL_COPY(&copy, attr_value);
+                         pdo_dbh_attribute_set(dbh, long_key, &copy);
+                         zval_ptr_dtor(&copy);
                        } ZEND_HASH_FOREACH_END();
                }

I will do a better fix if it works
 [2016-01-02 16:44 UTC] laruence@php.net
-Status: Assigned +Status: Feedback
 [2016-01-03 22:25 UTC] jo at feuersee dot de
-Status: Feedback +Status: Assigned
 [2016-01-03 22:25 UTC] jo at feuersee dot de
I could try to test the patch, but I have to admit that compiling PHP from sources ist't an my daily schedule for at lest the last 10 yrs. This would take some time.

On the other hand, AFAICS we are talking about a patch of PDO. I really don't think this bug is PDO related, it is opcache. The result is - in my app - that the valid SQL query "SET NAMES 'utf8'" gets evaluated to "1" which isn't a valid SQL statement at all.

I thought that someone who does maintain the opcode cache could look up the 'init' string and/or related code and goes "yeah, we really screwed this up" and fix it.

OK, if it's not this way I'll try the hard way.

Happy new year everyone, besides ;)
 [2016-01-05 05:43 UTC] laruence@php.net
the problem probably be a array which is cached in opcache is altered in pdo_set_attribute. 

anyway, I committed a fix here: https://github.com/php/php-src/commit/36b4311edd3e2dec31de1582c207e83b09d6f42b

maybe you could try with the latest snapshot ..
 [2016-01-09 10:20 UTC] jo at feuersee dot de
-PHP Version: 7.0.1 +PHP Version: 7.0.2
 [2016-01-09 10:20 UTC] jo at feuersee dot de
Still the same with PHP 7.0.2
 [2016-04-12 19:23 UTC] jo at feuersee dot de
-Status: Assigned +Status: Closed
 [2016-04-12 19:23 UTC] jo at feuersee dot de
Can't reproduce with PHP 7.0.5
Did noch check PHP 7.0.3 or .4
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Apr 20 16:01:29 2024 UTC