4
4
*
5
5
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
6
6
*
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 $
8
8
*
9
9
*-------------------------------------------------------------------------
10
10
*/
11
11
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
+
12
20
#include "postgres_fe.h"
13
21
#include "libpq-fe.h"
14
22
@@ -111,6 +119,7 @@ static void pgwin32_SetServiceStatus(DWORD);
111
119
static void WINAPI pgwin32_ServiceHandler (DWORD );
112
120
static void WINAPI pgwin32_ServiceMain (DWORD , LPTSTR * );
113
121
static void pgwin32_doRunAsService (void );
122
+ static int CreateRestrictedProcess (char * cmd , PROCESS_INFORMATION * processInfo );
114
123
#endif
115
124
static pgpid_t get_pgpid (void );
116
125
static char * * readfile (const char * path );
@@ -325,42 +334,46 @@ readfile(const char *path)
325
334
static int
326
335
start_postmaster (void )
327
336
{
337
+ char cmd [MAXPGPATH ];
338
+ #ifndef WIN32
328
339
/*
329
340
* Since there might be quotes to handle here, it is easier simply to pass
330
341
* everything to a shell to process them.
331
342
*/
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
- */
342
343
if (log_file != NULL )
343
- #ifndef WIN32 /* Cygwin doesn't have START */
344
344
snprintf (cmd , MAXPGPATH , "%s\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1 &%s" ,
345
345
SYSTEMQUOTE , postgres_path , pgdata_opt , post_opts ,
346
346
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
354
348
snprintf (cmd , MAXPGPATH , "%s\"%s\" %s%s < \"%s\" 2>&1 &%s" ,
355
349
SYSTEMQUOTE , postgres_path , pgdata_opt , post_opts ,
356
350
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" ,
359
368
SYSTEMQUOTE , postgres_path , pgdata_opt , post_opts ,
360
369
DEVNULL , SYSTEMQUOTE );
361
- #endif
362
370
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 */
364
377
}
365
378
366
379
@@ -1063,7 +1076,6 @@ pgwin32_ServiceHandler(DWORD request)
1063
1076
static void WINAPI
1064
1077
pgwin32_ServiceMain (DWORD argc , LPTSTR * argv )
1065
1078
{
1066
- STARTUPINFO si ;
1067
1079
PROCESS_INFORMATION pi ;
1068
1080
DWORD ret ;
1069
1081
@@ -1077,8 +1089,6 @@ pgwin32_ServiceMain(DWORD argc, LPTSTR * argv)
1077
1089
status .dwCurrentState = SERVICE_START_PENDING ;
1078
1090
1079
1091
memset (& pi , 0 , sizeof (pi ));
1080
- memset (& si , 0 , sizeof (si ));
1081
- si .cb = sizeof (si );
1082
1092
1083
1093
/* Register the control request handler */
1084
1094
if ((hStatus = RegisterServiceCtrlHandler (register_servicename , pgwin32_ServiceHandler )) == (SERVICE_STATUS_HANDLE ) 0 )
@@ -1089,7 +1099,7 @@ pgwin32_ServiceMain(DWORD argc, LPTSTR * argv)
1089
1099
1090
1100
/* Start the postmaster */
1091
1101
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 ))
1093
1103
{
1094
1104
pgwin32_SetServiceStatus (SERVICE_STOPPED );
1095
1105
return ;
@@ -1141,6 +1151,188 @@ pgwin32_doRunAsService(void)
1141
1151
exit (1 );
1142
1152
}
1143
1153
}
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
+
1144
1336
#endif
1145
1337
1146
1338
static void
0 commit comments