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