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