Hello,
I'm CCing Dmitry Stogov as maintainer because he's listed as an author
in ext/opcache/ZendAccelerator.c and has recent commits.
I've attached a patch for bug #69090. You can find a more detailed
writeup at https://bugs.php.net/bug.php?id=69090 . In short, the patch
adds EUID or Windows username at the beginning of OPCache keys to
prevent cross-user cache access, which will hopefully alleviate security
concerns of enabling OPCache on shared hosting servers.
I took this in a different direction than that proposed in bug #69090
(prepending inode to key) because I feel it more effectively addresses
the cross-user security concerns.
I don't have a test script yet because the change is transparent to
scripts, but I could probably cobble one together by checking OPCache
debug log for key names. I do intend to port this forward to PHP7 head,
but in my opinion the existing behavior in 5.6 is a serious
vulnerability which warrants a maintenance patch. If needed I can
provide working exploit scripts to demonstrate how bad the existing
behavior is for shared servers using OPCache.
I was hoping to get some feedback before I put in the effort to port
this to PHP7.
Thanks,
--
- [email protected]
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index 985a4ef..3522861 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -52,6 +52,8 @@
typedef int uid_t;
typedef int gid_t;
#include <io.h>
+#include <Windows.h>
+#include <Lmcons.h> /* username max len */
#endif
#ifndef ZEND_WIN32
@@ -949,8 +951,36 @@ static unsigned int zend_accel_script_checksum(zend_persistent_script *persisten
char *accel_make_persistent_key_ex(zend_file_handle *file_handle, int path_length, int *key_len TSRMLS_DC)
{
int key_length;
+ int key_offset = 0;
+ char *user_id_str = NULL;
+ int user_id_len = 0;
- /* CWD and include_path don't matter for absolute file names and streams */
+#ifdef ZEND_WIN32
+ /* Windows has no direct equivalent of EUID. SID and RID are roughly
+ * analagous, but for now simply using the username seems most
+ * straightforward since we can get max len from UNLEN in Lmcons.h
+ */
+ int username_len = UNLEN + 1;
+ char username_str[UNLEN + 1]; /* not wide-character compatible */
+ if (GetUserName(username_str, &username_len) == 0) {
+ zend_accel_error(ACCEL_LOG_WARNING, "GetUserName for opcache key user_id_str failed!");
+ return NULL;
+ } else {
+ user_id_len = username_len - 1; /* subtract terminating zero */
+ user_id_str = username_str;
+ }
+#else
+ #define EUID_BUFFSIZE 40 /* hope we never see >128-bit uid_t's */
+ uid_t euid = geteuid();
+ char euid_str[EUID_BUFFSIZE];
+ if (user_id_len = snprintf(euid_str, EUID_BUFFSIZE - 1, "%d", euid) >= EUID_BUFFSIZE) {
+ return NULL;
+ } else {
+ user_id_str = euid_str;
+ }
+#endif
+
+ /* CWD and include_path don't matter for absolute file names and streams */
if (ZCG(accel_directives).use_cwd &&
!IS_ABSOLUTE_PATH(file_handle->filename, path_length) &&
!is_stream_path(file_handle->filename)) {
@@ -958,7 +988,6 @@ char *accel_make_persistent_key_ex(zend_file_handle *file_handle, int path_lengt
int include_path_len = 0;
const char *parent_script = NULL;
int parent_script_len = 0;
- int cur_len = 0;
int cwd_len;
char *cwd;
@@ -1027,7 +1056,7 @@ char *accel_make_persistent_key_ex(zend_file_handle *file_handle, int path_lengt
}
/* Calculate key length */
- key_length = cwd_len + path_length + include_path_len + 2;
+ key_length = user_id_len + cwd_len + path_length + include_path_len + 3; /* +3 for delimiter colons */
if (parent_script_len) {
key_length += parent_script_len + 1;
}
@@ -1036,39 +1065,57 @@ char *accel_make_persistent_key_ex(zend_file_handle *file_handle, int path_lengt
* Note - the include_path must be the last element in the key,
* since in itself, it may include colons (which we use to separate
* different components of the key)
- */
+ */
if ((size_t)key_length >= sizeof(ZCG(key))) {
ZCG(key_len) = 0;
return NULL;
}
- memcpy(ZCG(key), cwd, cwd_len);
- ZCG(key)[cwd_len] = ':';
-
- memcpy(ZCG(key) + cwd_len + 1, file_handle->filename, path_length);
-
- ZCG(key)[cwd_len + 1 + path_length] = ':';
- cur_len = cwd_len + 1 + path_length + 1;
-
- if (parent_script_len) {
- memcpy(ZCG(key) + cur_len, parent_script, parent_script_len);
- cur_len += parent_script_len;
- ZCG(key)[cur_len] = ':';
- cur_len++;
+ /* Key on euid to prevent cross-user cache access bypassing file
+ * permissions. Prevents filename collision in chroots IFF each
+ * chroot environment has a different user.
+ */
+ memcpy(ZCG(key), user_id_str, user_id_len);
+ key_offset += user_id_len;
+ ZCG(key)[key_offset] = ':';
+ key_offset++;
+
+ memcpy(ZCG(key + key_offset), cwd, cwd_len);
+ key_offset += cwd_len;
+ ZCG(key)[key_offset] = ':';
+ key_offset++;
+
+ memcpy(ZCG(key) + key_offset, file_handle->filename, path_length);
+ key_offset += path_length;
+ ZCG(key)[key_offset] = ':';
+ key_offset++;
+
+ if (parent_script_len) {
+ memcpy(ZCG(key) + key_offset, parent_script, parent_script_len);
+ key_offset += parent_script_len;
+ ZCG(key)[key_offset] = ':';
+ key_offset++;
}
- memcpy(ZCG(key) + cur_len, include_path, include_path_len);
+ memcpy(ZCG(key) + key_offset, include_path, include_path_len);
ZCG(key)[key_length] = '\0';
} else {
- /* not use_cwd */
- key_length = path_length;
+ /* not use_cwd and use_cwd cases where filename is absolute */
+ key_length = user_id_len + 1 + path_length; /* <EUID>:<path> */
if ((size_t)key_length >= sizeof(ZCG(key))) {
ZCG(key_len) = 0;
return NULL;
}
- memcpy(ZCG(key), file_handle->filename, key_length + 1);
+
+ memcpy(ZCG(key), user_id_str, user_id_len);
+ key_offset = user_id_len;
+ ZCG(key)[key_offset] = ':';
+ key_offset++;
+
+ memcpy(ZCG(key + key_offset), file_handle->filename, key_length + 1);
}
*key_len = ZCG(key_len) = key_length;
+ zend_accel_error(ACCEL_LOG_DEBUG, "make_persistent_key_ex() returning key: %s", ZCG(key));
return ZCG(key);
}
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php