VirtualBox

Changeset 29716 in vbox for trunk/src/VBox/Runtime/r3


Ignore:
Timestamp:
May 21, 2010 8:31:19 AM (15 years ago)
Author:
vboxsync
Message:

IPRT/process-win: More generic way for starting processes from a service (no NT4 yet).

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Runtime/r3/win/process-win.cpp

    r29289 r29716  
    3333#include <Userenv.h>
    3434#include <Windows.h>
     35#include <tlhelp32.h>
    3536#include <process.h>
    3637#include <errno.h>
     
    245246                                 STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo, uint32_t fFlags)
    246247{
    247     HANDLE hToken = INVALID_HANDLE_VALUE;
     248    int rc = VINF_SUCCESS;
    248249    BOOL fRc = FALSE;
    249 #ifndef IPRT_TARGET_NT4
     250    DWORD dwErr = NO_ERROR;
     251
    250252    /*
    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).
    296255     */
    297256    if (!(fFlags & RTPROC_FLAGS_SERVICE))
    298     { 
    299         /*
    300          * The following rights are needed in order to use LogonUserW and
    301          * CreateProcessAsUserW, so the local policy has to be modified to:
    302          *  - SE_TCB_NAME = Act as part of the operating system
    303          *  - SE_ASSIGNPRIMARYTOKEN_NAME = Create/replace a token object
    304          *  - SE_INCREASE_QUOTA_NAME
    305          *
    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_NT4
    319         HANDLE hDuplicatedToken = INVALID_HANDLE_VALUE;
    320         HANDLE hProcessToken = INVALID_HANDLE_VALUE;
    321 
    322         /*
    323          * If used by a service, open this service process and adjust
    324          * privileges by enabling the SE_TCB_NAME right to act as part
    325          * 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 (   fRc
    334                 && 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_SUCCESS
    345             || 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 0
    349             /*
    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             else
    360                 dwErr = GetLastError();
    361 #endif
    362         }
    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. Otherwise
    367           *       we will get error 1314 (priviledge not held) as a response. */
    368 
    369         /* Hopefully having a valid token now! */
    370         if (   fRc
    371 #ifdef IPRT_TARGET_NT4
    372             && hToken           != INVALID_HANDLE_VALUE
    373 #else
    374             && hDuplicatedToken != INVALID_HANDLE_VALUE
    375 #endif /* IPRT_TARGET_NT4 */
    376             )
    377         {   
    378             fRc = CreateProcessAsUserW(
    379 #ifdef IPRT_TARGET_NT4
    380                                        hToken,
    381 #else
    382                                        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_NT4
    397             CloseHandle(hDuplicatedToken);
    398 #endif /* !IPRT_TARGET_NT4 */
    399         }
    400         CloseHandle(hToken);
    401 #ifndef IPRT_TARGET_NT4
    402         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     else
    411         dwErr = GetLastError();
    412 
    413 #ifndef IPRT_TARGET_NT4
    414     /*
    415      * If we don't hold enough priviledges to spawn a new process with
    416      * different credentials we have to use CreateProcessWithLogonW here.  This
    417      * 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)
    421257    {
    422258        RTLDRMOD hAdvAPI32;
     
    424260        if (RT_SUCCESS(rc))
    425261        {
    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             */
    427267            PFNCREATEPROCESSWITHLOGON pfnCreateProcessWithLogonW;
    428268            rc = RTLdrGetSymbol(hAdvAPI32, "CreateProcessWithLogonW", (void**)&pfnCreateProcessWithLogonW);
     
    440280                                                 pStartupInfo,
    441281                                                 pProcInfo);
    442                 if (fRc)
    443                     dwErr = NO_ERROR;
    444                 else
     282                if (!fRc)
    445283                    dwErr = GetLastError();
    446284            }
     
    448286        }
    449287    }
    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    }
    451520
    452521    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    }
    454545    else
    455546        rc = VINF_SUCCESS;
     
    586677                PROCESS_INFORMATION ProcInfo;
    587678                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))
    589687                {
    590688                    if (CreateProcessW(pwszExec,
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette