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