1 /*
2 +----------------------------------------------------------------------+
3 | Zend OPcache |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 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@php.net> |
16 | Zeev Suraski <zeev@php.net> |
17 | Stanislav Malyshev <stas@zend.com> |
18 | Dmitry Stogov <dmitry@php.net> |
19 +----------------------------------------------------------------------+
20 */
21
22 #include "php.h"
23 #include "ZendAccelerator.h"
24 #include "zend_shared_alloc.h"
25 #include "zend_accelerator_util_funcs.h"
26 #include "zend_execute.h"
27 #include "SAPI.h"
28 #include "tsrm_win32.h"
29 #include "win32/winutil.h"
30 #include <winbase.h>
31 #include <process.h>
32 #include <LMCONS.H>
33
34 #define ACCEL_FILEMAP_NAME "ZendOPcache.SharedMemoryArea"
35 #define ACCEL_MUTEX_NAME "ZendOPcache.SharedMemoryMutex"
36 #define ACCEL_EVENT_SOURCE "Zend OPcache"
37
38 /* address of mapping base and address of execute_ex */
39 #define ACCEL_BASE_POINTER_SIZE (2 * sizeof(void*))
40
41 static HANDLE memfile = NULL, memory_mutex = NULL;
42 static void *mapping_base;
43
44 #define MAX_MAP_RETRIES 25
45
zend_win_error_message(int type,char * msg,int err)46 static void zend_win_error_message(int type, char *msg, int err)
47 {
48 HANDLE h;
49 char *ev_msgs[2];
50 char *buf = php_win32_error_to_msg(err);
51
52 h = RegisterEventSource(NULL, TEXT(ACCEL_EVENT_SOURCE));
53 ev_msgs[0] = msg;
54 ev_msgs[1] = buf;
55 ReportEvent(h, // event log handle
56 EVENTLOG_ERROR_TYPE, // event type
57 0, // category zero
58 err, // event identifier
59 NULL, // no user security identifier
60 2, // one substitution string
61 0, // no data
62 ev_msgs, // pointer to string array
63 NULL); // pointer to data
64 DeregisterEventSource(h);
65
66 zend_accel_error(type, "%s", msg);
67
68 php_win32_error_msg_free(buf);
69 }
70
create_name_with_username(char * name)71 static char *create_name_with_username(char *name)
72 {
73 static char newname[MAXPATHLEN + 1 + 32 + 1 + 20 + 1 + 32 + 1];
74 char *p = newname;
75 p += strlcpy(newname, name, MAXPATHLEN + 1);
76 *(p++) = '@';
77 memcpy(p, accel_uname_id, 32);
78 p += 32;
79 *(p++) = '@';
80 p += strlcpy(p, sapi_module.name, 21);
81 *(p++) = '@';
82 memcpy(p, accel_system_id, 32);
83 p += 32;
84 *(p++) = '\0';
85 ZEND_ASSERT(p - newname <= sizeof(newname));
86
87 return newname;
88 }
89
zend_shared_alloc_create_lock(void)90 void zend_shared_alloc_create_lock(void)
91 {
92 memory_mutex = CreateMutex(NULL, FALSE, create_name_with_username(ACCEL_MUTEX_NAME));
93 if (!memory_mutex) {
94 zend_accel_error(ACCEL_LOG_FATAL, "Cannot create mutex (error %u)", GetLastError());
95 return;
96 }
97 ReleaseMutex(memory_mutex);
98 }
99
zend_shared_alloc_lock_win32(void)100 void zend_shared_alloc_lock_win32(void)
101 {
102 DWORD waitRes = WaitForSingleObject(memory_mutex, INFINITE);
103
104 if (waitRes == WAIT_FAILED) {
105 zend_accel_error(ACCEL_LOG_ERROR, "Cannot lock mutex");
106 }
107 }
108
zend_shared_alloc_unlock_win32(void)109 void zend_shared_alloc_unlock_win32(void)
110 {
111 ReleaseMutex(memory_mutex);
112 }
113
zend_shared_alloc_reattach(size_t requested_size,char ** error_in)114 static int zend_shared_alloc_reattach(size_t requested_size, char **error_in)
115 {
116 int err;
117 void *wanted_mapping_base;
118 MEMORY_BASIC_INFORMATION info;
119 void *execute_ex_base;
120 int execute_ex_moved;
121
122 mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, ACCEL_BASE_POINTER_SIZE, NULL);
123 if (mapping_base == NULL) {
124 err = GetLastError();
125 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to read base address", err);
126 *error_in="read mapping base";
127 return ALLOC_FAILURE;
128 }
129 wanted_mapping_base = ((void**)mapping_base)[0];
130 execute_ex_base = ((void**)mapping_base)[1];
131 UnmapViewOfFile(mapping_base);
132
133 execute_ex_moved = (void *)execute_ex != execute_ex_base;
134
135 /* Check if execute_ex is at the same address and if the requested address space is free */
136 if (execute_ex_moved ||
137 VirtualQuery(wanted_mapping_base, &info, sizeof(info)) == 0 ||
138 info.State != MEM_FREE ||
139 info.RegionSize < requested_size) {
140 #if ENABLE_FILE_CACHE_FALLBACK
141 if (ZCG(accel_directives).file_cache && ZCG(accel_directives).file_cache_fallback) {
142 size_t pre_size, wanted_mb_save;
143
144 wanted_mb_save = (size_t)wanted_mapping_base;
145
146 if (execute_ex_moved) {
147 err = ERROR_INVALID_ADDRESS;
148 zend_win_error_message(ACCEL_LOG_WARNING, "Opcode handlers are unusable due to ASLR (fall-back to file cache)", err);
149 } else {
150 err = ERROR_INVALID_ADDRESS;
151 zend_win_error_message(ACCEL_LOG_WARNING, "Base address marks unusable memory region (fall-back to file cache)", err);
152 }
153
154 pre_size = ZEND_ALIGNED_SIZE(sizeof(zend_smm_shared_globals)) + ZEND_ALIGNED_SIZE(sizeof(zend_shared_segment)) + ZEND_ALIGNED_SIZE(sizeof(void *)) + ZEND_ALIGNED_SIZE(sizeof(int));
155 /* Map only part of SHM to have access opcache shared globals */
156 mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, pre_size + ZEND_ALIGNED_SIZE(sizeof(zend_accel_shared_globals)), NULL);
157 if (mapping_base == NULL) {
158 err = GetLastError();
159 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to opcache shared globals", err);
160 return ALLOC_FAILURE;
161 }
162 accel_shared_globals = (zend_accel_shared_globals *)((char *)((zend_smm_shared_globals *)mapping_base)->app_shared_globals + ((char *)mapping_base - (char *)wanted_mb_save));
163
164 return ALLOC_FALLBACK;
165 }
166 #endif
167 if (execute_ex_moved) {
168 err = ERROR_INVALID_ADDRESS;
169 zend_win_error_message(ACCEL_LOG_FATAL, "Opcode handlers are unusable due to ASLR. Please setup opcache.file_cache and opcache.file_cache_fallback directives for more convenient Opcache usage", err);
170 } else {
171 err = ERROR_INVALID_ADDRESS;
172 zend_win_error_message(ACCEL_LOG_FATAL, "Base address marks unusable memory region. Please setup opcache.file_cache and opcache.file_cache_fallback directives for more convenient Opcache usage", err);
173 }
174 return ALLOC_FAILURE;
175 }
176
177 mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, wanted_mapping_base);
178
179 if (mapping_base == NULL) {
180 err = GetLastError();
181 if (err == ERROR_INVALID_ADDRESS) {
182 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
183 return ALLOC_FAILURE;
184 }
185 return ALLOC_FAIL_MAPPING;
186 }
187 smm_shared_globals = (zend_smm_shared_globals *) ((char*)mapping_base + ACCEL_BASE_POINTER_SIZE);
188
189 return SUCCESSFULLY_REATTACHED;
190 }
191
create_segments(size_t requested_size,zend_shared_segment *** shared_segments_p,int * shared_segments_count,char ** error_in)192 static int create_segments(size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, char **error_in)
193 {
194 int err = 0, ret;
195 zend_shared_segment *shared_segment;
196 int map_retries = 0;
197 void *default_mapping_base_set[] = { 0, 0 };
198 /* TODO:
199 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
200 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
201 desired. Not done yet, @zend refused but did not remember the exact reason, pls add info here if one of you know why :)
202 */
203 #if defined(_WIN64)
204 void *vista_mapping_base_set[] = { (void *) 0x0000100000000000, (void *) 0x0000200000000000, (void *) 0x0000300000000000, (void *) 0x0000700000000000, 0 };
205 DWORD size_high = (requested_size >> 32), size_low = (requested_size & 0xffffffff);
206 #else
207 void *vista_mapping_base_set[] = { (void *) 0x20000000, (void *) 0x21000000, (void *) 0x30000000, (void *) 0x31000000, (void *) 0x50000000, 0 };
208 DWORD size_high = 0, size_low = requested_size;
209 #endif
210 void **wanted_mapping_base = default_mapping_base_set;
211
212 zend_shared_alloc_lock_win32();
213 /* Mapping retries: When Apache2 restarts, the parent process startup routine
214 can be called before the child process is killed. In this case, the map will fail
215 and we have to sleep some time (until the child releases the mapping object) and retry.*/
216 do {
217 memfile = OpenFileMapping(FILE_MAP_WRITE, 0, create_name_with_username(ACCEL_FILEMAP_NAME));
218 if (memfile == NULL) {
219 err = GetLastError();
220 break;
221 }
222
223 ret = zend_shared_alloc_reattach(requested_size, error_in);
224 if (ret == ALLOC_FAIL_MAPPING) {
225 err = GetLastError();
226 /* Mapping failed, wait for mapping object to get freed and retry */
227 CloseHandle(memfile);
228 memfile = NULL;
229 if (++map_retries >= MAX_MAP_RETRIES) {
230 break;
231 }
232 zend_shared_alloc_unlock_win32();
233 Sleep(1000 * (map_retries + 1));
234 zend_shared_alloc_lock_win32();
235 } else {
236 zend_shared_alloc_unlock_win32();
237 return ret;
238 }
239 } while (1);
240
241 if (map_retries == MAX_MAP_RETRIES) {
242 zend_shared_alloc_unlock_win32();
243 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open file mapping", err);
244 *error_in = "OpenFileMapping";
245 return ALLOC_FAILURE;
246 }
247
248 /* creating segment here */
249 *shared_segments_count = 1;
250 *shared_segments_p = (zend_shared_segment **) calloc(1, sizeof(zend_shared_segment)+sizeof(void *));
251 if (!*shared_segments_p) {
252 err = GetLastError();
253 zend_shared_alloc_unlock_win32();
254 zend_win_error_message(ACCEL_LOG_FATAL, "calloc() failed", err);
255 *error_in = "calloc";
256 return ALLOC_FAILURE;
257 }
258 shared_segment = (zend_shared_segment *)((char *)(*shared_segments_p) + sizeof(void *));
259 (*shared_segments_p)[0] = shared_segment;
260
261 memfile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, size_high, size_low,
262 create_name_with_username(ACCEL_FILEMAP_NAME));
263 if (memfile == NULL) {
264 err = GetLastError();
265 zend_shared_alloc_unlock_win32();
266 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create file mapping", err);
267 *error_in = "CreateFileMapping";
268 return ALLOC_FAILURE;
269 }
270
271 /* Starting from windows Vista, heap randomization occurs which might cause our mapping base to
272 be taken (fail to map). So under Vista, we try to map into a hard coded predefined addresses
273 in high memory. */
274 if (!ZCG(accel_directives).mmap_base || !*ZCG(accel_directives).mmap_base) {
275 wanted_mapping_base = vista_mapping_base_set;
276 } else {
277 char *s = ZCG(accel_directives).mmap_base;
278
279 /* skip leading 0x, %p assumes hexdeciaml format anyway */
280 if (*s == '0' && *(s + 1) == 'x') {
281 s += 2;
282 }
283 if (sscanf(s, "%p", &default_mapping_base_set[0]) != 1) {
284 zend_shared_alloc_unlock_win32();
285 zend_win_error_message(ACCEL_LOG_FATAL, "Bad mapping address specified in opcache.mmap_base", err);
286 return ALLOC_FAILURE;
287 }
288 }
289
290 do {
291 shared_segment->p = mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, *wanted_mapping_base);
292 if (*wanted_mapping_base == NULL) { /* Auto address (NULL) is the last option on the array */
293 break;
294 }
295 wanted_mapping_base++;
296 } while (!mapping_base);
297
298 if (mapping_base == NULL) {
299 err = GetLastError();
300 zend_shared_alloc_unlock_win32();
301 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create view for file mapping", err);
302 *error_in = "MapViewOfFile";
303 return ALLOC_FAILURE;
304 } else {
305 ((void**)mapping_base)[0] = mapping_base;
306 ((void**)mapping_base)[1] = (void*)execute_ex;
307 }
308
309 shared_segment->pos = ACCEL_BASE_POINTER_SIZE;
310 shared_segment->size = requested_size - ACCEL_BASE_POINTER_SIZE;
311
312 zend_shared_alloc_unlock_win32();
313
314 return ALLOC_SUCCESS;
315 }
316
detach_segment(zend_shared_segment * shared_segment)317 static int detach_segment(zend_shared_segment *shared_segment)
318 {
319 zend_shared_alloc_lock_win32();
320 if (mapping_base) {
321 UnmapViewOfFile(mapping_base);
322 mapping_base = NULL;
323 }
324 CloseHandle(memfile);
325 memfile = NULL;
326 zend_shared_alloc_unlock_win32();
327 CloseHandle(memory_mutex);
328 memory_mutex = NULL;
329 return 0;
330 }
331
segment_type_size(void)332 static size_t segment_type_size(void)
333 {
334 return sizeof(zend_shared_segment);
335 }
336
337 zend_shared_memory_handlers zend_alloc_win32_handlers = {
338 create_segments,
339 detach_segment,
340 segment_type_size
341 };
342