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