Changeset 29716 in vbox for trunk/src/VBox/Runtime/r3
- Timestamp:
- May 21, 2010 8:31:19 AM (15 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Runtime/r3/win/process-win.cpp
r29289 r29716 33 33 #include <Userenv.h> 34 34 #include <Windows.h> 35 #include <tlhelp32.h> 35 36 #include <process.h> 36 37 #include <errno.h> … … 245 246 STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo, uint32_t fFlags) 246 247 { 247 HANDLE hToken = INVALID_HANDLE_VALUE;248 int rc = VINF_SUCCESS; 248 249 BOOL fRc = FALSE; 249 #ifndef IPRT_TARGET_NT4 250 DWORD dwErr = NO_ERROR; 251 250 252 /* 251 * Get the session ID. 252 */ 253 DWORD dwSessionID = 0; /* On W2K the session ID is always 0 (does not have fast user switching). */ 254 RTLDRMOD hKernel32; 255 int rc = RTLdrLoad("Kernel32.dll", &hKernel32); 256 if (RT_SUCCESS(rc)) 257 { 258 PFNWTSGETACTIVECONSOLESESSIONID pfnWTSGetActiveConsoleSessionId; 259 rc = RTLdrGetSymbol(hKernel32, "WTSGetActiveConsoleSessionId", (void **)&pfnWTSGetActiveConsoleSessionId); 260 if (RT_SUCCESS(rc)) 261 { 262 /* 263 * Console session means the session which the physical keyboard and mouse 264 * is connected to. Due to FUS (fast user switching) starting with Windows XP 265 * this can be a different session than 0. 266 */ 267 dwSessionID = pfnWTSGetActiveConsoleSessionId(); /* Get active console session ID. */ 268 } 269 RTLdrClose(hKernel32); 270 } 271 272 if (fFlags & RTPROC_FLAGS_SERVICE) 273 { 274 /* 275 * Get the current user token. 276 */ 277 RTLDRMOD hWtsAPI32; 278 rc = RTLdrLoad("Wtsapi32.dll", &hWtsAPI32); 279 if (RT_SUCCESS(rc)) 280 { 281 /* Note that WTSQueryUserToken() only is available on Windows XP and up! */ 282 PFNWTSQUERYUSERTOKEN pfnWTSQueryUserToken; 283 rc = RTLdrGetSymbol(hWtsAPI32, "WTSQueryUserToken", (void **)&pfnWTSQueryUserToken); 284 if (RT_SUCCESS(rc)) 285 fRc = pfnWTSQueryUserToken(dwSessionID, &hToken); 286 RTLdrClose(hWtsAPI32); 287 } 288 } 289 #endif /* !IPRT_TARGET_NT4 */ 290 291 DWORD dwErr = NO_ERROR; 292 /* 293 * If not run by a service, use the normal LogonUserW function in order 294 * to run the child process with different credentials using the returned 295 * primary token. 253 * If we run as a service CreateProcessWithLogon will fail, 254 * so don't even try it (because of Local System context). 296 255 */ 297 256 if (!(fFlags & RTPROC_FLAGS_SERVICE)) 298 {299 /*300 * The following rights are needed in order to use LogonUserW and301 * CreateProcessAsUserW, so the local policy has to be modified to:302 * - SE_TCB_NAME = Act as part of the operating system303 * - SE_ASSIGNPRIMARYTOKEN_NAME = Create/replace a token object304 * - SE_INCREASE_QUOTA_NAME305 *306 * We may fail here with ERROR_PRIVILEGE_NOT_HELD.307 */308 fRc = LogonUserW(pwszUser,309 NULL, /* lpDomain */310 pwszPassword,311 LOGON32_LOGON_INTERACTIVE,312 LOGON32_PROVIDER_DEFAULT,313 &hToken);314 }315 316 if (fRc)317 {318 #ifndef IPRT_TARGET_NT4319 HANDLE hDuplicatedToken = INVALID_HANDLE_VALUE;320 HANDLE hProcessToken = INVALID_HANDLE_VALUE;321 322 /*323 * If used by a service, open this service process and adjust324 * privileges by enabling the SE_TCB_NAME right to act as part325 * of the operating system.326 */327 if (fFlags & RTPROC_FLAGS_SERVICE)328 {329 TOKEN_PRIVILEGES privToken, privTokenOld;330 DWORD dwTokenSize;331 332 fRc = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken);333 if ( fRc334 && LookupPrivilegeValue(NULL, SE_TCB_NAME, &privToken.Privileges[0].Luid))335 {336 privToken.PrivilegeCount = 1;337 privToken.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;338 fRc = AdjustTokenPrivileges(hProcessToken, FALSE, &privToken, sizeof(privToken), &privTokenOld, &dwTokenSize);339 if (!fRc)340 dwErr = GetLastError();341 }342 }343 344 if ( dwErr == ERROR_SUCCESS345 || dwErr == ERROR_NOT_ALL_ASSIGNED) /* The AdjustTokenPrivileges() did not succeed, but this isn't fatal. */346 {347 fRc = DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hDuplicatedToken);348 #if 0349 /*350 * Assign the current session ID (> 0 on Windows Vista and up) to the primary token we just duplicated.351 ** @todo This does not work yet, needs more investigation! */352 */353 if (fRc)354 {355 fRc = SetTokenInformation(hDuplicatedToken, TokenSessionId, &dwSessionID, sizeof(DWORD));356 if (!fRc)357 dwErr = GetLastError();358 }359 else360 dwErr = GetLastError();361 #endif362 }363 364 #endif /* !IPRT_TARGET_NT4 */365 366 /** @todo On NT4 we need to enable the SeTcbPrivilege to act as part of the operating system. Otherwise367 * we will get error 1314 (priviledge not held) as a response. */368 369 /* Hopefully having a valid token now! */370 if ( fRc371 #ifdef IPRT_TARGET_NT4372 && hToken != INVALID_HANDLE_VALUE373 #else374 && hDuplicatedToken != INVALID_HANDLE_VALUE375 #endif /* IPRT_TARGET_NT4 */376 )377 {378 fRc = CreateProcessAsUserW(379 #ifdef IPRT_TARGET_NT4380 hToken,381 #else382 hDuplicatedToken,383 #endif /* !IPRT_TARGET_NT4 */384 pwszExec,385 pwszCmdLine,386 NULL, /* pProcessAttributes */387 NULL, /* pThreadAttributes */388 TRUE, /* fInheritHandles */389 dwCreationFlags,390 pwszzBlock,391 NULL, /* pCurrentDirectory */392 pStartupInfo,393 pProcInfo);394 if (!fRc)395 dwErr = GetLastError();396 #ifndef IPRT_TARGET_NT4397 CloseHandle(hDuplicatedToken);398 #endif /* !IPRT_TARGET_NT4 */399 }400 CloseHandle(hToken);401 #ifndef IPRT_TARGET_NT4402 if (fFlags & RTPROC_FLAGS_SERVICE)403 {404 /** @todo Drop SE_TCB_NAME priviledge before closing the process handle! */405 if (hProcessToken != INVALID_HANDLE_VALUE)406 CloseHandle(hProcessToken);407 }408 #endif /* !IPRT_TARGET_NT4 */409 }410 else411 dwErr = GetLastError();412 413 #ifndef IPRT_TARGET_NT4414 /*415 * If we don't hold enough priviledges to spawn a new process with416 * different credentials we have to use CreateProcessWithLogonW here. This417 * API is W2K+ and uses a system service for spawning the process.418 */419 /** @todo Use fFlags to either use this feature or just fail. */420 if (dwErr == ERROR_PRIVILEGE_NOT_HELD)421 257 { 422 258 RTLDRMOD hAdvAPI32; … … 424 260 if (RT_SUCCESS(rc)) 425 261 { 426 /* This may fail on too old (NT4) platforms. */ 262 /* 263 * This may fail on too old (NT4) platforms or if the calling process 264 * is running on a SYSTEM account (like a service, ERROR_ACCESS_DENIED) on newer 265 * platforms (however, this works on W2K!). 266 */ 427 267 PFNCREATEPROCESSWITHLOGON pfnCreateProcessWithLogonW; 428 268 rc = RTLdrGetSymbol(hAdvAPI32, "CreateProcessWithLogonW", (void**)&pfnCreateProcessWithLogonW); … … 440 280 pStartupInfo, 441 281 pProcInfo); 442 if (fRc) 443 dwErr = NO_ERROR; 444 else 282 if (!fRc) 445 283 dwErr = GetLastError(); 446 284 } … … 448 286 } 449 287 } 450 #endif /* !IPRT_TARGET_NT4 */ 288 289 /* 290 * Get the session ID. 291 */ 292 DWORD dwSessionID = 0; /* On W2K the session ID is always 0 (does not have fast user switching). */ 293 RTLDRMOD hKernel32; 294 rc = RTLdrLoad("Kernel32.dll", &hKernel32); 295 if (RT_SUCCESS(rc)) 296 { 297 PFNWTSGETACTIVECONSOLESESSIONID pfnWTSGetActiveConsoleSessionId; 298 rc = RTLdrGetSymbol(hKernel32, "WTSGetActiveConsoleSessionId", (void **)&pfnWTSGetActiveConsoleSessionId); 299 if (RT_SUCCESS(rc)) 300 { 301 /* 302 * Console session means the session which the physical keyboard and mouse 303 * is connected to. Due to FUS (fast user switching) starting with Windows XP 304 * this can be a different session than 0. 305 */ 306 dwSessionID = pfnWTSGetActiveConsoleSessionId(); /* Get active console session ID. */ 307 } 308 RTLdrClose(hKernel32); 309 } 310 311 /* 312 * Did the API call above fail because we're running on a too old OS (NT4) or 313 * we're running as a Windows service? 314 */ 315 if ( RT_FAILURE(rc) 316 || (fFlags & RTPROC_FLAGS_SERVICE)) 317 { 318 /* 319 * First we have to validate the credentials. If they're valid we can 320 * proceed. This is important when running as a service which then looks up 321 * the current session the user is logged on in order to start the actual 322 * process there. 323 * 324 * The following rights are needed in order to use LogonUserW and 325 * CreateProcessAsUserW, so the local policy has to be modified to: 326 * - SE_TCB_NAME = Act as part of the operating system 327 * - SE_ASSIGNPRIMARYTOKEN_NAME = Create/replace a token object 328 * - SE_INCREASE_QUOTA_NAME 329 * 330 * We may fail here with ERROR_PRIVILEGE_NOT_HELD. 331 * 332 ** @todo Deal with http://support.microsoft.com/kb/245683 for NULL domain names 333 * on NT4 (ignored here by now). Passing FQDNs should work! 334 */ 335 PHANDLE phToken = NULL; 336 HANDLE hTokenLogon = INVALID_HANDLE_VALUE; 337 fRc = LogonUserW(pwszUser, 338 NULL, 339 pwszPassword, 340 LOGON32_LOGON_INTERACTIVE, 341 LOGON32_PROVIDER_DEFAULT, 342 &hTokenLogon); 343 344 BOOL bFound = FALSE; 345 HANDLE hTokenVBoxTray = INVALID_HANDLE_VALUE; 346 if (fRc) 347 { 348 if (fFlags & RTPROC_FLAGS_SERVICE) 349 { 350 DWORD cbName = 0; /* Must be zero to query size! */ 351 DWORD cbDomain = 0; 352 SID_NAME_USE sidNameUse = SidTypeUser; 353 fRc = LookupAccountNameW(NULL, 354 pwszUser, 355 NULL, 356 &cbName, 357 NULL, 358 &cbDomain, 359 &sidNameUse); 360 if (!fRc) 361 dwErr = GetLastError(); 362 if ( !fRc 363 && dwErr == ERROR_INSUFFICIENT_BUFFER 364 && cbName > 0) 365 { 366 dwErr = NO_ERROR; 367 368 PSID pSID = (PSID)RTMemAlloc(cbName * sizeof(wchar_t)); 369 AssertPtrReturn(pSID, VERR_NO_MEMORY); 370 371 /** @todo No way to allocate a PRTUTF16 directly? */ 372 char *pszDomainUtf8 = NULL; 373 PRTUTF16 pwszDomain = NULL; 374 if (cbDomain > 0) 375 { 376 pszDomainUtf8 = RTStrAlloc(cbDomain); 377 rc = RTStrToUtf16(pszDomainUtf8, &pwszDomain); 378 AssertRCReturn(rc, rc); 379 RTStrFree(pszDomainUtf8); 380 } 381 382 /* Note: Also supports FQDNs! */ 383 if ( LookupAccountNameW(NULL, /* lpSystemName */ 384 pwszUser, 385 pSID, 386 &cbName, 387 pwszDomain, 388 &cbDomain, 389 &sidNameUse) 390 && IsValidSid(pSID)) 391 { 392 HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 393 if (hSnap != INVALID_HANDLE_VALUE) 394 { 395 PROCESSENTRY32 procEntry; 396 procEntry.dwSize = sizeof(PROCESSENTRY32); 397 if (Process32First(hSnap, &procEntry)) 398 { 399 DWORD dwVBoxTrayPID = 0; 400 DWORD dwCurSession = 0; 401 do 402 { 403 if ( _stricmp(procEntry.szExeFile, "VBoxTray.exe") == 0 404 && ProcessIdToSessionId(procEntry.th32ProcessID, &dwCurSession)) 405 { 406 HANDLE hProc = OpenProcess(MAXIMUM_ALLOWED, TRUE, procEntry.th32ProcessID); 407 if (hProc != NULL) 408 { 409 HANDLE hTokenProc; 410 fRc = OpenProcessToken(hProc, 411 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | 412 TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE, 413 &hTokenProc); 414 if (fRc) 415 { 416 DWORD dwSize = 0; 417 fRc = GetTokenInformation(hTokenProc, TokenUser, NULL, 0, &dwSize); 418 if (!fRc) 419 dwErr = GetLastError(); 420 if ( !fRc 421 && dwErr == ERROR_INSUFFICIENT_BUFFER 422 && dwSize > 0) 423 { 424 PTOKEN_USER pTokenUser = (PTOKEN_USER)RTMemAlloc(dwSize); 425 AssertPtrBreak(pTokenUser); 426 RT_ZERO(*pTokenUser); 427 if ( GetTokenInformation(hTokenProc, 428 TokenUser, 429 (LPVOID)pTokenUser, 430 dwSize, 431 &dwSize) 432 && IsValidSid(pTokenUser->User.Sid) 433 && EqualSid(pTokenUser->User.Sid, pSID)) 434 { 435 if (DuplicateTokenEx(hTokenProc, MAXIMUM_ALLOWED, 436 NULL, SecurityIdentification, TokenPrimary, &hTokenVBoxTray)) 437 { 438 /* 439 * So we found a VBoxTray instance which belongs to the user we want to 440 * to run our new process under. This duplicated token will be used for 441 * the actual CreateProcessAsUserW() call then. 442 */ 443 bFound = TRUE; 444 } 445 else 446 dwErr = GetLastError(); 447 } 448 else 449 dwErr = GetLastError(); 450 RTMemFree(pTokenUser); 451 } 452 else 453 dwErr = GetLastError(); 454 CloseHandle(hTokenProc); 455 } 456 else 457 dwErr = GetLastError(); 458 CloseHandle(hProc); 459 } 460 else 461 dwErr = GetLastError(); 462 } 463 } while (Process32Next(hSnap, &procEntry) && !bFound); 464 } 465 else /* Process32First */ 466 dwErr = GetLastError(); 467 CloseHandle(hSnap); 468 } 469 else /* hSnap =! INVALID_HANDLE_VALUE */ 470 dwErr = GetLastError(); 471 } 472 else 473 dwErr = GetLastError(); /* LookupAccountNameW() failed. */ 474 RTMemFree(pSID); 475 if (pwszDomain != NULL) 476 RTUtf16Free(pwszDomain); 477 } 478 } 479 else /* !RTPROC_FLAGS_SERVICE */ 480 { 481 /* Nothing to do here right now. */ 482 } 483 484 /* 485 * If we didn't find a matching VBoxTray, just use the token we got 486 * above from LogonUserW(). This enables us to at least run processes with 487 * desktop interaction without UI. 488 */ 489 phToken = bFound ? &hTokenVBoxTray : &hTokenLogon; 490 491 /* 492 * Useful KB articles: 493 * http://support.microsoft.com/kb/165194/ 494 * http://support.microsoft.com/kb/184802/ 495 * http://support.microsoft.com/kb/327618/ 496 */ 497 fRc = CreateProcessAsUserW(*phToken, 498 pwszExec, 499 pwszCmdLine, 500 NULL, /* pProcessAttributes */ 501 NULL, /* pThreadAttributes */ 502 TRUE, /* fInheritHandles */ 503 dwCreationFlags, 504 pwszzBlock, 505 NULL, /* pCurrentDirectory */ 506 pStartupInfo, 507 pProcInfo); 508 if (fRc) 509 dwErr = NO_ERROR; 510 else 511 dwErr = GetLastError(); /* CreateProcessAsUserW() failed. */ 512 513 if (hTokenVBoxTray != INVALID_HANDLE_VALUE) 514 CloseHandle(hTokenVBoxTray); 515 CloseHandle(hTokenLogon); 516 } 517 else 518 dwErr = GetLastError(); /* LogonUserW() failed. */ 519 } 451 520 452 521 if (dwErr != NO_ERROR) 453 rc = RTErrConvertFromWin32(dwErr); 522 { 523 /* 524 * Map some important or much used Windows error codes 525 * to our error codes. 526 */ 527 switch (dwErr) 528 { 529 case ERROR_NOACCESS: 530 case ERROR_PRIVILEGE_NOT_HELD: 531 rc = VERR_PERMISSION_DENIED; 532 break; 533 534 case ERROR_PASSWORD_EXPIRED: 535 case ERROR_ACCOUNT_RESTRICTION: 536 rc = VERR_LOGON_FAILURE; 537 break; 538 539 default: 540 /* Could trigger a debug assertion! */ 541 rc = RTErrConvertFromWin32(dwErr); 542 break; 543 } 544 } 454 545 else 455 546 rc = VINF_SUCCESS; … … 586 677 PROCESS_INFORMATION ProcInfo; 587 678 RT_ZERO(ProcInfo); 588 if (pszAsUser == NULL) 679 680 /* 681 * Only use the normal CreateProcess stuff if we have no user name 682 * and we are not running from a (Windows) service. Otherwise use 683 * the more advanced version in rtProcCreateAsUserHlp(). 684 */ 685 if ( pszAsUser == NULL 686 && !(fFlags & RTPROC_FLAGS_SERVICE)) 589 687 { 590 688 if (CreateProcessW(pwszExec,
Note:
See TracChangeset
for help on using the changeset viewer.