1 /*
2 +----------------------------------------------------------------------+
3 | Zend OPcache |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1998-2016 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Andi Gutmans <andi@zend.com> |
16 | Zeev Suraski <zeev@zend.com> |
17 | Stanislav Malyshev <stas@zend.com> |
18 | Dmitry Stogov <dmitry@zend.com> |
19 +----------------------------------------------------------------------+
20 */
21
22 #include "ZendAccelerator.h"
23 #include "zend_shared_alloc.h"
24 #include "zend_accelerator_util_funcs.h"
25 #include <winbase.h>
26 #include <process.h>
27 #include <LMCONS.H>
28
29 #include "main/php.h"
30 #include "ext/standard/md5.h"
31
32 #define ACCEL_FILEMAP_NAME "ZendOPcache.SharedMemoryArea"
33 #define ACCEL_MUTEX_NAME "ZendOPcache.SharedMemoryMutex"
34 #define ACCEL_FILEMAP_BASE_DEFAULT 0x01000000
35 #define ACCEL_FILEMAP_BASE "ZendOPcache.MemoryBase"
36 #define ACCEL_EVENT_SOURCE "Zend OPcache"
37
38 static HANDLE memfile = NULL, memory_mutex = NULL;
39 static void *mapping_base;
40
41 #define MAX_MAP_RETRIES 25
42
zend_win_error_message(int type,char * msg,int err)43 static void zend_win_error_message(int type, char *msg, int err)
44 {
45 LPVOID lpMsgBuf;
46 HANDLE h;
47 char *ev_msgs[2];
48
49 FormatMessage(
50 FORMAT_MESSAGE_ALLOCATE_BUFFER |
51 FORMAT_MESSAGE_FROM_SYSTEM |
52 FORMAT_MESSAGE_IGNORE_INSERTS,
53 NULL,
54 err,
55 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
56 (LPTSTR) &lpMsgBuf,
57 0,
58 NULL
59 );
60
61 h = RegisterEventSource(NULL, TEXT(ACCEL_EVENT_SOURCE));
62 ev_msgs[0] = msg;
63 ev_msgs[1] = lpMsgBuf;
64 ReportEvent(h, // event log handle
65 EVENTLOG_ERROR_TYPE, // event type
66 0, // category zero
67 err, // event identifier
68 NULL, // no user security identifier
69 2, // one substitution string
70 0, // no data
71 ev_msgs, // pointer to string array
72 NULL); // pointer to data
73 DeregisterEventSource(h);
74
75 LocalFree( lpMsgBuf );
76
77 zend_accel_error(type, msg);
78 }
79
80 /* Backported from 7.0, the way no globals are touched which means win32
81 only where it's essentially needed. */
82 #define ZEND_BIN_ID "BIN_" ZEND_TOSTR(SIZEOF_CHAR) ZEND_TOSTR(SIZEOF_INT) ZEND_TOSTR(SIZEOF_LONG) ZEND_TOSTR(SIZEOF_SIZE_T) ZEND_TOSTR(SIZEOF_ZEND_LONG) ZEND_TOSTR(ZEND_MM_ALIGNMENT)
accel_gen_system_id(void)83 static char *accel_gen_system_id(void)
84 {
85 PHP_MD5_CTX context;
86 unsigned char digest[16], c;
87 int i;
88 static char md5str[32];
89 static zend_bool done = 0;
90
91 if (done) {
92 return md5str;
93 }
94
95 PHP_MD5Init(&context);
96 PHP_MD5Update(&context, PHP_VERSION, sizeof(PHP_VERSION)-1);
97 PHP_MD5Update(&context, ZEND_EXTENSION_BUILD_ID, sizeof(ZEND_EXTENSION_BUILD_ID)-1);
98 PHP_MD5Update(&context, ZEND_BIN_ID, sizeof(ZEND_BIN_ID)-1);
99 if (strstr(PHP_VERSION, "-dev") != 0) {
100 /* Development versions may be changed from build to build */
101 PHP_MD5Update(&context, __DATE__, sizeof(__DATE__)-1);
102 PHP_MD5Update(&context, __TIME__, sizeof(__TIME__)-1);
103 }
104 PHP_MD5Final(digest, &context);
105 for (i = 0; i < 16; i++) {
106 c = digest[i] >> 4;
107 c = (c <= 9) ? c + '0' : c - 10 + 'a';
108 md5str[i * 2] = c;
109 c = digest[i] & 0x0f;
110 c = (c <= 9) ? c + '0' : c - 10 + 'a';
111 md5str[(i * 2) + 1] = c;
112 }
113
114 done = 1;
115
116 return md5str;
117 }
118
create_name_with_username(char * name)119 static char *create_name_with_username(char *name)
120 {
121 static char newname[MAXPATHLEN + UNLEN + 4 + 1 + 32];
122 char uname[UNLEN + 1];
123 DWORD unsize = UNLEN;
124
125 GetUserName(uname, &unsize);
126 snprintf(newname, sizeof(newname) - 1, "%s@%s@%.32s", name, uname, accel_gen_system_id());
127 return newname;
128 }
129
get_mmap_base_file(void)130 static char *get_mmap_base_file(void)
131 {
132 static char windir[MAXPATHLEN+UNLEN + 3 + sizeof("\\\\@") + 1 + 32];
133 char uname[UNLEN + 1];
134 DWORD unsize = UNLEN;
135 int l;
136
137 GetTempPath(MAXPATHLEN, windir);
138 GetUserName(uname, &unsize);
139 l = strlen(windir);
140 snprintf(windir + l, sizeof(windir) - l - 1, "\\%s@%s@%.32s", ACCEL_FILEMAP_BASE, uname, accel_gen_system_id());
141 return windir;
142 }
143
zend_shared_alloc_create_lock(void)144 void zend_shared_alloc_create_lock(void)
145 {
146 memory_mutex = CreateMutex(NULL, FALSE, create_name_with_username(ACCEL_MUTEX_NAME));
147 if (!memory_mutex) {
148 zend_accel_error(ACCEL_LOG_FATAL, "Cannot create mutex");
149 return;
150 }
151 ReleaseMutex(memory_mutex);
152 }
153
zend_shared_alloc_lock_win32(void)154 void zend_shared_alloc_lock_win32(void)
155 {
156 DWORD waitRes = WaitForSingleObject(memory_mutex, INFINITE);
157
158 if (waitRes == WAIT_FAILED) {
159 zend_accel_error(ACCEL_LOG_ERROR, "Cannot lock mutex");
160 }
161 }
162
zend_shared_alloc_unlock_win32(void)163 void zend_shared_alloc_unlock_win32(void)
164 {
165 ReleaseMutex(memory_mutex);
166 }
167
zend_shared_alloc_reattach(size_t requested_size,char ** error_in)168 static int zend_shared_alloc_reattach(size_t requested_size, char **error_in)
169 {
170 int err;
171 void *wanted_mapping_base;
172 char *mmap_base_file = get_mmap_base_file();
173 FILE *fp = fopen(mmap_base_file, "r");
174 MEMORY_BASIC_INFORMATION info;
175
176 err = GetLastError();
177 if (!fp) {
178 zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
179 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open base address file", err);
180 *error_in="fopen";
181 return ALLOC_FAILURE;
182 }
183 if (!fscanf(fp, "%p", &wanted_mapping_base)) {
184 err = GetLastError();
185 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to read base address", err);
186 *error_in="read mapping base";
187 fclose(fp);
188 return ALLOC_FAILURE;
189 }
190 fclose(fp);
191
192 /* Check if the requested address space is free */
193 if (VirtualQuery(wanted_mapping_base, &info, sizeof(info)) == 0 ||
194 info.State != MEM_FREE ||
195 info.RegionSize < requested_size) {
196 err = ERROR_INVALID_ADDRESS;
197 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
198 return ALLOC_FAILURE;
199 }
200
201 mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, wanted_mapping_base);
202 err = GetLastError();
203
204 if (mapping_base == NULL) {
205 if (err == ERROR_INVALID_ADDRESS) {
206 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
207 return ALLOC_FAILURE;
208 }
209 return ALLOC_FAIL_MAPPING;
210 }
211 smm_shared_globals = (zend_smm_shared_globals *) mapping_base;
212
213 return SUCCESSFULLY_REATTACHED;
214 }
215
create_segments(size_t requested_size,zend_shared_segment *** shared_segments_p,int * shared_segments_count,char ** error_in)216 static int create_segments(size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, char **error_in)
217 {
218 int err, ret;
219 zend_shared_segment *shared_segment;
220 int map_retries = 0;
221 void *default_mapping_base_set[] = { 0, 0 };
222 /* TODO:
223 improve fixed addresses on x64. It still makes no sense to do it as Windows addresses are virtual per se and can or should be randomized anyway
224 through Address Space Layout Radomization (ASLR). We can still let the OS do its job and be sure that each process gets the same address if
225 desired. Not done yet, @zend refused but did not remember the exact reason, pls add info here if one of you know why :)
226 */
227 #if defined(_WIN64)
228 void *vista_mapping_base_set[] = { (void *) 0x0000100000000000, (void *) 0x0000200000000000, (void *) 0x0000300000000000, (void *) 0x0000700000000000, 0 };
229 #else
230 void *vista_mapping_base_set[] = { (void *) 0x20000000, (void *) 0x21000000, (void *) 0x30000000, (void *) 0x31000000, (void *) 0x50000000, 0 };
231 #endif
232 void **wanted_mapping_base = default_mapping_base_set;
233 TSRMLS_FETCH();
234
235 zend_shared_alloc_lock_win32();
236 /* Mapping retries: When Apache2 restarts, the parent process startup routine
237 can be called before the child process is killed. In this case, the map will fail
238 and we have to sleep some time (until the child releases the mapping object) and retry.*/
239 do {
240 memfile = OpenFileMapping(FILE_MAP_WRITE, 0, create_name_with_username(ACCEL_FILEMAP_NAME));
241 err = GetLastError();
242 if (memfile == NULL) {
243 break;
244 }
245
246 ret = zend_shared_alloc_reattach(requested_size, error_in);
247 err = GetLastError();
248 if (ret == ALLOC_FAIL_MAPPING) {
249 /* Mapping failed, wait for mapping object to get freed and retry */
250 CloseHandle(memfile);
251 memfile = NULL;
252 if (++map_retries >= MAX_MAP_RETRIES) {
253 break;
254 }
255 zend_shared_alloc_unlock_win32();
256 Sleep(1000 * (map_retries + 1));
257 zend_shared_alloc_lock_win32();
258 } else {
259 zend_shared_alloc_unlock_win32();
260 return ret;
261 }
262 } while (1);
263
264 if (map_retries == MAX_MAP_RETRIES) {
265 zend_shared_alloc_unlock_win32();
266 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open file mapping", err);
267 *error_in = "OpenFileMapping";
268 return ALLOC_FAILURE;
269 }
270
271 /* creating segment here */
272 *shared_segments_count = 1;
273 *shared_segments_p = (zend_shared_segment **) calloc(1, sizeof(zend_shared_segment)+sizeof(void *));
274 if (!*shared_segments_p) {
275 zend_shared_alloc_unlock_win32();
276 zend_win_error_message(ACCEL_LOG_FATAL, "calloc() failed", GetLastError());
277 *error_in = "calloc";
278 return ALLOC_FAILURE;
279 }
280 shared_segment = (zend_shared_segment *)((char *)(*shared_segments_p) + sizeof(void *));
281 (*shared_segments_p)[0] = shared_segment;
282
283 memfile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, requested_size,
284 create_name_with_username(ACCEL_FILEMAP_NAME));
285 err = GetLastError();
286 if (memfile == NULL) {
287 zend_shared_alloc_unlock_win32();
288 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create file mapping", err);
289 *error_in = "CreateFileMapping";
290 return ALLOC_FAILURE;
291 }
292
293 /* Starting from windows Vista, heap randomization occurs which might cause our mapping base to
294 be taken (fail to map). So under Vista, we try to map into a hard coded predefined addresses
295 in high memory. */
296 if (!ZCG(accel_directives).mmap_base || !*ZCG(accel_directives).mmap_base) {
297 do {
298 OSVERSIONINFOEX osvi;
299 SYSTEM_INFO si;
300
301 ZeroMemory(&si, sizeof(SYSTEM_INFO));
302 ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
303
304 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
305
306 if (! GetVersionEx ((OSVERSIONINFO *) &osvi)) {
307 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
308 if (!GetVersionEx((OSVERSIONINFO *)&osvi)) {
309 break;
310 }
311 }
312
313 GetSystemInfo(&si);
314
315 /* Are we running Vista ? */
316 if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT && osvi.dwMajorVersion >= 6) {
317 wanted_mapping_base = vista_mapping_base_set;
318 }
319 } while (0);
320 } else {
321 char *s = ZCG(accel_directives).mmap_base;
322
323 /* skip leading 0x, %p assumes hexdeciaml format anyway */
324 if (*s == '0' && *(s + 1) == 'x') {
325 s += 2;
326 }
327 if (sscanf(s, "%p", &default_mapping_base_set[0]) != 1) {
328 zend_shared_alloc_unlock_win32();
329 zend_win_error_message(ACCEL_LOG_FATAL, "Bad mapping address specified in opcache.mmap_base", err);
330 return ALLOC_FAILURE;
331 }
332 }
333
334 do {
335 shared_segment->p = mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, *wanted_mapping_base);
336 if (*wanted_mapping_base == NULL) { /* Auto address (NULL) is the last option on the array */
337 break;
338 }
339 wanted_mapping_base++;
340 } while (!mapping_base);
341
342 err = GetLastError();
343 if (mapping_base == NULL) {
344 zend_shared_alloc_unlock_win32();
345 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create view for file mapping", err);
346 *error_in = "MapViewOfFile";
347 return ALLOC_FAILURE;
348 } else {
349 char *mmap_base_file = get_mmap_base_file();
350 FILE *fp = fopen(mmap_base_file, "w");
351 err = GetLastError();
352 if (!fp) {
353 zend_shared_alloc_unlock_win32();
354 zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
355 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to write base address", err);
356 return ALLOC_FAILURE;
357 }
358 fprintf(fp, "%p\n", mapping_base);
359 fclose(fp);
360 }
361
362 shared_segment->pos = 0;
363 shared_segment->size = requested_size;
364
365 zend_shared_alloc_unlock_win32();
366
367 return ALLOC_SUCCESS;
368 }
369
detach_segment(zend_shared_segment * shared_segment)370 static int detach_segment(zend_shared_segment *shared_segment)
371 {
372 zend_shared_alloc_lock_win32();
373 if (mapping_base) {
374 UnmapViewOfFile(mapping_base);
375 }
376 CloseHandle(memfile);
377 zend_shared_alloc_unlock_win32();
378 CloseHandle(memory_mutex);
379 return 0;
380 }
381
segment_type_size(void)382 static size_t segment_type_size(void)
383 {
384 return sizeof(zend_shared_segment);
385 }
386
387 zend_shared_memory_handlers zend_alloc_win32_handlers = {
388 create_segments,
389 detach_segment,
390 segment_type_size
391 };
392