php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #70656 require() statement broken after opcache_reset() or a few hours of use
Submitted: 2015-10-07 04:40 UTC Modified: 2015-11-01 03:53 UTC
Votes:9
Avg. Score:4.6 ± 0.7
Reproduced:9 of 9 (100.0%)
Same Version:6 (66.7%)
Same OS:7 (77.8%)
From: p at wspnr dot com Assigned: laruence
Status: Closed Package: opcache
PHP Version: 7.0Git-2015-10-07 (Git) OS: Debian (x86_64 and i686)
Private report: No CVE-ID:
 [2015-10-07 04:40 UTC] p at wspnr dot com
Description:
------------
Calling \opcache_reset() breaks the "require" statement on future page loads. The only thing that seems to fix it is restarting PHP-FPM.

The require fails, stating that it can't find the file path. The file path listed begins with the correct path, but is then followed by junk data. The 'junk' data seems to be other strings from the file(s).

Tested and reproduced on both 64-bit and 32-bit builds.

Test script:
---------------
This is split into three files, running with opcache enabled:

-- file1.php --
<?php

namespace my\nsname;

require 'file3.php';

class MyClass implements MyInterface {}

new MyClass();

echo 'I am file 1';

-- file2.php --
<?php

namespace my\nsname;

\opcache_reset();

echo 'I am file 2';

-- file3.php --
<?php

namespace my\nsname;

interface MyInterface {}

echo 'I am file 3';

-- TEST --
Load, in sequence:

file1.php
file2.php
file3.php
file1.php


Expected result:
----------------
I am file 3
I am file 1
I am file 2
I am file 3
I am file 3
I am file 1

Actual result:
--------------
I am file 3
I am file 1
I am file 2
I am file 3
Fatal error: require(): Failed opening required 'file3.php\myinte' (include_path='.:') in /.../file1.php on line 5

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-10-22 11:45 UTC] ealexs at gmail dot com
Just using PHP7, I don't call opcache_reset()
after a while I get something like:

<b>Fatal error</b>:  require_once(): Failed opening required '/var/www/~users/alex/php7/omi-frame-2/src/controller/App.phpb_my' (include_path='.:/opt/php-7.0.0/lib/php') in <b>/var/www/~users/alex/php7/omi-frame-2/src/init.php</b> on line <b>5</b><br />

It looks like the ending string mark is not copied 
I need to restart PHP-FPM 2 or 3 times per day

Thanks,
Alex
 [2015-10-23 06:20 UTC] ealexs at gmail dot com
This bug is important, it breaks opcache until php-fpm gets a restart !!!

It looks like a very simple fix !!! Probably a bad memcopy there, it does not copy the ending \0 

Thanks
 [2015-10-24 17:35 UTC] p at wspnr dot com
Can confirm: while this happens reliably with \opcache_reset(), we end up running into this issue after a couple of hours of regular use as well.
 [2015-10-25 15:55 UTC] p at wspnr dot com
-Summary: opcache_reset() breaks "require" +Summary: require() statement broken after opcache_reset() or a few hours of use
 [2015-10-25 15:55 UTC] p at wspnr dot com
Updated bug summary
 [2015-10-30 12:36 UTC] machin dot dmitry at gmail dot com
The same bug.
Ubuntu 14.04.3 LTS
PHP 7.0.1-dev (cli) (built: Oct 29 2015 10:15:42) ( NTS )

from PHP-7.0 branch
 [2015-10-30 17:45 UTC] p at wspnr dot com
I spent some time today tracing this bug as it is preventing our migration to PHP 7.

The bug is caused by the opcache interned strings functionality. A simple workaround is to set

opcache.interned_strings_buffer = 0

in php.ini.

Looking at the code, I believe that the bug might be caused by this [0] call to memcpy(). Changing this to 

memcpy(ZSTR_VAL(p->key), ZSTR_VAL(str), ZSTR_LEN(str) + 1);

has made the issue go away. However I am not familiar enough with opcache to determine whether this is an acceptable change.

[0] http://lxr.php.net/xref/PHP_MASTER/ext/opcache/ZendAccelerator.c#374
 [2015-10-31 08:09 UTC] tony dot levi at blackboard dot com
I can confirm that workaround and proposed fix solve the issue for me.

It looks like the right solution - based on pattern elsewhere - but I'm not an expert either.
 [2015-10-31 17:49 UTC] kalle@php.net
-Status: Open +Status: Assigned -Assigned To: +Assigned To: laruence
 [2015-10-31 17:49 UTC] kalle@php.net
Hey Xinchen, can you confirm this fix is proper or clarify the behavior? Thanks
 [2015-11-01 03:30 UTC] laruence@php.net
actually, it's intentional only copy Z_STRLEN, please verify this fix:

diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index eaadac6..18862b3 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -249,24 +249,27 @@ static void accel_interned_strings_restore_state(void)
     uint nIndex;
     Bucket *p;

-	ZCSG(interned_strings_top) = ZCSG(interned_strings_saved_top);
-    while (idx > 0) {
-    	idx--;
-		p = ZCSG(interned_strings).arData + idx;
-		if ((char*)p->key < ZCSG(interned_strings_top)) break;
-		ZCSG(interned_strings).nNumUsed--;
-		ZCSG(interned_strings).nNumOfElements--;
-
-		nIndex = p->h | ZCSG(interned_strings).nTableMask;
-		if (HT_HASH(&ZCSG(interned_strings), nIndex) == HT_IDX_TO_HASH(idx)) {
-			HT_HASH(&ZCSG(interned_strings), nIndex) = Z_NEXT(p->val);
-		} else {
-			uint32_t prev = HT_HASH(&ZCSG(interned_strings), nIndex);
-			while (Z_NEXT(HT_HASH_TO_BUCKET(&ZCSG(interned_strings), prev)->val) != idx) {
-				prev = Z_NEXT(HT_HASH_TO_BUCKET(&ZCSG(interned_strings), prev)->val);
- 			}
-			Z_NEXT(HT_HASH_TO_BUCKET(&ZCSG(interned_strings), prev)->val) = Z_NEXT(p->val);
- 		}
+	if (EXPECTED(ZCSG(interned_strings_top) > ZCSG(interned_strings_saved_top))) {
+		memset(ZCSG(interned_strings_saved_top),0, ZCSG(interned_strings_top) - ZCSG(interned_strings_saved_top));
+		ZCSG(interned_strings_top) = ZCSG(interned_strings_saved_top);
+		while (idx > 0) {
+			idx--;
+			p = ZCSG(interned_strings).arData + idx;
+			if ((char*)p->key < ZCSG(interned_strings_top)) break;
+			ZCSG(interned_strings).nNumUsed--;
+			ZCSG(interned_strings).nNumOfElements--;
+
+			nIndex = p->h | ZCSG(interned_strings).nTableMask;
+			if (HT_HASH(&ZCSG(interned_strings), nIndex) == HT_IDX_TO_HASH(idx)) {
+				HT_HASH(&ZCSG(interned_strings), nIndex) = Z_NEXT(p->val);
+			} else {
+				uint32_t prev = HT_HASH(&ZCSG(interned_strings), nIndex);
+				while (Z_NEXT(HT_HASH_TO_BUCKET(&ZCSG(interned_strings), prev)->val) != idx) {
+					prev = Z_NEXT(HT_HASH_TO_BUCKET(&ZCSG(interned_strings), prev)->val);
+				}
+				Z_NEXT(HT_HASH_TO_BUCKET(&ZCSG(interned_strings), prev)->val) = Z_NEXT(p->val);
+			}
+		}
 	}
 }
 [2015-11-01 03:53 UTC] p at wspnr dot com
Applied patch and recompiled. The bug seems to be resolved.
 [2015-11-01 04:42 UTC] tony dot levi at blackboard dot com
Confirming that patch solves it for me too.
 [2015-11-01 10:08 UTC] laruence@php.net
Automatic comment on behalf of laruence@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=3ef96c2cc6f3a1dddd8479e6c733f44e8dbcb5e9
Log: Fixed bug #70656 (require() statement broken after opcache_reset() or a few hours of use)
 [2015-11-01 10:08 UTC] laruence@php.net
-Status: Assigned +Status: Closed
 [2015-11-01 10:26 UTC] laruence@php.net
Automatic comment on behalf of laruence@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=008d8b6789081241c9aaafc13f6f28896e079b99
Log: Fixed bug #70656 (require() statement broken after opcache_reset() or a few hours of use)
 [2015-11-01 10:26 UTC] laruence@php.net
Automatic comment on behalf of laruence@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=31553f07f25184ff451ddb3c91c3cc90b88c6da1
Log: Revert &quot;Fixed bug #70656 (require() statement broken after opcache_reset() or a few hours of use)&quot;
 [2015-11-09 18:12 UTC] ab@php.net
Automatic comment on behalf of laruence@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c600bdcb79af7582c9de526ba569ac26515a9f62
Log: Fixed bug #70656 (require() statement broken after opcache_reset() or a few hours of use)
 [2016-07-20 11:35 UTC] davey@php.net
Automatic comment on behalf of laruence@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=008d8b6789081241c9aaafc13f6f28896e079b99
Log: Fixed bug #70656 (require() statement broken after opcache_reset() or a few hours of use)
 [2016-07-20 11:35 UTC] davey@php.net
Automatic comment on behalf of laruence@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=31553f07f25184ff451ddb3c91c3cc90b88c6da1
Log: Revert &quot;Fixed bug #70656 (require() statement broken after opcache_reset() or a few hours of use)&quot;
 [2016-07-20 11:35 UTC] davey@php.net
Automatic comment on behalf of laruence@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=3ef96c2cc6f3a1dddd8479e6c733f44e8dbcb5e9
Log: Fixed bug #70656 (require() statement broken after opcache_reset() or a few hours of use)
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Mon May 29 04:01:35 2017 UTC