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