From a25cd81007e827684343a53a80e8bc90f585ca8e Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 10 Feb 2006 22:00:59 +0000 Subject: [PATCH] Enable pg_ctl to give up admin privileges when starting the server under Windows (if newer than NT4, else works same as before). Magnus --- src/bin/pg_ctl/pg_ctl.c | 246 +++++++++++++++++++++++++++++++++++----- 1 file changed, 219 insertions(+), 27 deletions(-) diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c index 402db9fc34..8fb71ad859 100644 --- a/src/bin/pg_ctl/pg_ctl.c +++ b/src/bin/pg_ctl/pg_ctl.c @@ -4,11 +4,19 @@ * * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.65 2006/02/07 11:36:36 petere Exp $ + * $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.66 2006/02/10 22:00:59 tgl Exp $ * *------------------------------------------------------------------------- */ +#ifdef WIN32 +/* + * Need this to get defines for restricted tokens and jobs. And it + * has to be set before any header from the Win32 API is loaded. + */ +#define _WIN32_WINNT 0x0500 +#endif + #include "postgres_fe.h" #include "libpq-fe.h" @@ -111,6 +119,7 @@ static void pgwin32_SetServiceStatus(DWORD); static void WINAPI pgwin32_ServiceHandler(DWORD); static void WINAPI pgwin32_ServiceMain(DWORD, LPTSTR *); static void pgwin32_doRunAsService(void); +static int CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo); #endif static pgpid_t get_pgpid(void); static char **readfile(const char *path); @@ -325,42 +334,46 @@ readfile(const char *path) static int start_postmaster(void) { + char cmd[MAXPGPATH]; +#ifndef WIN32 /* * Since there might be quotes to handle here, it is easier simply to pass * everything to a shell to process them. */ - char cmd[MAXPGPATH]; - - /* - * Win32 needs START /B rather than "&". - * - * Win32 has a problem with START and quoted executable names. You must - * add a "" as the title at the beginning so you can quote the executable - * name: http://www.winnetmag.com/Article/ArticleID/14589/14589.html - * http://dev.remotenetworktechnology.com/cmd/cmdfaq.htm - */ if (log_file != NULL) -#ifndef WIN32 /* Cygwin doesn't have START */ snprintf(cmd, MAXPGPATH, "%s\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1 &%s", SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, DEVNULL, log_file, SYSTEMQUOTE); -#else - snprintf(cmd, MAXPGPATH, "%sSTART /B \"\" \"%s\" %s%s < \"%s\" >> \"%s\" 2>&1%s", - SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, - DEVNULL, log_file, SYSTEMQUOTE); -#endif - else -#ifndef WIN32 /* Cygwin doesn't have START */ + else snprintf(cmd, MAXPGPATH, "%s\"%s\" %s%s < \"%s\" 2>&1 &%s", SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, DEVNULL, SYSTEMQUOTE); -#else - snprintf(cmd, MAXPGPATH, "%sSTART /B \"\" \"%s\" %s%s < \"%s\" 2>&1%s", + + return system(cmd); + +#else /* WIN32 */ + /* + * On win32 we don't use system(). So we don't need to use & + * (which would be START /B on win32). However, we still call the shell + * (CMD.EXE) with it to handle redirection etc. + */ + PROCESS_INFORMATION pi; + + if (log_file != NULL) + snprintf(cmd, MAXPGPATH, "CMD /C %s\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1%s", + SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, + DEVNULL, log_file, SYSTEMQUOTE); + else + snprintf(cmd, MAXPGPATH, "CMD /C %s\"%s\" %s%s < \"%s\" 2>&1%s", SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts, DEVNULL, SYSTEMQUOTE); -#endif - return system(cmd); + if (!CreateRestrictedProcess(cmd, &pi)) + return GetLastError(); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return 0; +#endif /* WIN32 */ } @@ -1063,7 +1076,6 @@ pgwin32_ServiceHandler(DWORD request) static void WINAPI pgwin32_ServiceMain(DWORD argc, LPTSTR * argv) { - STARTUPINFO si; PROCESS_INFORMATION pi; DWORD ret; @@ -1077,8 +1089,6 @@ pgwin32_ServiceMain(DWORD argc, LPTSTR * argv) status.dwCurrentState = SERVICE_START_PENDING; memset(&pi, 0, sizeof(pi)); - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); /* Register the control request handler */ if ((hStatus = RegisterServiceCtrlHandler(register_servicename, pgwin32_ServiceHandler)) == (SERVICE_STATUS_HANDLE) 0) @@ -1089,7 +1099,7 @@ pgwin32_ServiceMain(DWORD argc, LPTSTR * argv) /* Start the postmaster */ pgwin32_SetServiceStatus(SERVICE_START_PENDING); - if (!CreateProcess(NULL, pgwin32_CommandLine(false), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) + if (!CreateRestrictedProcess(pgwin32_CommandLine(false), &pi)) { pgwin32_SetServiceStatus(SERVICE_STOPPED); return; @@ -1141,6 +1151,188 @@ pgwin32_doRunAsService(void) exit(1); } } + + +/* + * Mingw headers are incomplete, and so are the libraries. So we have to load + * a whole lot of API functions dynamically. Since we have to do this anyway, + * also load the couple of functions that *do* exist in minwg headers but not + * on NT4. That way, we don't break on NT4. + */ +typedef WINAPI BOOL (*__CreateRestrictedToken)(HANDLE, DWORD, DWORD, PSID_AND_ATTRIBUTES, DWORD, PLUID_AND_ATTRIBUTES, DWORD, PSID_AND_ATTRIBUTES, PHANDLE); +typedef WINAPI BOOL (*__IsProcessInJob)(HANDLE, HANDLE, PBOOL); +typedef WINAPI HANDLE (*__CreateJobObject)(LPSECURITY_ATTRIBUTES, LPCTSTR); +typedef WINAPI BOOL (*__SetInformationJobObject)(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD); +typedef WINAPI BOOL (*__AssignProcessToJobObject)(HANDLE, HANDLE); +typedef WINAPI BOOL (*__QueryInformationJobObject)(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD, LPDWORD); + +/* Windows API define missing from MingW headers */ +#define DISABLE_MAX_PRIVILEGE 0x1 + +/* + * Create a restricted token, a job object sandbox, and execute the specified + * process with it. + * + * Returns 0 on success, non-zero on failure, same as CreateProcess(). + * + * On NT4, or any other system not containing the required functions, will + * launch the process under the current token without doing any modifications. + * + * NOTE! Job object will only work when running as a service, because it's + * automatically destroyed when pg_ctl exits. + */ +static int +CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo) +{ + int r; + BOOL b; + STARTUPINFO si; + HANDLE origToken; + HANDLE restrictedToken; + SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY}; + SID_AND_ATTRIBUTES dropSids[2]; + + /* Functions loaded dynamically */ + __CreateRestrictedToken _CreateRestrictedToken = NULL; + __IsProcessInJob _IsProcessInJob = NULL; + __CreateJobObject _CreateJobObject = NULL; + __SetInformationJobObject _SetInformationJobObject = NULL; + __AssignProcessToJobObject _AssignProcessToJobObject = NULL; + __QueryInformationJobObject _QueryInformationJobObject = NULL; + HANDLE Kernel32Handle; + 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) + { + /* NT4 doesn't have CreateRestrictedToken, so just call ordinary CreateProcess */ + write_stderr("WARNING: Unable to create restricted tokens on this platform\n"); + if (Advapi32Handle != NULL) + FreeLibrary(Advapi32Handle); + return CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, processInfo); + } + + /* Open the current token to use as a base for the restricted one */ + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &origToken)) + { + write_stderr("Failed to open process token: %lu\n", 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)) + { + write_stderr("Failed to allocate SIDs: %lu\n", 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) + { + write_stderr("Failed to create restricted token: %lu\n", GetLastError()); + return 0; + } + + r = CreateProcessAsUser(restrictedToken, NULL, cmd, NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, &si, processInfo); + + Kernel32Handle = LoadLibrary("KERNEL32.DLL"); + if (Kernel32Handle != NULL) + { + _IsProcessInJob = (__IsProcessInJob) GetProcAddress(Kernel32Handle, "IsProcessInJob"); + _CreateJobObject = (__CreateJobObject) GetProcAddress(Kernel32Handle, "CreateJobObjectA"); + _SetInformationJobObject = (__SetInformationJobObject) GetProcAddress(Kernel32Handle, "SetInformationJobObject"); + _AssignProcessToJobObject = (__AssignProcessToJobObject) GetProcAddress(Kernel32Handle, "AssignProcessToJobObject"); + _QueryInformationJobObject = (__QueryInformationJobObject) GetProcAddress(Kernel32Handle, "QueryInformationJobObject"); + } + + /* Verify that we found all functions */ + if (_IsProcessInJob == NULL || _CreateJobObject == NULL || _SetInformationJobObject == NULL || _AssignProcessToJobObject == NULL || _QueryInformationJobObject == NULL) + { + write_stderr("WARNING: Unable to locate all job object functions in system API!\n"); + } + else + { + BOOL inJob; + if (_IsProcessInJob(processInfo->hProcess, NULL, &inJob)) + { + if (!inJob) + { + /* Job objects are working, and the new process isn't in one, so we can create one safely. + If any problems show up when setting it, we're going to ignore them. */ + HANDLE job; + char jobname[128]; + + sprintf(jobname,"PostgreSQL_%lu", processInfo->dwProcessId); + + job = _CreateJobObject(NULL, jobname); + if (job) + { + JOBOBJECT_BASIC_LIMIT_INFORMATION basicLimit; + JOBOBJECT_BASIC_UI_RESTRICTIONS uiRestrictions; + JOBOBJECT_SECURITY_LIMIT_INFORMATION securityLimit; + + ZeroMemory(&basicLimit, sizeof(basicLimit)); + ZeroMemory(&uiRestrictions, sizeof(uiRestrictions)); + ZeroMemory(&securityLimit, sizeof(securityLimit)); + + basicLimit.LimitFlags = JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION | JOB_OBJECT_LIMIT_PRIORITY_CLASS; + basicLimit.PriorityClass = NORMAL_PRIORITY_CLASS; + _SetInformationJobObject(job, JobObjectBasicLimitInformation, &basicLimit, sizeof(basicLimit)); + + uiRestrictions.UIRestrictionsClass = JOB_OBJECT_UILIMIT_DESKTOP | JOB_OBJECT_UILIMIT_DISPLAYSETTINGS | + JOB_OBJECT_UILIMIT_EXITWINDOWS | JOB_OBJECT_UILIMIT_HANDLES | JOB_OBJECT_UILIMIT_READCLIPBOARD | + JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | JOB_OBJECT_UILIMIT_WRITECLIPBOARD; + _SetInformationJobObject(job, JobObjectBasicUIRestrictions, &uiRestrictions, sizeof(uiRestrictions)); + + securityLimit.SecurityLimitFlags = JOB_OBJECT_SECURITY_NO_ADMIN | JOB_OBJECT_SECURITY_ONLY_TOKEN; + securityLimit.JobToken = restrictedToken; + _SetInformationJobObject(job, JobObjectSecurityLimitInformation, &securityLimit, sizeof(securityLimit)); + + _AssignProcessToJobObject(job, processInfo->hProcess); + } + } + } + } + + CloseHandle(restrictedToken); + + ResumeThread(processInfo->hThread); + + FreeLibrary(Kernel32Handle); + + /* + * We intentionally don't close the job object handle, because we want the + * object to live on until pg_ctl shuts down. + */ + return r; +} + #endif static void -- GitLab