From 9485663134c1c3d131bcdef8745d69871b193baf Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Mon, 30 Mar 2015 17:17:39 -0400 Subject: [PATCH] Run pg_upgrade and pg_resetxlog with restricted token on Windows As with initdb these programs need to run with a restricted token, and if they don't pg_upgrade will fail when run as a user with Adminstrator privileges. Backpatch to all live branches. On the development branch the code is reorganized so that the restricted token code is now in a single location. On the stable bramches a less invasive change is made by simply copying the relevant code to pg_upgrade.c and pg_resetxlog.c. Patches and bug report from Muhammad Asif Naeem, reviewed by Michael Paquier, slightly edited by me. --- contrib/pg_upgrade/pg_upgrade.c | 166 +++++++++++++++++++++++++ src/bin/pg_resetxlog/pg_resetxlog.c | 180 ++++++++++++++++++++++++++++ 2 files changed, 346 insertions(+) diff --git a/contrib/pg_upgrade/pg_upgrade.c b/contrib/pg_upgrade/pg_upgrade.c index a106511b4d..89e427503a 100644 --- a/contrib/pg_upgrade/pg_upgrade.c +++ b/contrib/pg_upgrade/pg_upgrade.c @@ -50,6 +50,11 @@ static void copy_clog_xlog_xid(void); static void set_frozenxids(void); static void setup(char *argv0, bool live_check); static void cleanup(void); +static void get_restricted_token(const char *progname); + +#ifdef WIN32 +static int CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, const char *progname); +#endif ClusterInfo old_cluster, new_cluster; @@ -67,6 +72,9 @@ char *output_files[] = { NULL }; +#ifdef WIN32 +static char *restrict_env; +#endif int main(int argc, char **argv) @@ -78,6 +86,8 @@ main(int argc, char **argv) parseCommandLine(argc, argv); + get_restricted_token(os_info.progname); + adjust_data_dir(&old_cluster); adjust_data_dir(&new_cluster); @@ -170,6 +180,162 @@ main(int argc, char **argv) return 0; } +#ifdef WIN32 +typedef BOOL(WINAPI * __CreateRestrictedToken) (HANDLE, DWORD, DWORD, PSID_AND_ATTRIBUTES, DWORD, PLUID_AND_ATTRIBUTES, DWORD, PSID_AND_ATTRIBUTES, PHANDLE); + +/* Windows API define missing from some versions of MingW headers */ +#ifndef DISABLE_MAX_PRIVILEGE +#define DISABLE_MAX_PRIVILEGE 0x1 +#endif + +/* +* Create a restricted token and execute the specified process with it. +* +* Returns 0 on failure, non-zero on success, same as CreateProcess(). +* +* On NT4, or any other system not containing the required functions, will +* NOT execute anything. +*/ +static int +CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, const char *progname) +{ + BOOL b; + STARTUPINFO si; + HANDLE origToken; + HANDLE restrictedToken; + SID_IDENTIFIER_AUTHORITY NtAuthority = { SECURITY_NT_AUTHORITY }; + SID_AND_ATTRIBUTES dropSids[2]; + __CreateRestrictedToken _CreateRestrictedToken = NULL; + HANDLE Advapi32Handle; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + Advapi32Handle = LoadLibrary("ADVAPI32.DLL"); + if (Advapi32Handle != NULL) + { + _CreateRestrictedToken = (__CreateRestrictedToken)GetProcAddress(Advapi32Handle, "CreateRestrictedToken"); + } + + if (_CreateRestrictedToken == NULL) + { + fprintf(stderr, _("%s: WARNING: cannot create restricted tokens on this platform\n"), progname); + if (Advapi32Handle != NULL) + FreeLibrary(Advapi32Handle); + return 0; + } + + /* Open the current token to use as a base for the restricted one */ + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &origToken)) + { + fprintf(stderr, _("%s: could not open process token: error code %lu\n"), progname, GetLastError()); + return 0; + } + + /* Allocate list of SIDs to remove */ + ZeroMemory(&dropSids, sizeof(dropSids)); + if (!AllocateAndInitializeSid(&NtAuthority, 2, + SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, + 0, &dropSids[0].Sid) || + !AllocateAndInitializeSid(&NtAuthority, 2, + SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_POWER_USERS, 0, 0, 0, 0, 0, + 0, &dropSids[1].Sid)) + { + fprintf(stderr, _("%s: could not to allocate SIDs: error code %lu\n"), progname, GetLastError()); + return 0; + } + + b = _CreateRestrictedToken(origToken, + DISABLE_MAX_PRIVILEGE, + sizeof(dropSids) / sizeof(dropSids[0]), + dropSids, + 0, NULL, + 0, NULL, + &restrictedToken); + + FreeSid(dropSids[1].Sid); + FreeSid(dropSids[0].Sid); + CloseHandle(origToken); + FreeLibrary(Advapi32Handle); + + if (!b) + { + fprintf(stderr, _("%s: could not create restricted token: error code %lu\n"), progname, GetLastError()); + return 0; + } + +#ifndef __CYGWIN__ + AddUserToTokenDacl(restrictedToken); +#endif + + if (!CreateProcessAsUser(restrictedToken, + NULL, + cmd, + NULL, + NULL, + TRUE, + CREATE_SUSPENDED, + NULL, + NULL, + &si, + processInfo)) + + { + fprintf(stderr, _("%s: could not start process for command \"%s\": error code %lu\n"), progname, cmd, GetLastError()); + return 0; + } + + return ResumeThread(processInfo->hThread); +} +#endif + +void +get_restricted_token(const char *progname) +{ +#ifdef WIN32 + + /* + * Before we execute another program, make sure that we are running with a + * restricted token. If not, re-execute ourselves with one. + */ + + if ((restrict_env = getenv("PG_RESTRICT_EXEC")) == NULL + || strcmp(restrict_env, "1") != 0) + { + PROCESS_INFORMATION pi; + char *cmdline; + + ZeroMemory(&pi, sizeof(pi)); + + cmdline = pg_strdup(GetCommandLine()); + + putenv("PG_RESTRICT_EXEC=1"); + + if (!CreateRestrictedProcess(cmdline, &pi, progname)) + { + fprintf(stderr, _("%s: could not re-execute with restricted token: error code %lu\n"), progname, GetLastError()); + } + else + { + /* + * Successfully re-execed. Now wait for child process to capture + * exitcode. + */ + DWORD x; + + CloseHandle(pi.hThread); + WaitForSingleObject(pi.hProcess, INFINITE); + + if (!GetExitCodeProcess(pi.hProcess, &x)) + { + fprintf(stderr, _("%s: could not get exit code from subprocess: error code %lu\n"), progname, GetLastError()); + exit(1); + } + exit(x); + } + } +#endif +} static void setup(char *argv0, bool live_check) diff --git a/src/bin/pg_resetxlog/pg_resetxlog.c b/src/bin/pg_resetxlog/pg_resetxlog.c index 92d98fc963..24de764f5c 100644 --- a/src/bin/pg_resetxlog/pg_resetxlog.c +++ b/src/bin/pg_resetxlog/pg_resetxlog.c @@ -65,6 +65,10 @@ static uint32 newXlogId, static bool guessed = false; /* T if we had to guess at any values */ static const char *progname; +#ifdef WIN32 +static char *restrict_env; +#endif + static bool ReadControlFile(void); static void GuessControlValues(void); static void PrintControlValues(bool guessed); @@ -74,7 +78,12 @@ static void KillExistingXLOG(void); static void KillExistingArchiveStatus(void); static void WriteEmptyXLOG(void); static void usage(void); +static void get_restricted_token(const char *progname); +#ifdef WIN32 +static int CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, const char *progname); +static char *pg_strdup(const char *str); +#endif int main(int argc, char *argv[]) @@ -256,6 +265,7 @@ main(int argc, char *argv[]) } #endif + get_restricted_token(progname); DataDir = argv[optind]; if (chdir(DataDir) < 0) @@ -380,6 +390,20 @@ main(int argc, char *argv[]) return 0; } +#ifdef WIN32 +static char * +pg_strdup(const char *str) +{ + char *result = strdup(str); + + if (!result) + { + fprintf(stderr, _("out of memory\n")); + exit(1); + } + return result; +} +#endif /* * Try to read the existing pg_control file. @@ -1022,6 +1046,162 @@ WriteEmptyXLOG(void) close(fd); } +#ifdef WIN32 +typedef BOOL(WINAPI * __CreateRestrictedToken) (HANDLE, DWORD, DWORD, PSID_AND_ATTRIBUTES, DWORD, PLUID_AND_ATTRIBUTES, DWORD, PSID_AND_ATTRIBUTES, PHANDLE); + +/* Windows API define missing from some versions of MingW headers */ +#ifndef DISABLE_MAX_PRIVILEGE +#define DISABLE_MAX_PRIVILEGE 0x1 +#endif + +/* +* Create a restricted token and execute the specified process with it. +* +* Returns 0 on failure, non-zero on success, same as CreateProcess(). +* +* On NT4, or any other system not containing the required functions, will +* NOT execute anything. +*/ +static int +CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, const char *progname) +{ + BOOL b; + STARTUPINFO si; + HANDLE origToken; + HANDLE restrictedToken; + SID_IDENTIFIER_AUTHORITY NtAuthority = { SECURITY_NT_AUTHORITY }; + SID_AND_ATTRIBUTES dropSids[2]; + __CreateRestrictedToken _CreateRestrictedToken = NULL; + HANDLE Advapi32Handle; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + Advapi32Handle = LoadLibrary("ADVAPI32.DLL"); + if (Advapi32Handle != NULL) + { + _CreateRestrictedToken = (__CreateRestrictedToken)GetProcAddress(Advapi32Handle, "CreateRestrictedToken"); + } + + if (_CreateRestrictedToken == NULL) + { + fprintf(stderr, _("%s: WARNING: cannot create restricted tokens on this platform\n"), progname); + if (Advapi32Handle != NULL) + FreeLibrary(Advapi32Handle); + return 0; + } + + /* Open the current token to use as a base for the restricted one */ + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &origToken)) + { + fprintf(stderr, _("%s: could not open process token: error code %lu\n"), progname, GetLastError()); + return 0; + } + + /* Allocate list of SIDs to remove */ + ZeroMemory(&dropSids, sizeof(dropSids)); + if (!AllocateAndInitializeSid(&NtAuthority, 2, + SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, + 0, &dropSids[0].Sid) || + !AllocateAndInitializeSid(&NtAuthority, 2, + SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_POWER_USERS, 0, 0, 0, 0, 0, + 0, &dropSids[1].Sid)) + { + fprintf(stderr, _("%s: could not to allocate SIDs: error code %lu\n"), progname, GetLastError()); + return 0; + } + + b = _CreateRestrictedToken(origToken, + DISABLE_MAX_PRIVILEGE, + sizeof(dropSids) / sizeof(dropSids[0]), + dropSids, + 0, NULL, + 0, NULL, + &restrictedToken); + + FreeSid(dropSids[1].Sid); + FreeSid(dropSids[0].Sid); + CloseHandle(origToken); + FreeLibrary(Advapi32Handle); + + if (!b) + { + fprintf(stderr, _("%s: could not create restricted token: error code %lu\n"), progname, GetLastError()); + return 0; + } + +#ifndef __CYGWIN__ + AddUserToTokenDacl(restrictedToken); +#endif + + if (!CreateProcessAsUser(restrictedToken, + NULL, + cmd, + NULL, + NULL, + TRUE, + CREATE_SUSPENDED, + NULL, + NULL, + &si, + processInfo)) + + { + fprintf(stderr, _("%s: could not start process for command \"%s\": error code %lu\n"), progname, cmd, GetLastError()); + return 0; + } + + return ResumeThread(processInfo->hThread); +} +#endif + +void +get_restricted_token(const char *progname) +{ +#ifdef WIN32 + + /* + * Before we execute another program, make sure that we are running with a + * restricted token. If not, re-execute ourselves with one. + */ + + if ((restrict_env = getenv("PG_RESTRICT_EXEC")) == NULL + || strcmp(restrict_env, "1") != 0) + { + PROCESS_INFORMATION pi; + char *cmdline; + + ZeroMemory(&pi, sizeof(pi)); + + cmdline = pg_strdup(GetCommandLine()); + + putenv("PG_RESTRICT_EXEC=1"); + + if (!CreateRestrictedProcess(cmdline, &pi, progname)) + { + fprintf(stderr, _("%s: could not re-execute with restricted token: error code %lu\n"), progname, GetLastError()); + } + else + { + /* + * Successfully re-execed. Now wait for child process to capture + * exitcode. + */ + DWORD x; + + CloseHandle(pi.hThread); + WaitForSingleObject(pi.hProcess, INFINITE); + + if (!GetExitCodeProcess(pi.hProcess, &x)) + { + fprintf(stderr, _("%s: could not get exit code from subprocess: error code %lu\n"), progname, GetLastError()); + exit(1); + } + exit(x); + } + } +#endif +} static void usage(void) -- 2.40.0