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