xref: /PHP-7.3/ext/opcache/shared_alloc_win32.c (revision d95c5383)
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