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);
111119static void WINAPI pgwin32_ServiceHandler (DWORD );
112120static void WINAPI pgwin32_ServiceMain (DWORD , LPTSTR * );
113121static void pgwin32_doRunAsService (void );
122+ static int CreateRestrictedProcess (char * cmd , PROCESS_INFORMATION * processInfo );
114123#endif
115124static pgpid_t get_pgpid (void );
116125static char * * readfile (const char * path );
@@ -325,42 +334,46 @@ readfile(const char *path)
325334static int
326335start_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)
10631076static void WINAPI
10641077pgwin32_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
11461338static void
0 commit comments