Skip to content

Commit a25cd81

Browse files
committed
Enable pg_ctl to give up admin privileges when starting the server under
Windows (if newer than NT4, else works same as before). Magnus
1 parent eb6d127 commit a25cd81

File tree

1 file changed

+219
-27
lines changed

1 file changed

+219
-27
lines changed

src/bin/pg_ctl/pg_ctl.c

Lines changed: 219 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@
44
*
55
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
66
*
7-
* $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.65 2006/02/07 11:36:36 petere Exp $
7+
* $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.66 2006/02/10 22:00:59 tgl Exp $
88
*
99
*-------------------------------------------------------------------------
1010
*/
1111

12+
#ifdef WIN32
13+
/*
14+
* Need this to get defines for restricted tokens and jobs. And it
15+
* has to be set before any header from the Win32 API is loaded.
16+
*/
17+
#define _WIN32_WINNT 0x0500
18+
#endif
19+
1220
#include "postgres_fe.h"
1321
#include "libpq-fe.h"
1422

@@ -111,6 +119,7 @@ static void pgwin32_SetServiceStatus(DWORD);
111119
static void WINAPI pgwin32_ServiceHandler(DWORD);
112120
static void WINAPI pgwin32_ServiceMain(DWORD, LPTSTR *);
113121
static void pgwin32_doRunAsService(void);
122+
static int CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo);
114123
#endif
115124
static pgpid_t get_pgpid(void);
116125
static char **readfile(const char *path);
@@ -325,42 +334,46 @@ readfile(const char *path)
325334
static int
326335
start_postmaster(void)
327336
{
337+
char cmd[MAXPGPATH];
338+
#ifndef WIN32
328339
/*
329340
* Since there might be quotes to handle here, it is easier simply to pass
330341
* everything to a shell to process them.
331342
*/
332-
char cmd[MAXPGPATH];
333-
334-
/*
335-
* Win32 needs START /B rather than "&".
336-
*
337-
* Win32 has a problem with START and quoted executable names. You must
338-
* add a "" as the title at the beginning so you can quote the executable
339-
* name: http://www.winnetmag.com/Article/ArticleID/14589/14589.html
340-
* http://dev.remotenetworktechnology.com/cmd/cmdfaq.htm
341-
*/
342343
if (log_file != NULL)
343-
#ifndef WIN32 /* Cygwin doesn't have START */
344344
snprintf(cmd, MAXPGPATH, "%s\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1 &%s",
345345
SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts,
346346
DEVNULL, log_file, SYSTEMQUOTE);
347-
#else
348-
snprintf(cmd, MAXPGPATH, "%sSTART /B \"\" \"%s\" %s%s < \"%s\" >> \"%s\" 2>&1%s",
349-
SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts,
350-
DEVNULL, log_file, SYSTEMQUOTE);
351-
#endif
352-
else
353-
#ifndef WIN32 /* Cygwin doesn't have START */
347+
else
354348
snprintf(cmd, MAXPGPATH, "%s\"%s\" %s%s < \"%s\" 2>&1 &%s",
355349
SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts,
356350
DEVNULL, SYSTEMQUOTE);
357-
#else
358-
snprintf(cmd, MAXPGPATH, "%sSTART /B \"\" \"%s\" %s%s < \"%s\" 2>&1%s",
351+
352+
return system(cmd);
353+
354+
#else /* WIN32 */
355+
/*
356+
* On win32 we don't use system(). So we don't need to use &
357+
* (which would be START /B on win32). However, we still call the shell
358+
* (CMD.EXE) with it to handle redirection etc.
359+
*/
360+
PROCESS_INFORMATION pi;
361+
362+
if (log_file != NULL)
363+
snprintf(cmd, MAXPGPATH, "CMD /C %s\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1%s",
364+
SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts,
365+
DEVNULL, log_file, SYSTEMQUOTE);
366+
else
367+
snprintf(cmd, MAXPGPATH, "CMD /C %s\"%s\" %s%s < \"%s\" 2>&1%s",
359368
SYSTEMQUOTE, postgres_path, pgdata_opt, post_opts,
360369
DEVNULL, SYSTEMQUOTE);
361-
#endif
362370

363-
return system(cmd);
371+
if (!CreateRestrictedProcess(cmd, &pi))
372+
return GetLastError();
373+
CloseHandle(pi.hProcess);
374+
CloseHandle(pi.hThread);
375+
return 0;
376+
#endif /* WIN32 */
364377
}
365378

366379

@@ -1063,7 +1076,6 @@ pgwin32_ServiceHandler(DWORD request)
10631076
static void WINAPI
10641077
pgwin32_ServiceMain(DWORD argc, LPTSTR * argv)
10651078
{
1066-
STARTUPINFO si;
10671079
PROCESS_INFORMATION pi;
10681080
DWORD ret;
10691081

@@ -1077,8 +1089,6 @@ pgwin32_ServiceMain(DWORD argc, LPTSTR * argv)
10771089
status.dwCurrentState = SERVICE_START_PENDING;
10781090

10791091
memset(&pi, 0, sizeof(pi));
1080-
memset(&si, 0, sizeof(si));
1081-
si.cb = sizeof(si);
10821092

10831093
/* Register the control request handler */
10841094
if ((hStatus = RegisterServiceCtrlHandler(register_servicename, pgwin32_ServiceHandler)) == (SERVICE_STATUS_HANDLE) 0)
@@ -1089,7 +1099,7 @@ pgwin32_ServiceMain(DWORD argc, LPTSTR * argv)
10891099

10901100
/* Start the postmaster */
10911101
pgwin32_SetServiceStatus(SERVICE_START_PENDING);
1092-
if (!CreateProcess(NULL, pgwin32_CommandLine(false), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
1102+
if (!CreateRestrictedProcess(pgwin32_CommandLine(false), &pi))
10931103
{
10941104
pgwin32_SetServiceStatus(SERVICE_STOPPED);
10951105
return;
@@ -1141,6 +1151,188 @@ pgwin32_doRunAsService(void)
11411151
exit(1);
11421152
}
11431153
}
1154+
1155+
1156+
/*
1157+
* Mingw headers are incomplete, and so are the libraries. So we have to load
1158+
* a whole lot of API functions dynamically. Since we have to do this anyway,
1159+
* also load the couple of functions that *do* exist in minwg headers but not
1160+
* on NT4. That way, we don't break on NT4.
1161+
*/
1162+
typedef WINAPI BOOL (*__CreateRestrictedToken)(HANDLE, DWORD, DWORD, PSID_AND_ATTRIBUTES, DWORD, PLUID_AND_ATTRIBUTES, DWORD, PSID_AND_ATTRIBUTES, PHANDLE);
1163+
typedef WINAPI BOOL (*__IsProcessInJob)(HANDLE, HANDLE, PBOOL);
1164+
typedef WINAPI HANDLE (*__CreateJobObject)(LPSECURITY_ATTRIBUTES, LPCTSTR);
1165+
typedef WINAPI BOOL (*__SetInformationJobObject)(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD);
1166+
typedef WINAPI BOOL (*__AssignProcessToJobObject)(HANDLE, HANDLE);
1167+
typedef WINAPI BOOL (*__QueryInformationJobObject)(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD, LPDWORD);
1168+
1169+
/* Windows API define missing from MingW headers */
1170+
#define DISABLE_MAX_PRIVILEGE 0x1
1171+
1172+
/*
1173+
* Create a restricted token, a job object sandbox, and execute the specified
1174+
* process with it.
1175+
*
1176+
* Returns 0 on success, non-zero on failure, same as CreateProcess().
1177+
*
1178+
* On NT4, or any other system not containing the required functions, will
1179+
* launch the process under the current token without doing any modifications.
1180+
*
1181+
* NOTE! Job object will only work when running as a service, because it's
1182+
* automatically destroyed when pg_ctl exits.
1183+
*/
1184+
static int
1185+
CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo)
1186+
{
1187+
int r;
1188+
BOOL b;
1189+
STARTUPINFO si;
1190+
HANDLE origToken;
1191+
HANDLE restrictedToken;
1192+
SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
1193+
SID_AND_ATTRIBUTES dropSids[2];
1194+
1195+
/* Functions loaded dynamically */
1196+
__CreateRestrictedToken _CreateRestrictedToken = NULL;
1197+
__IsProcessInJob _IsProcessInJob = NULL;
1198+
__CreateJobObject _CreateJobObject = NULL;
1199+
__SetInformationJobObject _SetInformationJobObject = NULL;
1200+
__AssignProcessToJobObject _AssignProcessToJobObject = NULL;
1201+
__QueryInformationJobObject _QueryInformationJobObject = NULL;
1202+
HANDLE Kernel32Handle;
1203+
HANDLE Advapi32Handle;
1204+
1205+
ZeroMemory(&si, sizeof(si));
1206+
si.cb = sizeof(si);
1207+
1208+
Advapi32Handle = LoadLibrary("ADVAPI32.DLL");
1209+
if (Advapi32Handle != NULL)
1210+
{
1211+
_CreateRestrictedToken = (__CreateRestrictedToken) GetProcAddress(Advapi32Handle, "CreateRestrictedToken");
1212+
}
1213+
1214+
if (_CreateRestrictedToken == NULL)
1215+
{
1216+
/* NT4 doesn't have CreateRestrictedToken, so just call ordinary CreateProcess */
1217+
write_stderr("WARNING: Unable to create restricted tokens on this platform\n");
1218+
if (Advapi32Handle != NULL)
1219+
FreeLibrary(Advapi32Handle);
1220+
return CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, processInfo);
1221+
}
1222+
1223+
/* Open the current token to use as a base for the restricted one */
1224+
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &origToken))
1225+
{
1226+
write_stderr("Failed to open process token: %lu\n", GetLastError());
1227+
return 0;
1228+
}
1229+
1230+
/* Allocate list of SIDs to remove */
1231+
ZeroMemory(&dropSids, sizeof(dropSids));
1232+
if (!AllocateAndInitializeSid(&NtAuthority, 2,
1233+
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0,0,0,0,0,
1234+
0, &dropSids[0].Sid) ||
1235+
!AllocateAndInitializeSid(&NtAuthority, 2,
1236+
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_POWER_USERS, 0,0,0,0,0,
1237+
0, &dropSids[1].Sid))
1238+
{
1239+
write_stderr("Failed to allocate SIDs: %lu\n", GetLastError());
1240+
return 0;
1241+
}
1242+
1243+
b = _CreateRestrictedToken(origToken,
1244+
DISABLE_MAX_PRIVILEGE,
1245+
sizeof(dropSids)/sizeof(dropSids[0]),
1246+
dropSids,
1247+
0, NULL,
1248+
0, NULL,
1249+
&restrictedToken);
1250+
1251+
FreeSid(dropSids[1].Sid);
1252+
FreeSid(dropSids[0].Sid);
1253+
CloseHandle(origToken);
1254+
FreeLibrary(Advapi32Handle);
1255+
1256+
if (!b)
1257+
{
1258+
write_stderr("Failed to create restricted token: %lu\n", GetLastError());
1259+
return 0;
1260+
}
1261+
1262+
r = CreateProcessAsUser(restrictedToken, NULL, cmd, NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, &si, processInfo);
1263+
1264+
Kernel32Handle = LoadLibrary("KERNEL32.DLL");
1265+
if (Kernel32Handle != NULL)
1266+
{
1267+
_IsProcessInJob = (__IsProcessInJob) GetProcAddress(Kernel32Handle, "IsProcessInJob");
1268+
_CreateJobObject = (__CreateJobObject) GetProcAddress(Kernel32Handle, "CreateJobObjectA");
1269+
_SetInformationJobObject = (__SetInformationJobObject) GetProcAddress(Kernel32Handle, "SetInformationJobObject");
1270+
_AssignProcessToJobObject = (__AssignProcessToJobObject) GetProcAddress(Kernel32Handle, "AssignProcessToJobObject");
1271+
_QueryInformationJobObject = (__QueryInformationJobObject) GetProcAddress(Kernel32Handle, "QueryInformationJobObject");
1272+
}
1273+
1274+
/* Verify that we found all functions */
1275+
if (_IsProcessInJob == NULL || _CreateJobObject == NULL || _SetInformationJobObject == NULL || _AssignProcessToJobObject == NULL || _QueryInformationJobObject == NULL)
1276+
{
1277+
write_stderr("WARNING: Unable to locate all job object functions in system API!\n");
1278+
}
1279+
else
1280+
{
1281+
BOOL inJob;
1282+
if (_IsProcessInJob(processInfo->hProcess, NULL, &inJob))
1283+
{
1284+
if (!inJob)
1285+
{
1286+
/* Job objects are working, and the new process isn't in one, so we can create one safely.
1287+
If any problems show up when setting it, we're going to ignore them. */
1288+
HANDLE job;
1289+
char jobname[128];
1290+
1291+
sprintf(jobname,"PostgreSQL_%lu", processInfo->dwProcessId);
1292+
1293+
job = _CreateJobObject(NULL, jobname);
1294+
if (job)
1295+
{
1296+
JOBOBJECT_BASIC_LIMIT_INFORMATION basicLimit;
1297+
JOBOBJECT_BASIC_UI_RESTRICTIONS uiRestrictions;
1298+
JOBOBJECT_SECURITY_LIMIT_INFORMATION securityLimit;
1299+
1300+
ZeroMemory(&basicLimit, sizeof(basicLimit));
1301+
ZeroMemory(&uiRestrictions, sizeof(uiRestrictions));
1302+
ZeroMemory(&securityLimit, sizeof(securityLimit));
1303+
1304+
basicLimit.LimitFlags = JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION | JOB_OBJECT_LIMIT_PRIORITY_CLASS;
1305+
basicLimit.PriorityClass = NORMAL_PRIORITY_CLASS;
1306+
_SetInformationJobObject(job, JobObjectBasicLimitInformation, &basicLimit, sizeof(basicLimit));
1307+
1308+
uiRestrictions.UIRestrictionsClass = JOB_OBJECT_UILIMIT_DESKTOP | JOB_OBJECT_UILIMIT_DISPLAYSETTINGS |
1309+
JOB_OBJECT_UILIMIT_EXITWINDOWS | JOB_OBJECT_UILIMIT_HANDLES | JOB_OBJECT_UILIMIT_READCLIPBOARD |
1310+
JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | JOB_OBJECT_UILIMIT_WRITECLIPBOARD;
1311+
_SetInformationJobObject(job, JobObjectBasicUIRestrictions, &uiRestrictions, sizeof(uiRestrictions));
1312+
1313+
securityLimit.SecurityLimitFlags = JOB_OBJECT_SECURITY_NO_ADMIN | JOB_OBJECT_SECURITY_ONLY_TOKEN;
1314+
securityLimit.JobToken = restrictedToken;
1315+
_SetInformationJobObject(job, JobObjectSecurityLimitInformation, &securityLimit, sizeof(securityLimit));
1316+
1317+
_AssignProcessToJobObject(job, processInfo->hProcess);
1318+
}
1319+
}
1320+
}
1321+
}
1322+
1323+
CloseHandle(restrictedToken);
1324+
1325+
ResumeThread(processInfo->hThread);
1326+
1327+
FreeLibrary(Kernel32Handle);
1328+
1329+
/*
1330+
* We intentionally don't close the job object handle, because we want the
1331+
* object to live on until pg_ctl shuts down.
1332+
*/
1333+
return r;
1334+
}
1335+
11441336
#endif
11451337

11461338
static void

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy