Skip to content
Snippets Groups Projects
Commit a25cd810 authored by Tom Lane's avatar Tom Lane
Browse files

Enable pg_ctl to give up admin privileges when starting the server under

Windows (if newer than NT4, else works same as before).

Magnus
parent eb6d1270
No related branches found
No related tags found
No related merge requests found
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment