xref: /php-src/TSRM/tsrm_win32.c (revision afbb28df)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Daniel Beulshausen <daniel@php4win.de>                      |
14    +----------------------------------------------------------------------+
15 */
16 
17 #include <stdio.h>
18 #include <fcntl.h>
19 #include <io.h>
20 #include <process.h>
21 #include <time.h>
22 #include <errno.h>
23 
24 #define TSRM_INCLUDE_FULL_WINDOWS_HEADERS
25 #include "SAPI.h"
26 #include "TSRM.h"
27 
28 #ifdef TSRM_WIN32
29 #include <Sddl.h>
30 #include "tsrm_win32.h"
31 #include "zend_virtual_cwd.h"
32 #include "win32/ioutil.h"
33 #include "win32/winutil.h"
34 
35 #ifdef ZTS
36 static ts_rsrc_id win32_globals_id;
37 #else
38 static tsrm_win32_globals win32_globals;
39 #endif
40 
tsrm_win32_ctor(tsrm_win32_globals * globals)41 static void tsrm_win32_ctor(tsrm_win32_globals *globals)
42 {/*{{{*/
43 #ifdef ZTS
44 TSRMLS_CACHE_UPDATE();
45 #endif
46 	globals->process = NULL;
47 	globals->shm	 = NULL;
48 	globals->process_size = 0;
49 	globals->shm_size	  = 0;
50 	globals->comspec = _strdup("cmd.exe");
51 
52 	/* Set it to INVALID_HANDLE_VALUE
53 	 * It will be initialized correctly in tsrm_win32_access or set to
54 	 * NULL if no impersonation has been done.
55 	 * the impersonated token can't be set here as the impersonation
56 	 * will happen later, in fcgi_accept_request (or whatever is the
57 	 * SAPI being used).
58 	 */
59 	globals->impersonation_token = INVALID_HANDLE_VALUE;
60 	globals->impersonation_token_sid = NULL;
61 }/*}}}*/
62 
tsrm_win32_dtor(tsrm_win32_globals * globals)63 static void tsrm_win32_dtor(tsrm_win32_globals *globals)
64 {/*{{{*/
65 	shm_pair *ptr;
66 
67 	if (globals->process) {
68 		free(globals->process);
69 	}
70 
71 	if (globals->shm) {
72 		for (ptr = globals->shm; ptr < (globals->shm + globals->shm_size); ptr++) {
73 			UnmapViewOfFile(ptr->descriptor);
74 			CloseHandle(ptr->segment);
75 		}
76 		free(globals->shm);
77 	}
78 
79 	free(globals->comspec);
80 
81 	if (globals->impersonation_token && globals->impersonation_token != INVALID_HANDLE_VALUE	) {
82 		CloseHandle(globals->impersonation_token);
83 	}
84 	if (globals->impersonation_token_sid) {
85 		free(globals->impersonation_token_sid);
86 	}
87 }/*}}}*/
88 
tsrm_win32_startup(void)89 TSRM_API void tsrm_win32_startup(void)
90 {/*{{{*/
91 #ifdef ZTS
92 	ts_allocate_id(&win32_globals_id, sizeof(tsrm_win32_globals), (ts_allocate_ctor)tsrm_win32_ctor, (ts_allocate_dtor)tsrm_win32_dtor);
93 #else
94 	tsrm_win32_ctor(&win32_globals);
95 #endif
96 }/*}}}*/
97 
tsrm_win32_shutdown(void)98 TSRM_API void tsrm_win32_shutdown(void)
99 {/*{{{*/
100 #ifndef ZTS
101 	tsrm_win32_dtor(&win32_globals);
102 #endif
103 }/*}}}*/
104 
tsrm_win32_get_path_sid_key(const char * pathname,size_t pathname_len,size_t * key_len)105 const char * tsrm_win32_get_path_sid_key(const char *pathname, size_t pathname_len, size_t *key_len)
106 {/*{{{*/
107 	PSID pSid = TWG(impersonation_token_sid);
108 	char *ptcSid = NULL;
109 	char *bucket_key = NULL;
110 	size_t ptc_sid_len;
111 
112 	if (!pSid) {
113 		*key_len = pathname_len;
114 		return pathname;
115 	}
116 
117 	if (!ConvertSidToStringSid(pSid, &ptcSid)) {
118 		*key_len = 0;
119 		return NULL;
120 	}
121 
122 
123 	ptc_sid_len = strlen(ptcSid);
124 	*key_len = pathname_len + ptc_sid_len;
125 	bucket_key = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *key_len + 1);
126 	if (!bucket_key) {
127 		LocalFree(ptcSid);
128 		*key_len = 0;
129 		return NULL;
130 	}
131 
132 	memcpy(bucket_key, ptcSid, ptc_sid_len);
133 	memcpy(bucket_key + ptc_sid_len, pathname, pathname_len + 1);
134 
135 	LocalFree(ptcSid);
136 	return bucket_key;
137 }/*}}}*/
138 
139 
tsrm_win32_get_token_sid(HANDLE hToken)140 PSID tsrm_win32_get_token_sid(HANDLE hToken)
141 {/*{{{*/
142 	DWORD dwLength = 0;
143 	PTOKEN_USER pTokenUser = NULL;
144 	DWORD sid_len;
145 	PSID pResultSid = NULL;
146 
147 	/* Get the actual size of the TokenUser structure */
148 	if (!GetTokenInformation(
149 			hToken, TokenUser, (LPVOID) pTokenUser, 0, &dwLength))  {
150 		if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
151 			goto Finished;
152 		}
153 
154 		pTokenUser = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength);
155 		if (pTokenUser == NULL) {
156 			goto Finished;
157 		}
158 	}
159 
160 	/* and fetch it now */
161 	if (!GetTokenInformation(
162 		hToken, TokenUser, (LPVOID) pTokenUser, dwLength, &dwLength)) {
163 		goto Finished;
164 	}
165 
166 	sid_len = GetLengthSid(pTokenUser->User.Sid);
167 
168 	/* ConvertSidToStringSid(pTokenUser->User.Sid, &ptcSidOwner); */
169 	pResultSid = malloc(sid_len);
170 	if (!pResultSid) {
171 		goto Finished;
172 	}
173 	if (!CopySid(sid_len, pResultSid, pTokenUser->User.Sid)) {
174 		goto Finished;
175 	}
176 	HeapFree(GetProcessHeap(), 0, (LPVOID)pTokenUser);
177 	return pResultSid;
178 
179 Finished:
180 	if (pResultSid) {
181 		free(pResultSid);
182 	}
183 	/* Free the buffer for the token groups. */
184 	if (pTokenUser != NULL) {
185 		HeapFree(GetProcessHeap(), 0, (LPVOID)pTokenUser);
186 	}
187 	return NULL;
188 }/*}}}*/
189 
tsrm_win32_access(const char * pathname,int mode)190 TSRM_API int tsrm_win32_access(const char *pathname, int mode)
191 {/*{{{*/
192 	time_t t;
193 	HANDLE thread_token = NULL;
194 	PSID token_sid;
195 	SECURITY_INFORMATION sec_info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
196 	GENERIC_MAPPING gen_map = { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS };
197 	DWORD priv_set_length = sizeof(PRIVILEGE_SET);
198 
199 	PRIVILEGE_SET privilege_set = {0};
200 	DWORD sec_desc_length = 0, desired_access = 0, granted_access = 0;
201 	BYTE * psec_desc = NULL;
202 	BOOL fAccess = FALSE;
203 
204 	realpath_cache_bucket * bucket = NULL;
205 	char real_path[MAXPATHLEN] = {0};
206 
207 	if(!IS_ABSOLUTE_PATH(pathname, strlen(pathname)+1)) {
208 		if(tsrm_realpath(pathname, real_path) == NULL) {
209 			SET_ERRNO_FROM_WIN32_CODE(ERROR_FILE_NOT_FOUND);
210 			return -1;
211 		}
212 		pathname = real_path;
213 	}
214 
215 	PHP_WIN32_IOUTIL_INIT_W(pathname)
216 	if (!pathw) {
217 		return -1;
218 	}
219 
220 	/* Either access call failed, or the mode was asking for a specific check.*/
221 	int ret = php_win32_ioutil_access_w(pathw, mode);
222 	if (0 > ret || X_OK == mode || F_OK == mode) {
223 		PHP_WIN32_IOUTIL_CLEANUP_W()
224 		return ret;
225 	}
226 
227 /* Only in NTS when impersonate==1 (aka FastCGI) */
228 
229 	/*
230 	 AccessCheck() requires an impersonation token.  We first get a primary
231 	 token and then create a duplicate impersonation token.  The
232 	 impersonation token is not actually assigned to the thread, but is
233 	 used in the call to AccessCheck.  Thus, this function itself never
234 	 impersonates, but does use the identity of the thread.  If the thread
235 	 was impersonating already, this function uses that impersonation context.
236 	*/
237 	if(!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &thread_token)) {
238 		if (GetLastError() == ERROR_NO_TOKEN) {
239 			if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &thread_token)) {
240 				 TWG(impersonation_token) = NULL;
241 				 goto Finished;
242 			 }
243 		}
244 	}
245 
246 	/* token_sid will be freed in tsrmwin32_dtor */
247 	token_sid = tsrm_win32_get_token_sid(thread_token);
248 	if (!token_sid) {
249 		if (TWG(impersonation_token_sid)) {
250 			free(TWG(impersonation_token_sid));
251 		}
252 		TWG(impersonation_token_sid) = NULL;
253 		goto Finished;
254 	}
255 
256 	/* Different identity, we need a new impersonated token as well */
257 	if (!TWG(impersonation_token_sid) || !EqualSid(token_sid, TWG(impersonation_token_sid))) {
258 		if (TWG(impersonation_token_sid)) {
259 			free(TWG(impersonation_token_sid));
260 		}
261 		TWG(impersonation_token_sid) = token_sid;
262 
263 		/* Duplicate the token as impersonated token */
264 		if (!DuplicateToken(thread_token, SecurityImpersonation, &TWG(impersonation_token))) {
265 			goto Finished;
266 		}
267 	} else {
268 		/* we already have it, free it then */
269 		free(token_sid);
270 	}
271 
272 	if (CWDG(realpath_cache_size_limit)) {
273 		t = time(0);
274 		bucket = realpath_cache_lookup(pathname, strlen(pathname), t);
275 		if(bucket == NULL && !real_path[0]) {
276 			/* We used the pathname directly. Call tsrm_realpath */
277 			/* so that entry is created in realpath cache */
278 			if(tsrm_realpath(pathname, real_path) != NULL) {
279 				pathname = real_path;
280 				bucket = realpath_cache_lookup(pathname, strlen(pathname), t);
281 				PHP_WIN32_IOUTIL_REINIT_W(pathname);
282 			}
283 		}
284 	}
285 
286 	/* Do a full access check because access() will only check read-only attribute */
287 	if(mode == 0 || mode > 6) {
288 		if(bucket != NULL && bucket->is_rvalid) {
289 			fAccess = bucket->is_readable;
290 			goto Finished;
291 		}
292 		desired_access = FILE_GENERIC_READ;
293 	} else if(mode <= 2) {
294 		if(bucket != NULL && bucket->is_wvalid) {
295 			fAccess = bucket->is_writable;
296 			goto Finished;
297 		}
298 		desired_access = FILE_GENERIC_WRITE;
299 	} else if(mode <= 4) {
300 		if(bucket != NULL && bucket->is_rvalid) {
301 			fAccess = bucket->is_readable;
302 			goto Finished;
303 		}
304 		desired_access = FILE_GENERIC_READ|FILE_FLAG_BACKUP_SEMANTICS;
305 	} else { // if(mode <= 6)
306 		if(bucket != NULL && bucket->is_rvalid && bucket->is_wvalid) {
307 			fAccess = bucket->is_readable & bucket->is_writable;
308 			goto Finished;
309 		}
310 		desired_access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
311 	}
312 
313 	if(TWG(impersonation_token) == NULL) {
314 		goto Finished;
315 	}
316 
317 	/* Get size of security buffer. Call is expected to fail */
318 	if(GetFileSecurityW(pathw, sec_info, NULL, 0, &sec_desc_length)) {
319 		goto Finished;
320 	}
321 
322 	psec_desc = (BYTE *)malloc(sec_desc_length);
323 	if(psec_desc == NULL ||
324 		 !GetFileSecurityW(pathw, sec_info, (PSECURITY_DESCRIPTOR)psec_desc, sec_desc_length, &sec_desc_length)) {
325 		goto Finished;
326 	}
327 
328 	MapGenericMask(&desired_access, &gen_map);
329 
330 	if(!AccessCheck((PSECURITY_DESCRIPTOR)psec_desc, TWG(impersonation_token), desired_access, &gen_map, &privilege_set, &priv_set_length, &granted_access, &fAccess)) {
331 		goto Finished_Impersonate;
332 	}
333 
334 	/* Keep the result in realpath_cache */
335 	if(bucket != NULL) {
336 		if(desired_access == (FILE_GENERIC_READ|FILE_FLAG_BACKUP_SEMANTICS)) {
337 			bucket->is_rvalid = 1;
338 			bucket->is_readable = fAccess;
339 		}
340 		else if(desired_access == FILE_GENERIC_WRITE) {
341 			bucket->is_wvalid = 1;
342 			bucket->is_writable = fAccess;
343 		} else if (desired_access == (FILE_GENERIC_READ | FILE_GENERIC_WRITE)) {
344 			bucket->is_rvalid = 1;
345 			bucket->is_readable = fAccess;
346 			bucket->is_wvalid = 1;
347 			bucket->is_writable = fAccess;
348 		}
349 	}
350 
351 Finished_Impersonate:
352 	if(psec_desc != NULL) {
353 		free(psec_desc);
354 		psec_desc = NULL;
355 	}
356 
357 Finished:
358 	if(thread_token != NULL) {
359 		CloseHandle(thread_token);
360 	}
361 
362 	PHP_WIN32_IOUTIL_CLEANUP_W()
363 	if(fAccess == FALSE) {
364 		errno = EACCES;
365 		return errno;
366 	} else {
367 		return 0;
368 	}
369 }/*}}}*/
370 
371 
process_get(FILE * stream)372 static process_pair *process_get(FILE *stream)
373 {/*{{{*/
374 	process_pair *ptr;
375 	process_pair *newptr;
376 
377 	for (ptr = TWG(process); ptr < (TWG(process) + TWG(process_size)); ptr++) {
378 		if (ptr->stream == stream) {
379 			break;
380 		}
381 	}
382 
383 	if (ptr < (TWG(process) + TWG(process_size))) {
384 		return ptr;
385 	}
386 
387 	newptr = (process_pair*)realloc((void*)TWG(process), (TWG(process_size)+1)*sizeof(process_pair));
388 	if (newptr == NULL) {
389 		return NULL;
390 	}
391 
392 	TWG(process) = newptr;
393 	ptr = newptr + TWG(process_size);
394 	TWG(process_size)++;
395 	return ptr;
396 }/*}}}*/
397 
shm_get(key_t key,void * addr)398 static shm_pair *shm_get(key_t key, void *addr)
399 {/*{{{*/
400 	shm_pair *ptr;
401 	shm_pair *newptr;
402 
403 	for (ptr = TWG(shm); ptr < (TWG(shm) + TWG(shm_size)); ptr++) {
404 		if (!ptr->descriptor) {
405 			continue;
406 		}
407 		if (!addr && ptr->descriptor->shm_perm.key == key) {
408 			break;
409 		} else if (ptr->addr == addr) {
410 			break;
411 		}
412 	}
413 
414 	if (ptr < (TWG(shm) + TWG(shm_size))) {
415 		return ptr;
416 	}
417 
418 	newptr = (shm_pair*)realloc((void*)TWG(shm), (TWG(shm_size)+1)*sizeof(shm_pair));
419 	if (newptr == NULL) {
420 		return NULL;
421 	}
422 
423 	TWG(shm) = newptr;
424 	ptr = newptr + TWG(shm_size);
425 	TWG(shm_size)++;
426 	memset(ptr, 0, sizeof(*ptr));
427 	return ptr;
428 }/*}}}*/
429 
dupHandle(HANDLE fh,BOOL inherit)430 static HANDLE dupHandle(HANDLE fh, BOOL inherit)
431 {/*{{{*/
432 	HANDLE copy, self = GetCurrentProcess();
433 	if (!DuplicateHandle(self, fh, self, &copy, 0, inherit, DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE)) {
434 		return NULL;
435 	}
436 	return copy;
437 }/*}}}*/
438 
popen(const char * command,const char * type)439 TSRM_API FILE *popen(const char *command, const char *type)
440 {/*{{{*/
441 
442 	return popen_ex(command, type, NULL, NULL);
443 }/*}}}*/
444 
popen_ex(const char * command,const char * type,const char * cwd,const char * env)445 TSRM_API FILE *popen_ex(const char *command, const char *type, const char *cwd, const char *env)
446 {/*{{{*/
447 	FILE *stream = NULL;
448 	int fno, type_len, read, mode;
449 	STARTUPINFOW startup;
450 	PROCESS_INFORMATION process;
451 	SECURITY_ATTRIBUTES security;
452 	HANDLE in, out;
453 	DWORD dwCreateFlags = 0;
454 	BOOL res;
455 	process_pair *proc;
456 	char *cmd = NULL;
457 	wchar_t *cmdw = NULL, *cwdw = NULL, *envw = NULL;
458 	char *ptype = (char *)type;
459 	HANDLE thread_token = NULL;
460 	HANDLE token_user = NULL;
461 	BOOL asuser = TRUE;
462 
463 	if (!type) {
464 		return NULL;
465 	}
466 
467 	type_len = (int)strlen(type);
468 	if (type_len < 1 || type_len > 2) {
469 		return NULL;
470 	}
471 
472 	if (ptype[0] != 'r' && ptype[0] != 'w') {
473 		return NULL;
474 	}
475 
476 	if (type_len > 1 && (ptype[1] != 'b' && ptype[1] != 't')) {
477 		return NULL;
478 	}
479 
480 	cmd = (char*)malloc(strlen(command)+strlen(TWG(comspec))+sizeof(" /s /c ")+2);
481 	if (!cmd) {
482 		return NULL;
483 	}
484 
485 	sprintf(cmd, "%s /s /c \"%s\"", TWG(comspec), command);
486 	cmdw = php_win32_cp_any_to_w(cmd);
487 	if (!cmdw) {
488 		free(cmd);
489 		return NULL;
490 	}
491 
492 	if (cwd) {
493 		cwdw = php_win32_ioutil_any_to_w(cwd);
494 		if (!cwdw) {
495 			free(cmd);
496 			free(cmdw);
497 			return NULL;
498 		}
499 	}
500 
501 	security.nLength				= sizeof(SECURITY_ATTRIBUTES);
502 	security.bInheritHandle			= TRUE;
503 	security.lpSecurityDescriptor	= NULL;
504 
505 	if (!type_len || !CreatePipe(&in, &out, &security, 2048L)) {
506 		free(cmdw);
507 		free(cwdw);
508 		free(cmd);
509 		return NULL;
510 	}
511 
512 	memset(&startup, 0, sizeof(STARTUPINFOW));
513 	memset(&process, 0, sizeof(PROCESS_INFORMATION));
514 
515 	startup.cb			= sizeof(STARTUPINFOW);
516 	startup.dwFlags		= STARTF_USESTDHANDLES;
517 	startup.hStdError	= GetStdHandle(STD_ERROR_HANDLE);
518 
519 	read = (type[0] == 'r') ? TRUE : FALSE;
520 	mode = ((type_len == 2) && (type[1] == 'b')) ? O_BINARY : O_TEXT;
521 
522 	if (read) {
523 		in = dupHandle(in, FALSE);
524 		startup.hStdInput  = GetStdHandle(STD_INPUT_HANDLE);
525 		startup.hStdOutput = out;
526 	} else {
527 		out = dupHandle(out, FALSE);
528 		startup.hStdInput  = in;
529 		startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
530 	}
531 
532 	dwCreateFlags = NORMAL_PRIORITY_CLASS;
533 	if (strcmp(sapi_module.name, "cli") != 0) {
534 		dwCreateFlags |= CREATE_NO_WINDOW;
535 	}
536 
537 	/* Get a token with the impersonated user. */
538 	if(OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &thread_token)) {
539 		DuplicateTokenEx(thread_token, MAXIMUM_ALLOWED, &security, SecurityImpersonation, TokenPrimary, &token_user);
540 	} else {
541 		DWORD err = GetLastError();
542 		if (err == ERROR_NO_TOKEN) {
543 			asuser = FALSE;
544 		}
545 	}
546 
547 	envw = php_win32_cp_env_any_to_w(env);
548 	if (envw) {
549 		dwCreateFlags |= CREATE_UNICODE_ENVIRONMENT;
550 	} else {
551 		if (env) {
552 			free(cmd);
553 			free(cmdw);
554 			free(cwdw);
555 			return NULL;
556 		}
557 	}
558 
559 	if (asuser) {
560 		res = CreateProcessAsUserW(token_user, NULL, cmdw, &security, &security, security.bInheritHandle, dwCreateFlags, envw, cwdw, &startup, &process);
561 		CloseHandle(token_user);
562 	} else {
563 		res = CreateProcessW(NULL, cmdw, &security, &security, security.bInheritHandle, dwCreateFlags, envw, cwdw, &startup, &process);
564 	}
565 	free(cmd);
566 	free(cmdw);
567 	free(cwdw);
568 	free(envw);
569 
570 	if (!res) {
571 		return NULL;
572 	}
573 
574 	CloseHandle(process.hThread);
575 	proc = process_get(NULL);
576 
577 	if (read) {
578 		fno = _open_osfhandle((tsrm_intptr_t)in, _O_RDONLY | mode);
579 		CloseHandle(out);
580 	} else {
581 		fno = _open_osfhandle((tsrm_intptr_t)out, _O_WRONLY | mode);
582 		CloseHandle(in);
583 	}
584 
585 	stream = _fdopen(fno, type);
586 	proc->prochnd = process.hProcess;
587 	proc->stream = stream;
588 	return stream;
589 }/*}}}*/
590 
pclose(FILE * stream)591 TSRM_API int pclose(FILE *stream)
592 {/*{{{*/
593 	DWORD termstat = 0;
594 	process_pair *process;
595 
596 	if ((process = process_get(stream)) == NULL) {
597 		return 0;
598 	}
599 
600 	fflush(process->stream);
601 	fclose(process->stream);
602 
603 	WaitForSingleObject(process->prochnd, INFINITE);
604 	GetExitCodeProcess(process->prochnd, &termstat);
605 	process->stream = NULL;
606 	CloseHandle(process->prochnd);
607 
608 	return termstat;
609 }/*}}}*/
610 
611 #define SEGMENT_PREFIX "TSRM_SHM_SEGMENT:"
612 #define INT_MIN_AS_STRING "-2147483648"
613 
614 
615 #define TSRM_BASE_SHM_KEY_ADDRESS 0x20000000
616 /* Returns a number between 0x2000_0000 and 0x3fff_ffff. On Windows, key_t is int. */
tsrm_choose_random_shm_key(key_t prev_key)617 static key_t tsrm_choose_random_shm_key(key_t prev_key) {
618 	unsigned char buf[4];
619 	if (php_win32_get_random_bytes(buf, 4) != SUCCESS) {
620 		return prev_key + 2;
621 	}
622 	uint32_t n =
623 		((uint32_t)(buf[0]) << 24) |
624 	    (((uint32_t)buf[1]) << 16) |
625 	    (((uint32_t)buf[2]) << 8) |
626 	    (((uint32_t)buf[3]));
627 	return (n & 0x1fffffff) + TSRM_BASE_SHM_KEY_ADDRESS;
628 }
629 
shmget(key_t key,size_t size,int flags)630 TSRM_API int shmget(key_t key, size_t size, int flags)
631 {/*{{{*/
632 	shm_pair *shm;
633 	char shm_segment[sizeof(SEGMENT_PREFIX INT_MIN_AS_STRING)];
634 	HANDLE shm_handle = NULL, info_handle = NULL;
635 	BOOL created = FALSE;
636 
637 	if (key != IPC_PRIVATE) {
638 		snprintf(shm_segment, sizeof(shm_segment), SEGMENT_PREFIX "%d", key);
639 
640 		shm_handle  = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, shm_segment);
641 	} else {
642 		/* IPC_PRIVATE always creates a new segment even if IPC_CREAT flag isn't passed. */
643 		flags |= IPC_CREAT;
644 	}
645 
646 	if (!shm_handle) {
647 		if (flags & IPC_CREAT) {
648 			if (size == 0 || size > SIZE_MAX - sizeof(shm->descriptor)) {
649 				return -1;
650 			}
651 			size += sizeof(shm->descriptor);
652 #if SIZEOF_SIZE_T == 8
653 			DWORD high = size >> 32;
654 			DWORD low = (DWORD)size;
655 #else
656 			DWORD high = 0;
657 			DWORD low = size;
658 #endif
659 			shm_handle	= CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, high, low, key == IPC_PRIVATE ? NULL : shm_segment);
660 			created		= TRUE;
661 		}
662 		if (!shm_handle) {
663 			return -1;
664 		}
665 	} else {
666 		if (flags & IPC_EXCL) {
667 			CloseHandle(shm_handle);
668 			return -1;
669 		}
670 	}
671 
672 	if (key == IPC_PRIVATE) {
673 		/* This should call shm_get with a brand new key id that isn't used yet. See https://man7.org/linux/man-pages/man2/shmget.2.html
674 		 * Because extensions such as shmop/sysvshm can be used in userland to attach to shared memory segments, use unpredictable high positive numbers to avoid accidentally conflicting with userland. */
675 		key = tsrm_choose_random_shm_key(TSRM_BASE_SHM_KEY_ADDRESS);
676 		for (shm_pair *ptr = TWG(shm); ptr < (TWG(shm) + TWG(shm_size)); ptr++) {
677 			if (ptr->descriptor && ptr->descriptor->shm_perm.key == key) {
678 				key = tsrm_choose_random_shm_key(key);
679 				ptr = TWG(shm);
680 				continue;
681 			}
682 		}
683 	}
684 
685 	shm = shm_get(key, NULL);
686 	if (!shm) {
687 		CloseHandle(shm_handle);
688 		return -1;
689 	}
690 	shm->segment = shm_handle;
691 	shm->descriptor = MapViewOfFileEx(shm->segment, FILE_MAP_ALL_ACCESS, 0, 0, 0, NULL);
692 
693 	if (NULL != shm->descriptor && created) {
694 		shm->descriptor->shm_perm.key	= key;
695 		shm->descriptor->shm_segsz		= size;
696 		shm->descriptor->shm_ctime		= time(NULL);
697 		shm->descriptor->shm_cpid		= getpid();
698 		shm->descriptor->shm_perm.mode	= flags;
699 
700 		shm->descriptor->shm_perm.cuid	= shm->descriptor->shm_perm.cgid= 0;
701 		shm->descriptor->shm_perm.gid	= shm->descriptor->shm_perm.uid = 0;
702 		shm->descriptor->shm_atime		= shm->descriptor->shm_dtime	= 0;
703 		shm->descriptor->shm_lpid		= shm->descriptor->shm_nattch	= 0;
704 		shm->descriptor->shm_perm.mode	= shm->descriptor->shm_perm.seq	= 0;
705 	}
706 
707 	if (NULL != shm->descriptor && (shm->descriptor->shm_perm.key != key || size > shm->descriptor->shm_segsz)) {
708 		if (NULL != shm->segment) {
709 			CloseHandle(shm->segment);
710 		}
711 		UnmapViewOfFile(shm->descriptor);
712 		return -1;
713 	}
714 
715 	return key;
716 }/*}}}*/
717 
shmat(int key,const void * shmaddr,int flags)718 TSRM_API void *shmat(int key, const void *shmaddr, int flags)
719 {/*{{{*/
720 	shm_pair *shm = shm_get(key, NULL);
721 
722 	if (!shm || !shm->segment) {
723 		return (void*)-1;
724 	}
725 
726 	shm->addr = shm->descriptor + sizeof(shm->descriptor);
727 	shm->descriptor->shm_atime = time(NULL);
728 	shm->descriptor->shm_lpid  = getpid();
729 	shm->descriptor->shm_nattch++;
730 
731 	return shm->addr;
732 }/*}}}*/
733 
shmdt(const void * shmaddr)734 TSRM_API int shmdt(const void *shmaddr)
735 {/*{{{*/
736 	shm_pair *shm = shm_get(0, (void*)shmaddr);
737 	int ret;
738 
739 	if (!shm || !shm->segment) {
740 		return -1;
741 	}
742 
743 	shm->descriptor->shm_dtime = time(NULL);
744 	shm->descriptor->shm_lpid  = getpid();
745 	shm->descriptor->shm_nattch--;
746 
747 	ret = 1;
748 	if (!ret  && shm->descriptor->shm_nattch <= 0) {
749 		ret = UnmapViewOfFile(shm->descriptor) ? 0 : -1;
750 		shm->descriptor = NULL;
751 	}
752 	return ret;
753 }/*}}}*/
754 
shmctl(int key,int cmd,struct shmid_ds * buf)755 TSRM_API int shmctl(int key, int cmd, struct shmid_ds *buf)
756 {/*{{{*/
757 	shm_pair *shm = shm_get(key, NULL);
758 
759 	if (!shm || !shm->segment) {
760 		return -1;
761 	}
762 
763 	switch (cmd) {
764 		case IPC_STAT:
765 			memcpy(buf, shm->descriptor, sizeof(struct shmid_ds));
766 			return 0;
767 
768 		case IPC_SET:
769 			shm->descriptor->shm_ctime		= time(NULL);
770 			shm->descriptor->shm_perm.uid	= buf->shm_perm.uid;
771 			shm->descriptor->shm_perm.gid	= buf->shm_perm.gid;
772 			shm->descriptor->shm_perm.mode	= buf->shm_perm.mode;
773 			return 0;
774 
775 		case IPC_RMID:
776 			if (shm->descriptor->shm_nattch < 1) {
777 				shm->descriptor->shm_perm.key = -1;
778 			}
779 			return 0;
780 
781 		default:
782 			return -1;
783 	}
784 }/*}}}*/
785 
UnixTimeToFileTime(time_t t,LPFILETIME pft)786 static zend_always_inline void UnixTimeToFileTime(time_t t, LPFILETIME pft) /* {{{ */
787 {
788 	// Note that LONGLONG is a 64-bit value
789 	LONGLONG ll;
790 
791 	ll = t * 10000000LL + 116444736000000000LL;
792 	pft->dwLowDateTime = (DWORD)ll;
793 	pft->dwHighDateTime = ll >> 32;
794 }
795 /* }}} */
796 
win32_utime(const char * filename,struct utimbuf * buf)797 TSRM_API int win32_utime(const char *filename, struct utimbuf *buf) /* {{{ */
798 {
799 	FILETIME mtime, atime;
800 	HANDLE hFile;
801 	PHP_WIN32_IOUTIL_INIT_W(filename)
802 
803 	if (!pathw) {
804 		return -1;
805 	}
806 
807 	hFile = CreateFileW(pathw, GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, NULL,
808 				 OPEN_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, NULL);
809 
810 	PHP_WIN32_IOUTIL_CLEANUP_W()
811 
812 	/* OPEN_ALWAYS mode sets the last error to ERROR_ALREADY_EXISTS but
813 	   the CreateFile operation succeeds */
814 	if (GetLastError() == ERROR_ALREADY_EXISTS) {
815 		SetLastError(0);
816 	}
817 
818 	if ( hFile == INVALID_HANDLE_VALUE ) {
819 		return -1;
820 	}
821 
822 	if (!buf) {
823 		SYSTEMTIME st;
824 		GetSystemTime(&st);
825 		SystemTimeToFileTime(&st, &mtime);
826 		atime = mtime;
827 	} else {
828 		UnixTimeToFileTime(buf->modtime, &mtime);
829 		UnixTimeToFileTime(buf->actime, &atime);
830 	}
831 	if (!SetFileTime(hFile, NULL, &atime, &mtime)) {
832 		CloseHandle(hFile);
833 		return -1;
834 	}
835 	CloseHandle(hFile);
836 	return 1;
837 }
838 /* }}} */
839 #endif
840