1 /*-------------------------------------------------------------------------
4 * Functions for finding and validating executable files
7 * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
14 *-------------------------------------------------------------------------
20 #include "postgres_fe.h"
29 #if defined(HAVE_SYS_PERSONALITY_H)
30 #include <sys/personality.h>
31 #elif defined(HAVE_SYS_PROCCTL_H)
32 #include <sys/procctl.h>
36 /* Inhibit mingw CRT's auto-globbing of command line arguments */
37 #if defined(WIN32) && !defined(_MSC_VER)
38 extern int _CRT_glob
= 0; /* 0 turns off globbing; 1 turns it on */
42 * Hacky solution to allow expressing both frontend and backend error reports
43 * in one macro call. First argument of log_error is an errcode() call of
44 * some sort (ignored if FRONTEND); the rest are errmsg_internal() arguments,
45 * i.e. message string and any parameters for it.
47 * Caller must provide the gettext wrapper around the message string, if
48 * appropriate, so that it gets translated in the FRONTEND case; this
49 * motivates using errmsg_internal() not errmsg(). We handle appending a
50 * newline, if needed, inside the macro, so that there's only one translatable
51 * string per call not two.
54 #define log_error(errcodefn, ...) \
55 ereport(LOG, (errcodefn, errmsg_internal(__VA_ARGS__)))
57 #define log_error(errcodefn, ...) \
58 (fprintf(stderr, __VA_ARGS__), fputc('\n', stderr))
62 #define getcwd(cwd,len) GetCurrentDirectory(len, cwd)
65 static int resolve_symlinks(char *path
);
68 static BOOL
GetTokenUser(HANDLE hToken
, PTOKEN_USER
*ppTokenUser
);
72 * validate_exec -- validate "path" as an executable file
74 * returns 0 if the file is found and no error is encountered.
75 * -1 if the regular file "path" does not exist or cannot be executed.
76 * -2 if the file is otherwise valid but cannot be read.
77 * in the failure cases, errno is set appropriately
80 validate_exec(const char *path
)
87 char path_exe
[MAXPGPATH
+ sizeof(".exe") - 1];
89 /* Win32 requires a .exe suffix for stat() */
90 if (strlen(path
) >= strlen(".exe") &&
91 pg_strcasecmp(path
+ strlen(path
) - strlen(".exe"), ".exe") != 0)
93 strlcpy(path_exe
, path
, sizeof(path_exe
) - 4);
94 strcat(path_exe
, ".exe");
100 * Ensure that the file exists and is a regular file.
102 * XXX if you have a broken system where stat() looks at the symlink
103 * instead of the underlying file, you lose.
105 if (stat(path
, &buf
) < 0)
108 if (!S_ISREG(buf
.st_mode
))
111 * POSIX offers no errno code that's simply "not a regular file". If
112 * it's a directory we can use EISDIR. Otherwise, it's most likely a
113 * device special file, and EPERM (Operation not permitted) isn't too
116 errno
= S_ISDIR(buf
.st_mode
) ? EISDIR
: EPERM
;
121 * Ensure that the file is both executable and readable (required for
125 is_r
= (access(path
, R_OK
) == 0);
126 is_x
= (access(path
, X_OK
) == 0);
127 /* access() will set errno if it returns -1 */
129 is_r
= buf
.st_mode
& S_IRUSR
;
130 is_x
= buf
.st_mode
& S_IXUSR
;
131 errno
= EACCES
; /* appropriate thing if we return nonzero */
133 return is_x
? (is_r
? 0 : -2) : -1;
138 * find_my_exec -- find an absolute path to a valid executable
140 * argv0 is the name passed on the command line
141 * retpath is the output area (must be of size MAXPGPATH)
142 * Returns 0 if OK, -1 if error.
144 * The reason we have to work so hard to find an absolute path is that
145 * on some platforms we can't do dynamic loading unless we know the
146 * executable's location. Also, we need a full path not a relative
147 * path because we will later change working directory. Finally, we want
148 * a true path not a symlink location, so that we can locate other files
149 * that are part of our installation relative to the executable.
152 find_my_exec(const char *argv0
, char *retpath
)
155 test_path
[MAXPGPATH
];
158 if (!getcwd(cwd
, MAXPGPATH
))
160 log_error(errcode_for_file_access(),
161 _("could not identify current directory: %m"));
166 * If argv0 contains a separator, then PATH wasn't used.
168 if (first_dir_separator(argv0
) != NULL
)
170 if (is_absolute_path(argv0
))
171 strlcpy(retpath
, argv0
, MAXPGPATH
);
173 join_path_components(retpath
, cwd
, argv0
);
174 canonicalize_path(retpath
);
176 if (validate_exec(retpath
) == 0)
177 return resolve_symlinks(retpath
);
179 log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE
),
180 _("invalid binary \"%s\": %m"), retpath
);
185 /* Win32 checks the current directory first for names without slashes */
186 join_path_components(retpath
, cwd
, argv0
);
187 if (validate_exec(retpath
) == 0)
188 return resolve_symlinks(retpath
);
192 * Since no explicit path was supplied, the user must have been relying on
193 * PATH. We'll search the same PATH.
195 if ((path
= getenv("PATH")) && *path
)
207 endp
= first_path_var_separator(startp
);
209 endp
= startp
+ strlen(startp
); /* point to end */
211 strlcpy(test_path
, startp
, Min(endp
- startp
+ 1, MAXPGPATH
));
213 if (is_absolute_path(test_path
))
214 join_path_components(retpath
, test_path
, argv0
);
217 join_path_components(retpath
, cwd
, test_path
);
218 join_path_components(retpath
, retpath
, argv0
);
220 canonicalize_path(retpath
);
222 switch (validate_exec(retpath
))
224 case 0: /* found ok */
225 return resolve_symlinks(retpath
);
226 case -1: /* wasn't even a candidate, keep looking */
228 case -2: /* found but disqualified */
229 log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE
),
230 _("could not read binary \"%s\": %m"),
237 log_error(errcode(ERRCODE_UNDEFINED_FILE
),
238 _("could not find a \"%s\" to execute"), argv0
);
244 * resolve_symlinks - resolve symlinks to the underlying file
246 * Replace "path" by the absolute path to the referenced file.
248 * Returns 0 if OK, -1 if error.
250 * Note: we are not particularly tense about producing nice error messages
251 * because we are not really expecting error here; we just determined that
252 * the symlink does point to a valid executable.
254 * Here we test HAVE_READLINK, which excludes Windows. There's no point in
255 * using our junction point-based replacement code for this, because that only
256 * works for directories.
259 resolve_symlinks(char *path
)
263 char orig_wd
[MAXPGPATH
],
268 * To resolve a symlink properly, we have to chdir into its directory and
269 * then chdir to where the symlink points; otherwise we may fail to
270 * resolve relative links correctly (consider cases involving mount
271 * points, for example). After following the final symlink, we use
272 * getcwd() to figure out where the heck we're at.
274 * One might think we could skip all this if path doesn't point to a
275 * symlink to start with, but that's wrong. We also want to get rid of
276 * any directory symlinks that are present in the given path. We expect
277 * getcwd() to give us an accurate, symlink-free path.
279 if (!getcwd(orig_wd
, MAXPGPATH
))
281 log_error(errcode_for_file_access(),
282 _("could not identify current directory: %m"));
291 lsep
= last_dir_separator(path
);
295 if (chdir(path
) == -1)
297 log_error(errcode_for_file_access(),
298 _("could not change directory to \"%s\": %m"), path
);
306 if (lstat(fname
, &buf
) < 0 ||
307 !S_ISLNK(buf
.st_mode
))
311 rllen
= readlink(fname
, link_buf
, sizeof(link_buf
));
312 if (rllen
< 0 || rllen
>= sizeof(link_buf
))
314 log_error(errcode_for_file_access(),
315 _("could not read symbolic link \"%s\": %m"), fname
);
318 link_buf
[rllen
] = '\0';
319 strcpy(path
, link_buf
);
322 /* must copy final component out of 'path' temporarily */
323 strlcpy(link_buf
, fname
, sizeof(link_buf
));
325 if (!getcwd(path
, MAXPGPATH
))
327 log_error(errcode_for_file_access(),
328 _("could not identify current directory: %m"));
331 join_path_components(path
, path
, link_buf
);
332 canonicalize_path(path
);
334 if (chdir(orig_wd
) == -1)
336 log_error(errcode_for_file_access(),
337 _("could not change directory to \"%s\": %m"), orig_wd
);
340 #endif /* HAVE_READLINK */
347 * Find another program in our binary's directory,
348 * then make sure it is the proper version.
351 find_other_exec(const char *argv0
, const char *target
,
352 const char *versionstr
, char *retpath
)
355 char line
[MAXPGPATH
];
357 if (find_my_exec(argv0
, retpath
) < 0)
360 /* Trim off program name and keep just directory */
361 *last_dir_separator(retpath
) = '\0';
362 canonicalize_path(retpath
);
364 /* Now append the other program's name */
365 snprintf(retpath
+ strlen(retpath
), MAXPGPATH
- strlen(retpath
),
366 "/%s%s", target
, EXE
);
368 if (validate_exec(retpath
) != 0)
371 snprintf(cmd
, sizeof(cmd
), "\"%s\" -V", retpath
);
373 if (!pipe_read_line(cmd
, line
, sizeof(line
)))
376 if (strcmp(line
, versionstr
) != 0)
384 * Execute a command in a pipe and read the first line from it.
387 pipe_read_line(char *cmd
, char *line
, int maxsize
)
394 if ((pgver
= popen(cmd
, "r")) == NULL
)
396 perror("popen failure");
401 if (fgets(line
, maxsize
, pgver
) == NULL
)
404 fprintf(stderr
, "no data was returned by command \"%s\"\n", cmd
);
406 perror("fgets failure");
407 pclose(pgver
); /* no error checking */
411 if (pclose_check(pgver
))
419 * pclose() plus useful error reporting
422 pclose_check(FILE *stream
)
427 exitstatus
= pclose(stream
);
430 return 0; /* all is well */
432 if (exitstatus
== -1)
434 /* pclose() itself failed, and hopefully set errno */
435 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
436 _("%s() failed: %m"), "pclose");
440 reason
= wait_result_to_str(exitstatus
);
441 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
449 * set_pglocale_pgservice
451 * Set application-specific locale and service directory
453 * This function takes the value of argv[0] rather than a full path.
455 * (You may be wondering why this is in exec.c. It requires this module's
456 * services and doesn't introduce any new dependencies, so this seems as
460 set_pglocale_pgservice(const char *argv0
, const char *app
)
462 char path
[MAXPGPATH
];
463 char my_exec_path
[MAXPGPATH
];
465 /* don't set LC_ALL in the backend */
466 if (strcmp(app
, PG_TEXTDOMAIN("postgres")) != 0)
468 setlocale(LC_ALL
, "");
471 * One could make a case for reproducing here PostmasterMain()'s test
472 * for whether the process is multithreaded. Unlike the postmaster,
473 * no frontend program calls sigprocmask() or otherwise provides for
474 * mutual exclusion between signal handlers. While frontends using
475 * fork(), if multithreaded, are formally exposed to undefined
476 * behavior, we have not witnessed a concrete bug. Therefore,
477 * complaining about multithreading here may be mere pedantry.
481 if (find_my_exec(argv0
, my_exec_path
) < 0)
485 get_locale_path(my_exec_path
, path
);
486 bindtextdomain(app
, path
);
488 /* set for libpq to use, but don't override existing setting */
489 setenv("PGLOCALEDIR", path
, 0);
492 if (getenv("PGSYSCONFDIR") == NULL
)
494 get_etc_path(my_exec_path
, path
);
495 /* set for libpq to use */
496 setenv("PGSYSCONFDIR", path
, 0);
502 * For the benefit of PostgreSQL developers testing EXEC_BACKEND on Unix
503 * systems (code paths normally exercised only on Windows), provide a way to
504 * disable address space layout randomization, if we know how on this platform.
505 * Otherwise, backends may fail to attach to shared memory at the fixed address
506 * chosen by the postmaster. (See also the macOS-specific hack in
510 pg_disable_aslr(void)
512 #if defined(HAVE_SYS_PERSONALITY_H)
513 return personality(ADDR_NO_RANDOMIZE
);
514 #elif defined(HAVE_SYS_PROCCTL_H) && defined(PROC_ASLR_FORCE_DISABLE)
515 int data
= PROC_ASLR_FORCE_DISABLE
;
517 return procctl(P_PID
, 0, PROC_ASLR_CTL
, &data
);
528 * AddUserToTokenDacl(HANDLE hToken)
530 * This function adds the current user account to the restricted
531 * token used when we create a restricted process.
533 * This is required because of some security changes in Windows
534 * that appeared in patches to XP/2K3 and in Vista/2008.
536 * On these machines, the Administrator account is not included in
537 * the default DACL - you just get Administrators + System. For
538 * regular users you get User + System. Because we strip Administrators
539 * when we create the restricted token, we are left with only System
540 * in the DACL which leads to access denied errors for later CreatePipe()
541 * and CreateProcess() calls when running as Administrator.
543 * This function fixes this problem by modifying the DACL of the
544 * token the process will use, and explicitly re-adding the current
545 * user account. This is still secure because the Administrator account
546 * inherits its privileges from the Administrators group - it doesn't
547 * have any of its own.
550 AddUserToTokenDacl(HANDLE hToken
)
553 ACL_SIZE_INFORMATION asi
;
554 ACCESS_ALLOWED_ACE
*pace
;
557 DWORD dwTokenInfoLength
= 0;
559 PTOKEN_USER pTokenUser
= NULL
;
560 TOKEN_DEFAULT_DACL tddNew
;
561 TOKEN_DEFAULT_DACL
*ptdd
= NULL
;
562 TOKEN_INFORMATION_CLASS tic
= TokenDefaultDacl
;
565 /* Figure out the buffer size for the DACL info */
566 if (!GetTokenInformation(hToken
, tic
, (LPVOID
) NULL
, dwTokenInfoLength
, &dwSize
))
568 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER
)
570 ptdd
= (TOKEN_DEFAULT_DACL
*) LocalAlloc(LPTR
, dwSize
);
573 log_error(errcode(ERRCODE_OUT_OF_MEMORY
),
578 if (!GetTokenInformation(hToken
, tic
, (LPVOID
) ptdd
, dwSize
, &dwSize
))
580 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
581 "could not get token information: error code %lu",
588 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
589 "could not get token information buffer size: error code %lu",
595 /* Get the ACL info */
596 if (!GetAclInformation(ptdd
->DefaultDacl
, (LPVOID
) &asi
,
597 (DWORD
) sizeof(ACL_SIZE_INFORMATION
),
600 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
601 "could not get ACL information: error code %lu",
606 /* Get the current user SID */
607 if (!GetTokenUser(hToken
, &pTokenUser
))
608 goto cleanup
; /* callee printed a message */
610 /* Figure out the size of the new ACL */
611 dwNewAclSize
= asi
.AclBytesInUse
+ sizeof(ACCESS_ALLOWED_ACE
) +
612 GetLengthSid(pTokenUser
->User
.Sid
) - sizeof(DWORD
);
614 /* Allocate the ACL buffer & initialize it */
615 pacl
= (PACL
) LocalAlloc(LPTR
, dwNewAclSize
);
618 log_error(errcode(ERRCODE_OUT_OF_MEMORY
),
623 if (!InitializeAcl(pacl
, dwNewAclSize
, ACL_REVISION
))
625 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
626 "could not initialize ACL: error code %lu", GetLastError());
630 /* Loop through the existing ACEs, and build the new ACL */
631 for (i
= 0; i
< (int) asi
.AceCount
; i
++)
633 if (!GetAce(ptdd
->DefaultDacl
, i
, (LPVOID
*) &pace
))
635 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
636 "could not get ACE: error code %lu", GetLastError());
640 if (!AddAce(pacl
, ACL_REVISION
, MAXDWORD
, pace
, ((PACE_HEADER
) pace
)->AceSize
))
642 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
643 "could not add ACE: error code %lu", GetLastError());
648 /* Add the new ACE for the current user */
649 if (!AddAccessAllowedAceEx(pacl
, ACL_REVISION
, OBJECT_INHERIT_ACE
, GENERIC_ALL
, pTokenUser
->User
.Sid
))
651 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
652 "could not add access allowed ACE: error code %lu",
657 /* Set the new DACL in the token */
658 tddNew
.DefaultDacl
= pacl
;
660 if (!SetTokenInformation(hToken
, tic
, (LPVOID
) &tddNew
, dwNewAclSize
))
662 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
663 "could not set token information: error code %lu",
672 LocalFree((HLOCAL
) pTokenUser
);
675 LocalFree((HLOCAL
) pacl
);
678 LocalFree((HLOCAL
) ptdd
);
684 * GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser)
686 * Get the users token information from a process token.
688 * The caller of this function is responsible for calling LocalFree() on the
689 * returned TOKEN_USER memory.
692 GetTokenUser(HANDLE hToken
, PTOKEN_USER
*ppTokenUser
)
698 if (!GetTokenInformation(hToken
,
704 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER
)
706 *ppTokenUser
= (PTOKEN_USER
) LocalAlloc(LPTR
, dwLength
);
708 if (*ppTokenUser
== NULL
)
710 log_error(errcode(ERRCODE_OUT_OF_MEMORY
),
717 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
718 "could not get token information buffer size: error code %lu",
724 if (!GetTokenInformation(hToken
,
730 LocalFree(*ppTokenUser
);
733 log_error(errcode(ERRCODE_SYSTEM_ERROR
),
734 "could not get token information: error code %lu",
739 /* Memory in *ppTokenUser is LocalFree():d by the caller */