From ecba563f2fa1e027ea91b9ee0d50611273852995 Mon Sep 17 00:00:00 2001
From: Dmitry Stogov <dmitry@zend.com>
Date: Wed, 16 Nov 2016 12:43:10 +0300
Subject: [PATCH] Fixed bug #69090 (check cached files permissions)

Index: php-7.0.7/ext/opcache/README
===================================================================
--- php-7.0.7.orig/ext/opcache/README	2016-05-25 15:14:16.000000000 +0200
+++ php-7.0.7/ext/opcache/README	2017-03-10 13:17:00.810760946 +0100
@@ -102,6 +102,16 @@ opcache.validate_timestamps (default "1"
 	The frequency of the check is controlled by the directive
 	"opcache.revalidate_freq".
 
+opcache.validate_permission (default "0")
+	Leads OPcache to check file readability on each access to cached file.
+	This directive should be enabled in shared hosting environment, when few
+	users (PHP-FPM pools) reuse the common OPcache shared memory.
+
+opcache.validate_root (default "0")
+	This directive prevents file name collisions in different "chroot"
+	environments. It should be enabled for sites that may serve requests in
+	different "chroot" environments.
+
 opcache.revalidate_freq (default "2")
 	How often (in seconds) to check file timestamps for changes to the shared
 	memory storage allocation. ("1" means validate once per second, but only
Index: php-7.0.7/ext/opcache/ZendAccelerator.c
===================================================================
--- php-7.0.7.orig/ext/opcache/ZendAccelerator.c	2016-05-25 15:14:16.000000000 +0200
+++ php-7.0.7/ext/opcache/ZendAccelerator.c	2017-03-10 13:19:16.508960740 +0100
@@ -1728,6 +1728,28 @@ zend_op_array *persistent_compile_file(z
 		ZCG(counted) = 1;
 	}
 
+	/* Revalidate acessibility of cached file */
+	if (EXPECTED(persistent_script != NULL) &&
+	    UNEXPECTED(ZCG(accel_directives).validate_permission) &&
+	    file_handle->type == ZEND_HANDLE_FILENAME &&
+	    UNEXPECTED(access(file_handle->filename, R_OK) != 0)) {
+		if (type == ZEND_REQUIRE) {
+#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO
+			zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, file_handle->filename);
+#else
+			zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, file_handle->filename TSRMLS_CC);
+#endif
+			zend_bailout();
+		} else {
+#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO
+			zend_message_dispatcher(ZMSG_FAILED_INCLUDE_FOPEN, file_handle->filename);
+#else
+			zend_message_dispatcher(ZMSG_FAILED_INCLUDE_FOPEN, file_handle->filename TSRMLS_CC);
+#endif
+		}
+		return NULL;
+	}
+
 	SHM_UNPROTECT();
 
 	/* If script is found then validate_timestamps if option is enabled */
@@ -2007,6 +2029,31 @@ static void accel_activate(void)
 		return;
 	}
 
+#ifndef ZEND_WIN32
+	if (ZCG(accel_directives).validate_root) {
+		struct stat buf;
+
+		if (stat("/", &buf) != 0) {
+			ZCG(root_hash) = 0;
+		} else {
+			unsigned long x = buf.st_ino;
+
+#if SIZEOF_LONG == 4
+			x = ((x >> 16) ^ x) * 0x45d9f3b;
+			x = ((x >> 16) ^ x) * 0x45d9f3b;
+			x = (x >> 16) ^ x;
+#elif SIZEOF_LONG == 8
+			x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
+			x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
+			x = x ^ (x >> 31);
+#endif
+			ZCG(root_hash) = x;
+		}
+	} else {
+		ZCG(root_hash) = 0;
+	}
+#endif
+
 	if (!ZCG(function_table).nTableSize) {
 		zend_hash_init(&ZCG(function_table), zend_hash_num_elements(CG(function_table)), NULL, ZEND_FUNCTION_DTOR, 1);
 		zend_accel_copy_internal_functions();
Index: php-7.0.7/ext/opcache/ZendAccelerator.h
===================================================================
--- php-7.0.7.orig/ext/opcache/ZendAccelerator.h	2017-03-10 13:17:00.810760946 +0100
+++ php-7.0.7/ext/opcache/ZendAccelerator.h	2017-03-10 13:20:58.326623536 +0100
@@ -204,6 +204,8 @@ typedef struct _zend_accel_directives {
 	zend_bool      file_override_enabled;
 	zend_bool      inherited_hack;
 	zend_bool      enable_cli;
+	zend_bool      validate_permission;
+	zend_bool      validate_root;
 	zend_ulong  revalidate_freq;
 	zend_ulong  file_update_protection;
 	char          *error_log;
@@ -256,6 +258,9 @@ typedef struct _zend_accel_globals {
 	time_t                  last_restart_time; /* used to synchronize SHM and in-process caches */
 	char                    system_id[32];
 	HashTable               xlat_table;
+#ifndef ZEND_WIN32
+	unsigned long           root_hash;
+#endif
 	/* preallocated shared-memory block to save current script */
 	void                   *mem;
 	void                   *arena_mem;
Index: php-7.0.7/ext/opcache/zend_accelerator_hash.c
===================================================================
--- php-7.0.7.orig/ext/opcache/zend_accelerator_hash.c	2016-05-25 15:14:16.000000000 +0200
+++ php-7.0.7/ext/opcache/zend_accelerator_hash.c	2017-03-10 13:29:04.454560722 +0100
@@ -86,6 +86,9 @@ zend_accel_hash_entry* zend_accel_hash_u
 	}
 
 	hash_value = zend_inline_hash_func(key, key_length);
+#ifndef ZEND_WIN32
+	hash_value ^= ZCG(root_hash);
+#endif
 	index = hash_value % accel_hash->max_num_entries;
 
 	/* try to see if the element already exists in the hash */
@@ -142,6 +145,10 @@ static zend_always_inline void* zend_acc
 	zend_ulong index = hash_value % accel_hash->max_num_entries;
 	zend_accel_hash_entry *entry = accel_hash->hash_table[index];
 
+#ifndef ZEND_WIN32
+       hash_value ^= ZCG(root_hash);
+#endif
+
 	while (entry) {
 		if (entry->hash_value == hash_value
 			&& entry->key_length == key_length
@@ -224,6 +231,9 @@ int zend_accel_hash_unlink(zend_accel_ha
     zend_accel_hash_entry *entry, *last_entry=NULL;
 
 	hash_value = zend_inline_hash_func(key, key_length);
+#ifndef ZEND_WIN32
+	hash_value ^= ZCG(root_hash);
+#endif
 	index = hash_value % accel_hash->max_num_entries;
 
 	entry = accel_hash->hash_table[index];
Index: php-7.0.7/ext/opcache/zend_accelerator_module.c
===================================================================
--- php-7.0.7.orig/ext/opcache/zend_accelerator_module.c	2016-05-25 15:14:16.000000000 +0200
+++ php-7.0.7/ext/opcache/zend_accelerator_module.c	2017-03-10 13:30:45.672212653 +0100
@@ -272,6 +272,8 @@ ZEND_INI_BEGIN()
 	STD_PHP_INI_BOOLEAN("opcache.enable"             , "1", PHP_INI_ALL,    OnEnable,     enabled                             , zend_accel_globals, accel_globals)
 	STD_PHP_INI_BOOLEAN("opcache.use_cwd"            , "1", PHP_INI_SYSTEM, OnUpdateBool, accel_directives.use_cwd            , zend_accel_globals, accel_globals)
 	STD_PHP_INI_BOOLEAN("opcache.validate_timestamps", "1", PHP_INI_ALL   , OnUpdateBool, accel_directives.validate_timestamps, zend_accel_globals, accel_globals)
+	STD_PHP_INI_BOOLEAN("opcache.validate_permission", "0", PHP_INI_SYSTEM, OnUpdateBool, accel_directives.validate_permission, zend_accel_globals, accel_globals)
+	STD_PHP_INI_BOOLEAN("opcache.validate_root"      , "0", PHP_INI_SYSTEM, OnUpdateBool, accel_directives.validate_root      , zend_accel_globals, accel_globals)
 	STD_PHP_INI_BOOLEAN("opcache.inherited_hack"     , "1", PHP_INI_SYSTEM, OnUpdateBool, accel_directives.inherited_hack     , zend_accel_globals, accel_globals)
 	STD_PHP_INI_BOOLEAN("opcache.dups_fix"           , "0", PHP_INI_ALL   , OnUpdateBool, accel_directives.ignore_dups        , zend_accel_globals, accel_globals)
 	STD_PHP_INI_BOOLEAN("opcache.revalidate_path"    , "0", PHP_INI_ALL   , OnUpdateBool, accel_directives.revalidate_path    , zend_accel_globals, accel_globals)
@@ -690,6 +692,8 @@ static ZEND_FUNCTION(opcache_get_configu
 	add_assoc_bool(&directives, "opcache.enable_cli",          ZCG(accel_directives).enable_cli);
 	add_assoc_bool(&directives, "opcache.use_cwd",             ZCG(accel_directives).use_cwd);
 	add_assoc_bool(&directives, "opcache.validate_timestamps", ZCG(accel_directives).validate_timestamps);
+	add_assoc_bool(&directives, "opcache.validate_permission", ZCG(accel_directives).validate_permission);
+	add_assoc_bool(&directives, "opcache.validate_root",       ZCG(accel_directives).validate_root);
 	add_assoc_bool(&directives, "opcache.inherited_hack",      ZCG(accel_directives).inherited_hack);
 	add_assoc_bool(&directives, "opcache.dups_fix",            ZCG(accel_directives).ignore_dups);
 	add_assoc_bool(&directives, "opcache.revalidate_path",     ZCG(accel_directives).revalidate_path);
