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