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