Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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

+219-27
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)