xref: /PHP-8.4/TSRM/tsrm_win32.c (revision f303840a)
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 	size_t cmd_buffer_size = strlen(command) + strlen(TWG(comspec)) + sizeof(" /s /c ") + 2;
481 	cmd = malloc(cmd_buffer_size);
482 	if (!cmd) {
483 		return NULL;
484 	}
485 
486 	snprintf(cmd, cmd_buffer_size, "%s /s /c \"%s\"", TWG(comspec), command);
487 	cmdw = php_win32_cp_any_to_w(cmd);
488 	if (!cmdw) {
489 		free(cmd);
490 		return NULL;
491 	}
492 
493 	if (cwd) {
494 		cwdw = php_win32_ioutil_any_to_w(cwd);
495 		if (!cwdw) {
496 			free(cmd);
497 			free(cmdw);
498 			return NULL;
499 		}
500 	}
501 
502 	security.nLength				= sizeof(SECURITY_ATTRIBUTES);
503 	security.bInheritHandle			= TRUE;
504 	security.lpSecurityDescriptor	= NULL;
505 
506 	if (!type_len || !CreatePipe(&in, &out, &security, 2048L)) {
507 		free(cmdw);
508 		free(cwdw);
509 		free(cmd);
510 		return NULL;
511 	}
512 
513 	memset(&startup, 0, sizeof(STARTUPINFOW));
514 	memset(&process, 0, sizeof(PROCESS_INFORMATION));
515 
516 	startup.cb			= sizeof(STARTUPINFOW);
517 	startup.dwFlags		= STARTF_USESTDHANDLES;
518 	startup.hStdError	= GetStdHandle(STD_ERROR_HANDLE);
519 
520 	read = (type[0] == 'r') ? TRUE : FALSE;
521 	mode = ((type_len == 2) && (type[1] == 'b')) ? O_BINARY : O_TEXT;
522 
523 	if (read) {
524 		in = dupHandle(in, FALSE);
525 		startup.hStdInput  = GetStdHandle(STD_INPUT_HANDLE);
526 		startup.hStdOutput = out;
527 	} else {
528 		out = dupHandle(out, FALSE);
529 		startup.hStdInput  = in;
530 		startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
531 	}
532 
533 	dwCreateFlags = NORMAL_PRIORITY_CLASS;
534 	if (strcmp(sapi_module.name, "cli") != 0) {
535 		dwCreateFlags |= CREATE_NO_WINDOW;
536 	}
537 
538 	/* Get a token with the impersonated user. */
539 	if(OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &thread_token)) {
540 		DuplicateTokenEx(thread_token, MAXIMUM_ALLOWED, &security, SecurityImpersonation, TokenPrimary, &token_user);
541 	} else {
542 		DWORD err = GetLastError();
543 		if (err == ERROR_NO_TOKEN) {
544 			asuser = FALSE;
545 		}
546 	}
547 
548 	envw = php_win32_cp_env_any_to_w(env);
549 	if (envw) {
550 		dwCreateFlags |= CREATE_UNICODE_ENVIRONMENT;
551 	} else {
552 		if (env) {
553 			free(cmd);
554 			free(cmdw);
555 			free(cwdw);
556 			return NULL;
557 		}
558 	}
559 
560 	if (asuser) {
561 		res = CreateProcessAsUserW(token_user, NULL, cmdw, &security, &security, security.bInheritHandle, dwCreateFlags, envw, cwdw, &startup, &process);
562 		CloseHandle(token_user);
563 	} else {
564 		res = CreateProcessW(NULL, cmdw, &security, &security, security.bInheritHandle, dwCreateFlags, envw, cwdw, &startup, &process);
565 	}
566 	free(cmd);
567 	free(cmdw);
568 	free(cwdw);
569 	free(envw);
570 
571 	if (!res) {
572 		return NULL;
573 	}
574 
575 	CloseHandle(process.hThread);
576 	proc = process_get(NULL);
577 
578 	if (read) {
579 		fno = _open_osfhandle((tsrm_intptr_t)in, _O_RDONLY | mode);
580 		CloseHandle(out);
581 	} else {
582 		fno = _open_osfhandle((tsrm_intptr_t)out, _O_WRONLY | mode);
583 		CloseHandle(in);
584 	}
585 
586 	stream = _fdopen(fno, type);
587 	proc->prochnd = process.hProcess;
588 	proc->stream = stream;
589 	return stream;
590 }/*}}}*/
591 
pclose(FILE * stream)592 TSRM_API int pclose(FILE *stream)
593 {/*{{{*/
594 	DWORD termstat = 0;
595 	process_pair *process;
596 
597 	if ((process = process_get(stream)) == NULL) {
598 		return 0;
599 	}
600 
601 	fflush(process->stream);
602 	fclose(process->stream);
603 
604 	WaitForSingleObject(process->prochnd, INFINITE);
605 	GetExitCodeProcess(process->prochnd, &termstat);
606 	process->stream = NULL;
607 	CloseHandle(process->prochnd);
608 
609 	return termstat;
610 }/*}}}*/
611 
612 #define SEGMENT_PREFIX "TSRM_SHM_SEGMENT:"
613 #define INT_MIN_AS_STRING "-2147483648"
614 
615 
616 #define TSRM_BASE_SHM_KEY_ADDRESS 0x20000000
617 /* Returns a number between 0x2000_0000 and 0x3fff_ffff. On Windows, key_t is int. */
tsrm_choose_random_shm_key(key_t prev_key)618 static key_t tsrm_choose_random_shm_key(key_t prev_key) {
619 	unsigned char buf[4];
620 	if (php_win32_get_random_bytes(buf, 4) != SUCCESS) {
621 		return prev_key + 2;
622 	}
623 	uint32_t n =
624 		((uint32_t)(buf[0]) << 24) |
625 	    (((uint32_t)buf[1]) << 16) |
626 	    (((uint32_t)buf[2]) << 8) |
627 	    (((uint32_t)buf[3]));
628 	return (n & 0x1fffffff) + TSRM_BASE_SHM_KEY_ADDRESS;
629 }
630 
shmget(key_t key,size_t size,int flags)631 TSRM_API int shmget(key_t key, size_t size, int flags)
632 {/*{{{*/
633 	shm_pair *shm;
634 	char shm_segment[sizeof(SEGMENT_PREFIX INT_MIN_AS_STRING)];
635 	HANDLE shm_handle = NULL, info_handle = NULL;
636 	BOOL created = FALSE;
637 
638 	if (key != IPC_PRIVATE) {
639 		snprintf(shm_segment, sizeof(shm_segment), SEGMENT_PREFIX "%d", key);
640 
641 		shm_handle  = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, shm_segment);
642 	} else {
643 		/* IPC_PRIVATE always creates a new segment even if IPC_CREAT flag isn't passed. */
644 		flags |= IPC_CREAT;
645 	}
646 
647 	if (!shm_handle) {
648 		if (flags & IPC_CREAT) {
649 			if (size == 0 || size > SIZE_MAX - sizeof(shm->descriptor)) {
650 				return -1;
651 			}
652 			size += sizeof(shm->descriptor);
653 #if SIZEOF_SIZE_T == 8
654 			DWORD high = size >> 32;
655 			DWORD low = (DWORD)size;
656 #else
657 			DWORD high = 0;
658 			DWORD low = size;
659 #endif
660 			shm_handle	= CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, high, low, key == IPC_PRIVATE ? NULL : shm_segment);
661 			created		= TRUE;
662 		}
663 		if (!shm_handle) {
664 			return -1;
665 		}
666 	} else {
667 		if (flags & IPC_EXCL) {
668 			CloseHandle(shm_handle);
669 			return -1;
670 		}
671 	}
672 
673 	if (key == IPC_PRIVATE) {
674 		/* 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
675 		 * 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. */
676 		key = tsrm_choose_random_shm_key(TSRM_BASE_SHM_KEY_ADDRESS);
677 		for (shm_pair *ptr = TWG(shm); ptr < (TWG(shm) + TWG(shm_size)); ptr++) {
678 			if (ptr->descriptor && ptr->descriptor->shm_perm.key == key) {
679 				key = tsrm_choose_random_shm_key(key);
680 				ptr = TWG(shm);
681 				continue;
682 			}
683 		}
684 	}
685 
686 	shm = shm_get(key, NULL);
687 	if (!shm) {
688 		CloseHandle(shm_handle);
689 		return -1;
690 	}
691 	shm->segment = shm_handle;
692 	shm->descriptor = MapViewOfFileEx(shm->segment, FILE_MAP_ALL_ACCESS, 0, 0, 0, NULL);
693 
694 	if (NULL != shm->descriptor && created) {
695 		shm->descriptor->shm_perm.key	= key;
696 		shm->descriptor->shm_segsz		= size;
697 		shm->descriptor->shm_ctime		= time(NULL);
698 		shm->descriptor->shm_cpid		= getpid();
699 		shm->descriptor->shm_perm.mode	= flags;
700 
701 		shm->descriptor->shm_perm.cuid	= shm->descriptor->shm_perm.cgid= 0;
702 		shm->descriptor->shm_perm.gid	= shm->descriptor->shm_perm.uid = 0;
703 		shm->descriptor->shm_atime		= shm->descriptor->shm_dtime	= 0;
704 		shm->descriptor->shm_lpid		= shm->descriptor->shm_nattch	= 0;
705 		shm->descriptor->shm_perm.mode	= shm->descriptor->shm_perm.seq	= 0;
706 	}
707 
708 	if (NULL != shm->descriptor && (shm->descriptor->shm_perm.key != key || size > shm->descriptor->shm_segsz)) {
709 		if (NULL != shm->segment) {
710 			CloseHandle(shm->segment);
711 			shm->segment = INVALID_HANDLE_VALUE;
712 		}
713 		UnmapViewOfFile(shm->descriptor);
714 		shm->descriptor = NULL;
715 		return -1;
716 	}
717 
718 	return key;
719 }/*}}}*/
720 
shmat(int key,const void * shmaddr,int flags)721 TSRM_API void *shmat(int key, const void *shmaddr, int flags)
722 {/*{{{*/
723 	shm_pair *shm = shm_get(key, NULL);
724 
725 	if (!shm || !shm->segment) {
726 		return (void*)-1;
727 	}
728 
729 	shm->addr = shm->descriptor + sizeof(shm->descriptor);
730 	shm->descriptor->shm_atime = time(NULL);
731 	shm->descriptor->shm_lpid  = getpid();
732 	shm->descriptor->shm_nattch++;
733 
734 	return shm->addr;
735 }/*}}}*/
736 
shmdt(const void * shmaddr)737 TSRM_API int shmdt(const void *shmaddr)
738 {/*{{{*/
739 	shm_pair *shm = shm_get(0, (void*)shmaddr);
740 	int ret;
741 
742 	if (!shm || !shm->segment) {
743 		return -1;
744 	}
745 
746 	shm->descriptor->shm_dtime = time(NULL);
747 	shm->descriptor->shm_lpid  = getpid();
748 	shm->descriptor->shm_nattch--;
749 
750 	ret = 0;
751 	if (shm->descriptor->shm_nattch <= 0) {
752 		ret = UnmapViewOfFile(shm->descriptor) ? 0 : -1;
753 		shm->descriptor = NULL;
754 	}
755 	return ret;
756 }/*}}}*/
757 
shmctl(int key,int cmd,struct shmid_ds * buf)758 TSRM_API int shmctl(int key, int cmd, struct shmid_ds *buf)
759 {/*{{{*/
760 	shm_pair *shm = shm_get(key, NULL);
761 
762 	if (!shm || !shm->segment) {
763 		return -1;
764 	}
765 
766 	switch (cmd) {
767 		case IPC_STAT:
768 			memcpy(buf, shm->descriptor, sizeof(struct shmid_ds));
769 			return 0;
770 
771 		case IPC_SET:
772 			shm->descriptor->shm_ctime		= time(NULL);
773 			shm->descriptor->shm_perm.uid	= buf->shm_perm.uid;
774 			shm->descriptor->shm_perm.gid	= buf->shm_perm.gid;
775 			shm->descriptor->shm_perm.mode	= buf->shm_perm.mode;
776 			return 0;
777 
778 		case IPC_RMID:
779 			if (shm->descriptor->shm_nattch < 1) {
780 				shm->descriptor->shm_perm.key = -1;
781 			}
782 			return 0;
783 
784 		default:
785 			return -1;
786 	}
787 }/*}}}*/
788 
UnixTimeToFileTime(time_t t,LPFILETIME pft)789 static zend_always_inline void UnixTimeToFileTime(time_t t, LPFILETIME pft) /* {{{ */
790 {
791 	// Note that LONGLONG is a 64-bit value
792 	LONGLONG ll;
793 
794 	ll = t * 10000000LL + 116444736000000000LL;
795 	pft->dwLowDateTime = (DWORD)ll;
796 	pft->dwHighDateTime = ll >> 32;
797 }
798 /* }}} */
799 
win32_utime(const char * filename,struct utimbuf * buf)800 TSRM_API int win32_utime(const char *filename, struct utimbuf *buf) /* {{{ */
801 {
802 	FILETIME mtime, atime;
803 	HANDLE hFile;
804 	PHP_WIN32_IOUTIL_INIT_W(filename)
805 
806 	if (!pathw) {
807 		return -1;
808 	}
809 
810 	hFile = CreateFileW(pathw, GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, NULL,
811 				 OPEN_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, NULL);
812 
813 	PHP_WIN32_IOUTIL_CLEANUP_W()
814 
815 	/* OPEN_ALWAYS mode sets the last error to ERROR_ALREADY_EXISTS but
816 	   the CreateFile operation succeeds */
817 	if (GetLastError() == ERROR_ALREADY_EXISTS) {
818 		SetLastError(0);
819 	}
820 
821 	if ( hFile == INVALID_HANDLE_VALUE ) {
822 		return -1;
823 	}
824 
825 	if (!buf) {
826 		SYSTEMTIME st;
827 		GetSystemTime(&st);
828 		SystemTimeToFileTime(&st, &mtime);
829 		atime = mtime;
830 	} else {
831 		UnixTimeToFileTime(buf->modtime, &mtime);
832 		UnixTimeToFileTime(buf->actime, &atime);
833 	}
834 	if (!SetFileTime(hFile, NULL, &atime, &mtime)) {
835 		CloseHandle(hFile);
836 		return -1;
837 	}
838 	CloseHandle(hFile);
839 	return 1;
840 }
841 /* }}} */
842 #endif
843