OSDN Git Service

Enable pg_ctl to give up admin privileges when starting the server under
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 10 Feb 2006 22:00:59 +0000 (22:00 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 10 Feb 2006 22:00:59 +0000 (22:00 +0000)
Windows (if newer than NT4, else works same as before).

Magnus

src/bin/pg_ctl/pg_ctl.c

index 402db9f..8fb71ad 100644 (file)
@@ -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