1 /*-------------------------------------------------------------------------
2  *
3  * exec.c
4  *		Functions for finding and validating executable files
5  *
6  *
7  * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
8  * Portions Copyright (c) 1994, Regents of the University of California
9  *
10  *
11  * IDENTIFICATION
12  *	  src/common/exec.c
13  *
14  *-------------------------------------------------------------------------
15  */
16 
17 #ifndef FRONTEND
18 #include "postgres.h"
19 #else
20 #include "postgres_fe.h"
21 #endif
22 
23 #include <signal.h>
24 #include <sys/stat.h>
25 #include <sys/wait.h>
26 #include <unistd.h>
27 
28 /*
29  * Hacky solution to allow expressing both frontend and backend error reports
30  * in one macro call.  First argument of log_error is an errcode() call of
31  * some sort (ignored if FRONTEND); the rest are errmsg_internal() arguments,
32  * i.e. message string and any parameters for it.
33  *
34  * Caller must provide the gettext wrapper around the message string, if
35  * appropriate, so that it gets translated in the FRONTEND case; this
36  * motivates using errmsg_internal() not errmsg().  We handle appending a
37  * newline, if needed, inside the macro, so that there's only one translatable
38  * string per call not two.
39  */
40 #ifndef FRONTEND
41 #define log_error(errcodefn, ...) \
42 	ereport(LOG, (errcodefn, errmsg_internal(__VA_ARGS__)))
43 #else
44 #define log_error(errcodefn, ...) \
45 	(fprintf(stderr, __VA_ARGS__), fputc('\n', stderr))
46 #endif
47 
48 #ifdef _MSC_VER
49 #define getcwd(cwd,len)  GetCurrentDirectory(len, cwd)
50 #endif
51 
52 static int	validate_exec(const char *path);
53 static int	resolve_symlinks(char *path);
54 
55 #ifdef WIN32
56 static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
57 #endif
58 
59 /*
60  * validate_exec -- validate "path" as an executable file
61  *
62  * returns 0 if the file is found and no error is encountered.
63  *		  -1 if the regular file "path" does not exist or cannot be executed.
64  *		  -2 if the file is otherwise valid but cannot be read.
65  */
66 static int
validate_exec(const char * path)67 validate_exec(const char *path)
68 {
69 	struct stat buf;
70 	int			is_r;
71 	int			is_x;
72 
73 #ifdef WIN32
74 	char		path_exe[MAXPGPATH + sizeof(".exe") - 1];
75 
76 	/* Win32 requires a .exe suffix for stat() */
77 	if (strlen(path) >= strlen(".exe") &&
78 		pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
79 	{
80 		strlcpy(path_exe, path, sizeof(path_exe) - 4);
81 		strcat(path_exe, ".exe");
82 		path = path_exe;
83 	}
84 #endif
85 
86 	/*
87 	 * Ensure that the file exists and is a regular file.
88 	 *
89 	 * XXX if you have a broken system where stat() looks at the symlink
90 	 * instead of the underlying file, you lose.
91 	 */
92 	if (stat(path, &buf) < 0)
93 		return -1;
94 
95 	if (!S_ISREG(buf.st_mode))
96 		return -1;
97 
98 	/*
99 	 * Ensure that the file is both executable and readable (required for
100 	 * dynamic loading).
101 	 */
102 #ifndef WIN32
103 	is_r = (access(path, R_OK) == 0);
104 	is_x = (access(path, X_OK) == 0);
105 #else
106 	is_r = buf.st_mode & S_IRUSR;
107 	is_x = buf.st_mode & S_IXUSR;
108 #endif
109 	return is_x ? (is_r ? 0 : -2) : -1;
110 }
111 
112 
113 /*
114  * find_my_exec -- find an absolute path to a valid executable
115  *
116  *	argv0 is the name passed on the command line
117  *	retpath is the output area (must be of size MAXPGPATH)
118  *	Returns 0 if OK, -1 if error.
119  *
120  * The reason we have to work so hard to find an absolute path is that
121  * on some platforms we can't do dynamic loading unless we know the
122  * executable's location.  Also, we need a full path not a relative
123  * path because we will later change working directory.  Finally, we want
124  * a true path not a symlink location, so that we can locate other files
125  * that are part of our installation relative to the executable.
126  */
127 int
find_my_exec(const char * argv0,char * retpath)128 find_my_exec(const char *argv0, char *retpath)
129 {
130 	char		cwd[MAXPGPATH],
131 				test_path[MAXPGPATH];
132 	char	   *path;
133 
134 	if (!getcwd(cwd, MAXPGPATH))
135 	{
136 		log_error(errcode_for_file_access(),
137 				  _("could not identify current directory: %m"));
138 		return -1;
139 	}
140 
141 	/*
142 	 * If argv0 contains a separator, then PATH wasn't used.
143 	 */
144 	if (first_dir_separator(argv0) != NULL)
145 	{
146 		if (is_absolute_path(argv0))
147 			StrNCpy(retpath, argv0, MAXPGPATH);
148 		else
149 			join_path_components(retpath, cwd, argv0);
150 		canonicalize_path(retpath);
151 
152 		if (validate_exec(retpath) == 0)
153 			return resolve_symlinks(retpath);
154 
155 		log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE),
156 				  _("invalid binary \"%s\""), retpath);
157 		return -1;
158 	}
159 
160 #ifdef WIN32
161 	/* Win32 checks the current directory first for names without slashes */
162 	join_path_components(retpath, cwd, argv0);
163 	if (validate_exec(retpath) == 0)
164 		return resolve_symlinks(retpath);
165 #endif
166 
167 	/*
168 	 * Since no explicit path was supplied, the user must have been relying on
169 	 * PATH.  We'll search the same PATH.
170 	 */
171 	if ((path = getenv("PATH")) && *path)
172 	{
173 		char	   *startp = NULL,
174 				   *endp = NULL;
175 
176 		do
177 		{
178 			if (!startp)
179 				startp = path;
180 			else
181 				startp = endp + 1;
182 
183 			endp = first_path_var_separator(startp);
184 			if (!endp)
185 				endp = startp + strlen(startp); /* point to end */
186 
187 			StrNCpy(test_path, startp, Min(endp - startp + 1, MAXPGPATH));
188 
189 			if (is_absolute_path(test_path))
190 				join_path_components(retpath, test_path, argv0);
191 			else
192 			{
193 				join_path_components(retpath, cwd, test_path);
194 				join_path_components(retpath, retpath, argv0);
195 			}
196 			canonicalize_path(retpath);
197 
198 			switch (validate_exec(retpath))
199 			{
200 				case 0:			/* found ok */
201 					return resolve_symlinks(retpath);
202 				case -1:		/* wasn't even a candidate, keep looking */
203 					break;
204 				case -2:		/* found but disqualified */
205 					log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE),
206 							  _("could not read binary \"%s\""),
207 							  retpath);
208 					break;
209 			}
210 		} while (*endp);
211 	}
212 
213 	log_error(errcode(ERRCODE_UNDEFINED_FILE),
214 			  _("could not find a \"%s\" to execute"), argv0);
215 	return -1;
216 }
217 
218 
219 /*
220  * resolve_symlinks - resolve symlinks to the underlying file
221  *
222  * Replace "path" by the absolute path to the referenced file.
223  *
224  * Returns 0 if OK, -1 if error.
225  *
226  * Note: we are not particularly tense about producing nice error messages
227  * because we are not really expecting error here; we just determined that
228  * the symlink does point to a valid executable.
229  */
230 static int
resolve_symlinks(char * path)231 resolve_symlinks(char *path)
232 {
233 #ifdef HAVE_READLINK
234 	struct stat buf;
235 	char		orig_wd[MAXPGPATH],
236 				link_buf[MAXPGPATH];
237 	char	   *fname;
238 
239 	/*
240 	 * To resolve a symlink properly, we have to chdir into its directory and
241 	 * then chdir to where the symlink points; otherwise we may fail to
242 	 * resolve relative links correctly (consider cases involving mount
243 	 * points, for example).  After following the final symlink, we use
244 	 * getcwd() to figure out where the heck we're at.
245 	 *
246 	 * One might think we could skip all this if path doesn't point to a
247 	 * symlink to start with, but that's wrong.  We also want to get rid of
248 	 * any directory symlinks that are present in the given path. We expect
249 	 * getcwd() to give us an accurate, symlink-free path.
250 	 */
251 	if (!getcwd(orig_wd, MAXPGPATH))
252 	{
253 		log_error(errcode_for_file_access(),
254 				  _("could not identify current directory: %m"));
255 		return -1;
256 	}
257 
258 	for (;;)
259 	{
260 		char	   *lsep;
261 		int			rllen;
262 
263 		lsep = last_dir_separator(path);
264 		if (lsep)
265 		{
266 			*lsep = '\0';
267 			if (chdir(path) == -1)
268 			{
269 				log_error(errcode_for_file_access(),
270 						  _("could not change directory to \"%s\": %m"), path);
271 				return -1;
272 			}
273 			fname = lsep + 1;
274 		}
275 		else
276 			fname = path;
277 
278 		if (lstat(fname, &buf) < 0 ||
279 			!S_ISLNK(buf.st_mode))
280 			break;
281 
282 		errno = 0;
283 		rllen = readlink(fname, link_buf, sizeof(link_buf));
284 		if (rllen < 0 || rllen >= sizeof(link_buf))
285 		{
286 			log_error(errcode_for_file_access(),
287 					  _("could not read symbolic link \"%s\": %m"), fname);
288 			return -1;
289 		}
290 		link_buf[rllen] = '\0';
291 		strcpy(path, link_buf);
292 	}
293 
294 	/* must copy final component out of 'path' temporarily */
295 	strlcpy(link_buf, fname, sizeof(link_buf));
296 
297 	if (!getcwd(path, MAXPGPATH))
298 	{
299 		log_error(errcode_for_file_access(),
300 				  _("could not identify current directory: %m"));
301 		return -1;
302 	}
303 	join_path_components(path, path, link_buf);
304 	canonicalize_path(path);
305 
306 	if (chdir(orig_wd) == -1)
307 	{
308 		log_error(errcode_for_file_access(),
309 				  _("could not change directory to \"%s\": %m"), orig_wd);
310 		return -1;
311 	}
312 #endif							/* HAVE_READLINK */
313 
314 	return 0;
315 }
316 
317 
318 /*
319  * Find another program in our binary's directory,
320  * then make sure it is the proper version.
321  */
322 int
find_other_exec(const char * argv0,const char * target,const char * versionstr,char * retpath)323 find_other_exec(const char *argv0, const char *target,
324 				const char *versionstr, char *retpath)
325 {
326 	char		cmd[MAXPGPATH];
327 	char		line[MAXPGPATH];
328 
329 	if (find_my_exec(argv0, retpath) < 0)
330 		return -1;
331 
332 	/* Trim off program name and keep just directory */
333 	*last_dir_separator(retpath) = '\0';
334 	canonicalize_path(retpath);
335 
336 	/* Now append the other program's name */
337 	snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
338 			 "/%s%s", target, EXE);
339 
340 	if (validate_exec(retpath) != 0)
341 		return -1;
342 
343 	snprintf(cmd, sizeof(cmd), "\"%s\" -V", retpath);
344 
345 	if (!pipe_read_line(cmd, line, sizeof(line)))
346 		return -1;
347 
348 	if (strcmp(line, versionstr) != 0)
349 		return -2;
350 
351 	return 0;
352 }
353 
354 
355 /*
356  * Execute a command in a pipe and read the first line from it.
357  */
358 char *
pipe_read_line(char * cmd,char * line,int maxsize)359 pipe_read_line(char *cmd, char *line, int maxsize)
360 {
361 	FILE	   *pgver;
362 
363 	/* flush output buffers in case popen does not... */
364 	fflush(stdout);
365 	fflush(stderr);
366 
367 	errno = 0;
368 	if ((pgver = popen(cmd, "r")) == NULL)
369 	{
370 		perror("popen failure");
371 		return NULL;
372 	}
373 
374 	errno = 0;
375 	if (fgets(line, maxsize, pgver) == NULL)
376 	{
377 		if (feof(pgver))
378 			fprintf(stderr, "no data was returned by command \"%s\"\n", cmd);
379 		else
380 			perror("fgets failure");
381 		pclose(pgver);			/* no error checking */
382 		return NULL;
383 	}
384 
385 	if (pclose_check(pgver))
386 		return NULL;
387 
388 	return line;
389 }
390 
391 
392 /*
393  * pclose() plus useful error reporting
394  */
395 int
pclose_check(FILE * stream)396 pclose_check(FILE *stream)
397 {
398 	int			exitstatus;
399 	char	   *reason;
400 
401 	exitstatus = pclose(stream);
402 
403 	if (exitstatus == 0)
404 		return 0;				/* all is well */
405 
406 	if (exitstatus == -1)
407 	{
408 		/* pclose() itself failed, and hopefully set errno */
409 		log_error(errcode(ERRCODE_SYSTEM_ERROR),
410 				  _("pclose failed: %m"));
411 	}
412 	else
413 	{
414 		reason = wait_result_to_str(exitstatus);
415 		log_error(errcode(ERRCODE_SYSTEM_ERROR),
416 				  "%s", reason);
417 		pfree(reason);
418 	}
419 	return exitstatus;
420 }
421 
422 /*
423  *	set_pglocale_pgservice
424  *
425  *	Set application-specific locale and service directory
426  *
427  *	This function takes the value of argv[0] rather than a full path.
428  *
429  * (You may be wondering why this is in exec.c.  It requires this module's
430  * services and doesn't introduce any new dependencies, so this seems as
431  * good as anyplace.)
432  */
433 void
set_pglocale_pgservice(const char * argv0,const char * app)434 set_pglocale_pgservice(const char *argv0, const char *app)
435 {
436 	char		path[MAXPGPATH];
437 	char		my_exec_path[MAXPGPATH];
438 	char		env_path[MAXPGPATH + sizeof("PGSYSCONFDIR=")];	/* longer than
439 																 * PGLOCALEDIR */
440 	char	   *dup_path;
441 
442 	/* don't set LC_ALL in the backend */
443 	if (strcmp(app, PG_TEXTDOMAIN("postgres")) != 0)
444 	{
445 		setlocale(LC_ALL, "");
446 
447 		/*
448 		 * One could make a case for reproducing here PostmasterMain()'s test
449 		 * for whether the process is multithreaded.  Unlike the postmaster,
450 		 * no frontend program calls sigprocmask() or otherwise provides for
451 		 * mutual exclusion between signal handlers.  While frontends using
452 		 * fork(), if multithreaded, are formally exposed to undefined
453 		 * behavior, we have not witnessed a concrete bug.  Therefore,
454 		 * complaining about multithreading here may be mere pedantry.
455 		 */
456 	}
457 
458 	if (find_my_exec(argv0, my_exec_path) < 0)
459 		return;
460 
461 #ifdef ENABLE_NLS
462 	get_locale_path(my_exec_path, path);
463 	bindtextdomain(app, path);
464 	textdomain(app);
465 
466 	if (getenv("PGLOCALEDIR") == NULL)
467 	{
468 		/* set for libpq to use */
469 		snprintf(env_path, sizeof(env_path), "PGLOCALEDIR=%s", path);
470 		canonicalize_path(env_path + 12);
471 		dup_path = strdup(env_path);
472 		if (dup_path)
473 			putenv(dup_path);
474 	}
475 #endif
476 
477 	if (getenv("PGSYSCONFDIR") == NULL)
478 	{
479 		get_etc_path(my_exec_path, path);
480 
481 		/* set for libpq to use */
482 		snprintf(env_path, sizeof(env_path), "PGSYSCONFDIR=%s", path);
483 		canonicalize_path(env_path + 13);
484 		dup_path = strdup(env_path);
485 		if (dup_path)
486 			putenv(dup_path);
487 	}
488 }
489 
490 #ifdef WIN32
491 
492 /*
493  * AddUserToTokenDacl(HANDLE hToken)
494  *
495  * This function adds the current user account to the restricted
496  * token used when we create a restricted process.
497  *
498  * This is required because of some security changes in Windows
499  * that appeared in patches to XP/2K3 and in Vista/2008.
500  *
501  * On these machines, the Administrator account is not included in
502  * the default DACL - you just get Administrators + System. For
503  * regular users you get User + System. Because we strip Administrators
504  * when we create the restricted token, we are left with only System
505  * in the DACL which leads to access denied errors for later CreatePipe()
506  * and CreateProcess() calls when running as Administrator.
507  *
508  * This function fixes this problem by modifying the DACL of the
509  * token the process will use, and explicitly re-adding the current
510  * user account.  This is still secure because the Administrator account
511  * inherits its privileges from the Administrators group - it doesn't
512  * have any of its own.
513  */
514 BOOL
AddUserToTokenDacl(HANDLE hToken)515 AddUserToTokenDacl(HANDLE hToken)
516 {
517 	int			i;
518 	ACL_SIZE_INFORMATION asi;
519 	ACCESS_ALLOWED_ACE *pace;
520 	DWORD		dwNewAclSize;
521 	DWORD		dwSize = 0;
522 	DWORD		dwTokenInfoLength = 0;
523 	PACL		pacl = NULL;
524 	PTOKEN_USER pTokenUser = NULL;
525 	TOKEN_DEFAULT_DACL tddNew;
526 	TOKEN_DEFAULT_DACL *ptdd = NULL;
527 	TOKEN_INFORMATION_CLASS tic = TokenDefaultDacl;
528 	BOOL		ret = FALSE;
529 
530 	/* Figure out the buffer size for the DACL info */
531 	if (!GetTokenInformation(hToken, tic, (LPVOID) NULL, dwTokenInfoLength, &dwSize))
532 	{
533 		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
534 		{
535 			ptdd = (TOKEN_DEFAULT_DACL *) LocalAlloc(LPTR, dwSize);
536 			if (ptdd == NULL)
537 			{
538 				log_error(errcode(ERRCODE_OUT_OF_MEMORY),
539 						  _("out of memory"));
540 				goto cleanup;
541 			}
542 
543 			if (!GetTokenInformation(hToken, tic, (LPVOID) ptdd, dwSize, &dwSize))
544 			{
545 				log_error(errcode(ERRCODE_SYSTEM_ERROR),
546 						  "could not get token information: error code %lu",
547 						  GetLastError());
548 				goto cleanup;
549 			}
550 		}
551 		else
552 		{
553 			log_error(errcode(ERRCODE_SYSTEM_ERROR),
554 					  "could not get token information buffer size: error code %lu",
555 					  GetLastError());
556 			goto cleanup;
557 		}
558 	}
559 
560 	/* Get the ACL info */
561 	if (!GetAclInformation(ptdd->DefaultDacl, (LPVOID) &asi,
562 						   (DWORD) sizeof(ACL_SIZE_INFORMATION),
563 						   AclSizeInformation))
564 	{
565 		log_error(errcode(ERRCODE_SYSTEM_ERROR),
566 				  "could not get ACL information: error code %lu",
567 				  GetLastError());
568 		goto cleanup;
569 	}
570 
571 	/* Get the current user SID */
572 	if (!GetTokenUser(hToken, &pTokenUser))
573 		goto cleanup;			/* callee printed a message */
574 
575 	/* Figure out the size of the new ACL */
576 	dwNewAclSize = asi.AclBytesInUse + sizeof(ACCESS_ALLOWED_ACE) +
577 		GetLengthSid(pTokenUser->User.Sid) - sizeof(DWORD);
578 
579 	/* Allocate the ACL buffer & initialize it */
580 	pacl = (PACL) LocalAlloc(LPTR, dwNewAclSize);
581 	if (pacl == NULL)
582 	{
583 		log_error(errcode(ERRCODE_OUT_OF_MEMORY),
584 				  _("out of memory"));
585 		goto cleanup;
586 	}
587 
588 	if (!InitializeAcl(pacl, dwNewAclSize, ACL_REVISION))
589 	{
590 		log_error(errcode(ERRCODE_SYSTEM_ERROR),
591 				  "could not initialize ACL: error code %lu", GetLastError());
592 		goto cleanup;
593 	}
594 
595 	/* Loop through the existing ACEs, and build the new ACL */
596 	for (i = 0; i < (int) asi.AceCount; i++)
597 	{
598 		if (!GetAce(ptdd->DefaultDacl, i, (LPVOID *) &pace))
599 		{
600 			log_error(errcode(ERRCODE_SYSTEM_ERROR),
601 					  "could not get ACE: error code %lu", GetLastError());
602 			goto cleanup;
603 		}
604 
605 		if (!AddAce(pacl, ACL_REVISION, MAXDWORD, pace, ((PACE_HEADER) pace)->AceSize))
606 		{
607 			log_error(errcode(ERRCODE_SYSTEM_ERROR),
608 					  "could not add ACE: error code %lu", GetLastError());
609 			goto cleanup;
610 		}
611 	}
612 
613 	/* Add the new ACE for the current user */
614 	if (!AddAccessAllowedAceEx(pacl, ACL_REVISION, OBJECT_INHERIT_ACE, GENERIC_ALL, pTokenUser->User.Sid))
615 	{
616 		log_error(errcode(ERRCODE_SYSTEM_ERROR),
617 				  "could not add access allowed ACE: error code %lu",
618 				  GetLastError());
619 		goto cleanup;
620 	}
621 
622 	/* Set the new DACL in the token */
623 	tddNew.DefaultDacl = pacl;
624 
625 	if (!SetTokenInformation(hToken, tic, (LPVOID) &tddNew, dwNewAclSize))
626 	{
627 		log_error(errcode(ERRCODE_SYSTEM_ERROR),
628 				  "could not set token information: error code %lu",
629 				  GetLastError());
630 		goto cleanup;
631 	}
632 
633 	ret = TRUE;
634 
635 cleanup:
636 	if (pTokenUser)
637 		LocalFree((HLOCAL) pTokenUser);
638 
639 	if (pacl)
640 		LocalFree((HLOCAL) pacl);
641 
642 	if (ptdd)
643 		LocalFree((HLOCAL) ptdd);
644 
645 	return ret;
646 }
647 
648 /*
649  * GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser)
650  *
651  * Get the users token information from a process token.
652  *
653  * The caller of this function is responsible for calling LocalFree() on the
654  * returned TOKEN_USER memory.
655  */
656 static BOOL
GetTokenUser(HANDLE hToken,PTOKEN_USER * ppTokenUser)657 GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser)
658 {
659 	DWORD		dwLength;
660 
661 	*ppTokenUser = NULL;
662 
663 	if (!GetTokenInformation(hToken,
664 							 TokenUser,
665 							 NULL,
666 							 0,
667 							 &dwLength))
668 	{
669 		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
670 		{
671 			*ppTokenUser = (PTOKEN_USER) LocalAlloc(LPTR, dwLength);
672 
673 			if (*ppTokenUser == NULL)
674 			{
675 				log_error(errcode(ERRCODE_OUT_OF_MEMORY),
676 						  _("out of memory"));
677 				return FALSE;
678 			}
679 		}
680 		else
681 		{
682 			log_error(errcode(ERRCODE_SYSTEM_ERROR),
683 					  "could not get token information buffer size: error code %lu",
684 					  GetLastError());
685 			return FALSE;
686 		}
687 	}
688 
689 	if (!GetTokenInformation(hToken,
690 							 TokenUser,
691 							 *ppTokenUser,
692 							 dwLength,
693 							 &dwLength))
694 	{
695 		LocalFree(*ppTokenUser);
696 		*ppTokenUser = NULL;
697 
698 		log_error(errcode(ERRCODE_SYSTEM_ERROR),
699 				  "could not get token information: error code %lu",
700 				  GetLastError());
701 		return FALSE;
702 	}
703 
704 	/* Memory in *ppTokenUser is LocalFree():d by the caller */
705 	return TRUE;
706 }
707 
708 #endif
709