/*+ * * Microsoft Windows * Copyright (C) Microsoft Corporation, 1997 - 1998. * * Name : seclogon.cxx * Author:Jeffrey Richter (v-jeffrr) * * Abstract: * This is the service DLL for Secondary Logon Service * This service supports the CreateProcessWithLogon API implemented * in advapi32.dll * * Revision History: * PraeritG 10/8/97 To integrate this in to services.exe * -*/ #define STRICT // Disable some warnings we don't care about: #pragma warning(disable:4115) // '': named type definition in parentheses #pragma warning(disable:4201) // nonstandard extension used : nameless struct/union #pragma warning(disable:4204) // nonstandard extension used : non-constant aggregate initializer #include #include #include #include #include #define SECURITY_WIN32 #define SECURITY_KERBEROS #include #include #include #include #include #include #include #include #include #include #include #include "seclogon.h" #include #include "stringid.h" #include "dbgdef.h" // // must move to winbase.h soon! #define LOGON_WITH_PROFILE 0x00000001 #define LOGON_NETCREDENTIALS_ONLY 0x00000002 #define MAXIMUM_SECLOGON_PROCESSES MAXIMUM_WAIT_OBJECTS*4 struct SECL_STATE { SERVICE_STATUS serviceStatus; SERVICE_STATUS_HANDLE hServiceStatus; HANDLE hLSA; ULONG hMSVPackage; ULONG hKerbPackage; BOOL fRPCServerActive; LIST_ENTRY JobListHead; } g_state; typedef struct _SECONDARYLOGONINFOW { // First fields should all be quad-word types to avoid alignment errors: LPSTARTUPINFO lpStartupInfo; LPWSTR lpUsername; LPWSTR lpDomain; LPWSTR lpApplicationName; LPWSTR lpCommandLine; LPVOID lpEnvironment; LPCWSTR lpCurrentDirectory; UNICODE_STRING uszPassword; // use UNICODE_STRING for the passwd (easier to work with Rtl(De/En)cryptMemory()) // Next group of fields are double-word types: DWORD dwProcessId; ULONG LogonIdLowPart; LONG LogonIdHighPart; DWORD dwLogonFlags; DWORD dwCreationFlags; DWORD dwSeclogonFlags; // Insert smaller types below: HANDLE hToken; // client access token handle, for CreateProcessWithToken } SECONDARYLOGONINFOW, *PSECONDARYLOGONINFOW; typedef struct _SECONDARYLOGONRETINFO { PROCESS_INFORMATION pi; DWORD dwErrorCode; } SECONDARYLOGONRETINFO, *PSECONDARYLOGONRETINFO; typedef struct _SECONDARYLOGINWATCHINFO { HANDLE hProcess; HANDLE hToken; HANDLE hProfile; LUID LogonId; DWORD dwClientSessionId; PSECONDARYLOGONINFOW psli; } SECONDARYLOGONWATCHINFO, *PSECONDARYLOGONWATCHINFO; // // The SecondaryLogonJob structure contains all of the information needed to cleanup when // // a) a secondary logon job group terminates (non-TS case) // OR b) a secondary logon process terminates (TS case) // typedef struct _SecondaryLogonJob { HANDLE hJob; // The Job to watch for (case 'a') HANDLE hProcess; // The Process to watch for (case 'b') HANDLE hRegisteredProcessTerminated; // Used to deregister wait on hProcess (case 'b') HANDLE hToken; // Used to unload profile on cleanup HANDLE hProfile; // Used to unload profile on cleanup LUID RootLogonId; // The root logon session which this process/job is associated with // BUGBUG: psli no longer has to be preserved until cleanup. However, there's no reason to // destabilze the codebase by fixing it now. PSECONDARYLOGONINFOW psli; // More data to free when the process/job goes away LIST_ENTRY list; // Links this struct to other active jobs BOOL fHeapAllocated; // TRUE if this job was HeapAlloc'd (must be freed on cleanup). } SecondaryLogonJob; #define _JumpCondition(condition, label) \ if (condition) \ { \ goto label; \ } \ else { } #define _JumpConditionWithExpr(condition, label, expr) \ if (condition) \ { \ expr; \ goto label; \ } \ else { } #define ARRAYSIZE(array) ((sizeof(array)) / (sizeof(array[0]))) #define FIELDOFFSET(s,m) ((size_t)(ULONG_PTR)&(((s *)0)->m)) CRITICAL_SECTION csForProcessCount; BOOL g_fIsCsInitialized = FALSE; PSVCHOST_GLOBAL_DATA GlobalData; HANDLE g_hIOCP = NULL; BOOL g_fCleanupThreadActive = FALSE; // // function prototypes // void Free_SECONDARYLOGONINFOW(PSECONDARYLOGONINFOW psli); void FreeGlobalState(); DWORD InitGlobalState(); DWORD MySetServiceStatus(DWORD dwCurrentState, DWORD dwCheckPoint, DWORD dwWaitHint, DWORD dwExitCode); DWORD MySetServiceStopped(DWORD dwExitCode); DWORD SeclStartRpcServer(); DWORD SeclStopRpcServer(); void WINAPI TS_SecondaryLogonCleanupProcess(PVOID pvIgnored, BOOLEAN bIgnored); VOID SecondaryLogonCleanupJob(LPVOID pvJobIndex, BOOL *pfLastJob); BOOL SlpLoadUserProfile(HANDLE hToken, PHANDLE hProfile); DWORD To_SECONDARYLOGONINFOW(PSECL_SLI pSeclSli, PSECONDARYLOGONINFOW *ppsli); DWORD To_SECL_SLRI(SECONDARYLOGONRETINFO *pslri, PSECL_SLRI pSeclSlri); void DbgPrintf( DWORD dwSubSysId, LPCSTR pszFormat , ...) { #if DBG va_list args; CHAR pszBuffer[1024]; HRESULT hr; va_start(args, pszFormat); hr = StringCchVPrintfA(pszBuffer, 1024, pszFormat, args); if (FAILED(hr)) return; va_end(args); OutputDebugStringA(pszBuffer); #else UNREFERENCED_PARAMETER(pszFormat); #endif // #if DBG UNREFERENCED_PARAMETER(dwSubSysId); } BOOL IsSystemProcess( VOID ) { PTOKEN_USER User; HANDLE Token; DWORD RetLen; PSID SystemSid = NULL; SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_NT_AUTHORITY; BYTE Buffer[100]; if(AllocateAndInitializeSid(&SidAuthority,1,SECURITY_LOCAL_SYSTEM_RID, 0,0,0,0,0,0,0,&SystemSid)) { if(OpenThreadToken(GetCurrentThread(), MAXIMUM_ALLOWED, FALSE, &Token)) { if(GetTokenInformation(Token, TokenUser, Buffer, 100, &RetLen)) { User = (PTOKEN_USER)Buffer; CloseHandle(Token); if(EqualSid(User->User.Sid, SystemSid)) { FreeSid(SystemSid); return TRUE; } } else CloseHandle(Token); } FreeSid(SystemSid); } return FALSE; } DWORD SlpGetClientSessionId(HANDLE hProcess, DWORD *pdwSessionId) { DWORD dwResult; DWORD dwReturnLen; HANDLE hToken = NULL; if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { goto OpenProcessTokenError; } if (!GetTokenInformation (hToken, TokenSessionId, pdwSessionId, sizeof(DWORD), &dwReturnLen)) { goto GetTokenInformationError; } dwResult = ERROR_SUCCESS; ErrorReturn: if (NULL != hToken) { CloseHandle(hToken); } return dwResult; SET_DWRESULT(OpenProcessTokenError, GetLastError()); SET_DWRESULT(GetTokenInformationError, GetLastError()); } DWORD SlpGetClientLogonId( HANDLE Process, PLUID LogonId ) { HANDLE Token; TOKEN_STATISTICS TokenStats; DWORD ReturnLength; // // Get handle to the process token. // if(OpenProcessToken(Process, MAXIMUM_ALLOWED, &Token)) { if(GetTokenInformation ( Token, TokenStatistics, (PVOID)&TokenStats, sizeof( TOKEN_STATISTICS ), &ReturnLength )) { *LogonId = TokenStats.AuthenticationId; CloseHandle(Token); return ERROR_SUCCESS; } CloseHandle(Token); } return GetLastError(); } DWORD GetLogonSid(PSID *ppSid) { DWORD dwIndex; DWORD dwResult; DWORD dwReturnLen = 0; DWORD dwSidLen; HANDLE hToken = NULL; PSID *pSid = NULL; TOKEN_GROUPS *pTokenGroups = NULL; // Get the current thread's token if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken)) goto OpenThreadTokenError; // Compute the size of this token's groups: GetTokenInformation(hToken, TokenGroups, NULL, 0, &dwReturnLen); if (0 == dwReturnLen) goto GetTokenInformationSizeError; pTokenGroups = (TOKEN_GROUPS *)HeapAlloc(GetProcessHeap(), 0, dwReturnLen); if (NULL == pTokenGroups) goto MemoryError; // Get this token's groups: if (!GetTokenInformation(hToken, TokenGroups, pTokenGroups, dwReturnLen, &dwReturnLen)) goto GetTokenInformationError; // Find the logon sid in this token's groups: for (dwIndex = 0; dwIndex < pTokenGroups->GroupCount; dwIndex++) { if (SE_GROUP_LOGON_ID & pTokenGroups->Groups[dwIndex].Attributes ) { // we've found it. Copy it to a new sid: dwSidLen = RtlLengthSid(pTokenGroups->Groups[dwIndex].Sid); pSid = (PSID)HeapAlloc(GetProcessHeap(), 0, dwSidLen); if (NULL == pSid) goto MemoryError; RtlCopySid(dwSidLen, pSid, pTokenGroups->Groups[dwIndex].Sid); break; } } // Did we find the logon sid? if (NULL == pSid) goto NoLogonSidError; // No: this token doesn't have one. *ppSid = pSid; pSid = NULL; dwResult = ERROR_SUCCESS; ErrorReturn: if (NULL != hToken) CloseHandle(hToken); if (NULL != pTokenGroups) HeapFree(GetProcessHeap(), 0, pTokenGroups); if (NULL != pSid) HeapFree(GetProcessHeap(), 0, pSid); return dwResult; SET_DWRESULT(GetTokenInformationError, GetLastError()); SET_DWRESULT(GetTokenInformationSizeError, GetLastError()); SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY); SET_DWRESULT(NoLogonSidError, ERROR_ACCESS_DENIED); SET_DWRESULT(OpenThreadTokenError, GetLastError()); } DWORD DecryptString(UNICODE_STRING usz) { DWORD dwResult; NTSTATUS ntStatus; // We want the MaximumLength to be greater than Length because we'll want to add a NULL // termination character to prevent buffer overruns. if (usz.Length >= usz.MaximumLength) goto InvalidParameterError; // We've got the empty string, nothing to decrypt if (0 == usz.Length) goto done; // We should've been passed an encrypted string that's a multiple of the block size if (0 != ((sizeof(WCHAR)*usz.Length) % RTL_ENCRYPT_MEMORY_SIZE)) goto InvalidParameterError; // Attempt to decrypt the password ntStatus = RtlDecryptMemory(usz.Buffer, sizeof(WCHAR)*usz.Length, RTL_ENCRYPT_OPTION_SAME_LOGON); if (!NT_SUCCESS(ntStatus)) goto RtlDecryptMemoryError; done: // Terminate the buffer to prevent overrun attacks (probably already done) // This is OK because we've already checked that MaximumLength is > Length usz.Buffer[usz.Length] = L'\0'; dwResult = ERROR_SUCCESS; ErrorReturn: return dwResult; SET_DWRESULT(InvalidParameterError, ERROR_INVALID_PARAMETER); SET_DWRESULT(RtlDecryptMemoryError, RtlNtStatusToDosError(ntStatus)); } void DestroyAuthInfo(BOOL bUseNTLM, PVOID pvAuthInfo) { UNICODE_STRING *pustrPasswd; if (bUseNTLM) { pustrPasswd = &(((MSV1_0_INTERACTIVE_LOGON *)pvAuthInfo)->Password); } else { pustrPasswd = &(((KERB_INTERACTIVE_LOGON *)pvAuthInfo)->Password); } // this was allocated with HEAP_ZERO_MEMORY, so this check is valid if (NULL != pustrPasswd->Buffer) { // will the compiler optimize this out? SecureZeroMemory(pustrPasswd->Buffer, pustrPasswd->Length); } HeapFree(GetProcessHeap(), 0, pvAuthInfo); } DWORD MakeAuthInfo(BOOL bUseNTLM, LPWSTR pwszUserName, LPWSTR pwszDomainName, LPWSTR pwszPasswd, ULONG *pulAuthPackage, PVOID *ppvAuthInfo, ULONG *pulAuthInfoLength) { DWORD dwResult; HRESULT hr; LPBYTE pbCurrent; LPBYTE pbEnd; PVOID pvAuthInfo = NULL; ULONG cbAuthInfoStrings; ULONG ulAuthInfoLength; ULONG ulAuthPackage; UNICODE_STRING *pustrUserName; UNICODE_STRING *pustrDomainName; UNICODE_STRING *pustrPasswd; if (bUseNTLM) { ulAuthPackage = g_state.hMSVPackage; ulAuthInfoLength = sizeof(MSV1_0_INTERACTIVE_LOGON); } else { ulAuthPackage = g_state.hKerbPackage; ulAuthInfoLength = sizeof(KERB_INTERACTIVE_LOGON); } ulAuthInfoLength += (ULONG)(sizeof(WCHAR) * (wcslen(pwszUserName))); ulAuthInfoLength += (ULONG)(sizeof(WCHAR) * (wcslen(pwszDomainName))); ulAuthInfoLength += (ULONG)(sizeof(WCHAR) * (wcslen(pwszPasswd)+1)); // zero out the allocated memory (checked by DestroyAuthInfo) pvAuthInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ulAuthInfoLength); if (NULL == pvAuthInfo) goto MemoryError; // fill out the auth info structure: if (bUseNTLM) { ((MSV1_0_INTERACTIVE_LOGON *)pvAuthInfo)->MessageType = MsV1_0InteractiveLogon; pustrUserName = &(((MSV1_0_INTERACTIVE_LOGON *)pvAuthInfo)->UserName); pustrDomainName = &(((MSV1_0_INTERACTIVE_LOGON *)pvAuthInfo)->LogonDomainName); pustrPasswd = &(((MSV1_0_INTERACTIVE_LOGON *)pvAuthInfo)->Password); pbCurrent = ((LPBYTE)pvAuthInfo) + sizeof(MSV1_0_INTERACTIVE_LOGON); cbAuthInfoStrings = ulAuthInfoLength - sizeof(MSV1_0_INTERACTIVE_LOGON); } else { ((KERB_INTERACTIVE_LOGON *)pvAuthInfo)->MessageType = KerbInteractiveLogon; pustrUserName = &(((KERB_INTERACTIVE_LOGON *)pvAuthInfo)->UserName); pustrDomainName = &(((KERB_INTERACTIVE_LOGON *)pvAuthInfo)->LogonDomainName); pustrPasswd = &(((KERB_INTERACTIVE_LOGON *)pvAuthInfo)->Password); pbCurrent = ((LPBYTE)pvAuthInfo) + sizeof(KERB_INTERACTIVE_LOGON); cbAuthInfoStrings = ulAuthInfoLength - sizeof(KERB_INTERACTIVE_LOGON); } // Initialize a pointer to the end of our buffer. pbEnd = pbCurrent + cbAuthInfoStrings; // Copy the auth info strings into our allocated buffer hr = StringCbCopy((WCHAR *)pbCurrent, pbEnd-pbCurrent, pwszUserName); if (FAILED(hr)) goto StringCchCopyError; RtlInitUnicodeString(pustrUserName, (LPWSTR)pbCurrent); pbCurrent += sizeof(WCHAR) * (wcslen(pwszUserName)); hr = StringCbCopy((WCHAR *)pbCurrent, pbEnd-pbCurrent, pwszDomainName); if (FAILED(hr)) goto StringCchCopyError; RtlInitUnicodeString(pustrDomainName, (LPWSTR)pbCurrent); pbCurrent += sizeof(WCHAR) * (wcslen(pwszDomainName)); hr = StringCbCopy((WCHAR *)pbCurrent, pbEnd-pbCurrent, pwszPasswd); if (FAILED(hr)) goto StringCchCopyError; RtlInitUnicodeString(pustrPasswd, (LPWSTR)pbCurrent); pbCurrent += sizeof(WCHAR) * (wcslen(pwszPasswd)); *pulAuthPackage = ulAuthPackage; *ppvAuthInfo = pvAuthInfo; pvAuthInfo = NULL; *pulAuthInfoLength = ulAuthInfoLength; dwResult = ERROR_SUCCESS; ErrorReturn: if (NULL != pvAuthInfo) DestroyAuthInfo(bUseNTLM, pvAuthInfo); return dwResult; SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY); SET_DWRESULT(StringCchCopyError, (DWORD)hr); } DWORD LogonUserWrap(LPWSTR pwszUserName, LPWSTR pwszDomainName, UNICODE_STRING uszPassword, DWORD dwLogonType, DWORD dwLogonProvider, PSID pLogonSid, HANDLE *phToken, LPWSTR *ppwszProfilePath) { BOOL bUseNTLM = FALSE; DWORD cTokenGroups; DWORD dwLengthSid; DWORD dwResult; HRESULT hr; KERB_INTERACTIVE_LOGON kerbLogon; LPWSTR pwszProfilePath = NULL; MSV1_0_INTERACTIVE_LOGON msv10Logon; NTSTATUS ntStatus; PSID pLocalSid = NULL; PUNICODE_STRING puszProfilePath = NULL; SID_IDENTIFIER_AUTHORITY LocalSidAuthority = SECURITY_LOCAL_SID_AUTHORITY; // Declare the parameters to LsaLogonUser: LSA_STRING lsastr_OriginName; SECURITY_LOGON_TYPE sltLogonType; ULONG ulAuthPackage; PVOID pvAuthInfo = NULL; ULONG ulAuthInfoLength; TOKEN_GROUPS *pTokenGroups = NULL; TOKEN_SOURCE sourceContext; PVOID pvProfileBuffer = NULL; ULONG ulProfileBufferLength; LUID LogonId; QUOTA_LIMITS quotas; NTSTATUS ntSubStatus; ZeroMemory(&kerbLogon, sizeof(kerbLogon)); ZeroMemory(&msv10Logon, sizeof(msv10Logon)); // Map NULLs to the empty strings so we don't have to deal with them: if (NULL == pwszUserName) pwszUserName = L""; if (NULL == pwszDomainName) pwszDomainName = L""; if (NULL == uszPassword.Buffer) { uszPassword.Buffer = L""; uszPassword.Length = 0; uszPassword.MaximumLength = 1; } // Check if we use NTLM or not: if (NULL == pwszDomainName || L'\0' == pwszDomainName[0]) { if (NULL == wcschr(pwszUserName, L'@')) { bUseNTLM = TRUE; } } RtlInitString(&lsastr_OriginName, "seclogon"); sltLogonType = dwLogonType; // The password is current encrypted. Need to decrypt it before we can use it: dwResult = DecryptString(uszPassword); if (ERROR_SUCCESS != dwResult) goto DecryptStringError; // Get the values of the auth-package dependendent LsaLogonUser parameters. dwResult = MakeAuthInfo(bUseNTLM, pwszUserName, pwszDomainName, uszPassword.Buffer, &ulAuthPackage, &pvAuthInfo, &ulAuthInfoLength); if (ERROR_SUCCESS != dwResult) goto GetAuthInfoError; // We don't need the password anymore. Zero it out: SecureZeroMemory(uszPassword.Buffer, sizeof(WCHAR)*uszPassword.Length); // If the caller specified a logon sid, cram it into a TOKEN_GROUPS structure if (NULL != pLogonSid) { // BUG 522969: If we specify a token groups to LsaLogonUser, it's not going to add the local sid to the token groups. // So, we have to do that ourselves: dwLengthSid = RtlLengthRequiredSid(1); pLocalSid = (PSID)HeapAlloc(GetProcessHeap(), 0, dwLengthSid); if (NULL == pLocalSid) goto MemoryError; RtlInitializeSid(pLocalSid, &LocalSidAuthority, 1); *(RtlSubAuthoritySid(pLocalSid, 0)) = SECURITY_LOCAL_RID; // Initialize a TOKEN_GROUPS structure cTokenGroups = 2; // the local sid, and the logon sid pTokenGroups = (TOKEN_GROUPS *)HeapAlloc(GetProcessHeap(), 0, sizeof(TOKEN_GROUPS) + ((cTokenGroups - ANYSIZE_ARRAY) * sizeof(SID_AND_ATTRIBUTES))); if (NULL == pTokenGroups) goto MemoryError; pTokenGroups->GroupCount = cTokenGroups; pTokenGroups->Groups[0].Sid = pLogonSid; pTokenGroups->Groups[0].Attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_LOGON_ID; pTokenGroups->Groups[1].Sid = pLocalSid; pTokenGroups->Groups[1].Attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT; } strncpy(sourceContext.SourceName, "seclogon ", sizeof(sourceContext.SourceName)); ntStatus = LsaLogonUser (g_state.hLSA, &lsastr_OriginName, sltLogonType, ulAuthPackage, pvAuthInfo, ulAuthInfoLength, pTokenGroups, &sourceContext, &pvProfileBuffer, &ulProfileBufferLength, &LogonId, phToken, "as, &ntSubStatus); if (!NT_SUCCESS(ntStatus)) goto LsaLogonUserError; if (!NT_SUCCESS(ntSubStatus)) { ntStatus = ntSubStatus; goto LsaLogonUserError; } if (NULL != pvProfileBuffer) { puszProfilePath = &(((MSV1_0_INTERACTIVE_PROFILE *)pvProfileBuffer)->ProfilePath); if (NULL != puszProfilePath->Buffer) { pwszProfilePath = (LPWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WCHAR)*(puszProfilePath->Length+1)); if (NULL == pwszProfilePath) goto MemoryError; hr = StringCchCopy(pwszProfilePath, puszProfilePath->Length+1, puszProfilePath->Buffer); if (FAILED(hr)) goto StringCchCopyError; } } *ppwszProfilePath = pwszProfilePath; pwszProfilePath = NULL; dwResult = ERROR_SUCCESS; ErrorReturn: if (NULL != pvAuthInfo) DestroyAuthInfo(bUseNTLM, pvAuthInfo); if (NULL != pLocalSid) HeapFree(GetProcessHeap(), 0, pLocalSid); if (NULL != pTokenGroups) HeapFree(GetProcessHeap(), 0, pTokenGroups); if (NULL != pvProfileBuffer) LsaFreeReturnBuffer(pvProfileBuffer); if (NULL != pwszProfilePath) HeapFree(GetProcessHeap(), 0, pwszProfilePath); return dwResult; SET_DWRESULT(DecryptStringError, dwResult); SET_DWRESULT(GetAuthInfoError, dwResult); SET_DWRESULT(LsaLogonUserError, RtlNtStatusToDosError(ntStatus)); SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY); SET_DWRESULT(StringCchCopyError, (DWORD)hr); UNREFERENCED_PARAMETER(dwLogonProvider); } DWORD WINAPI WaitForNextJobTermination(PVOID pvIgnored) { BOOL fResult; DWORD dwNumberOfBytes; DWORD dwResult; OVERLAPPED *po; ULONG_PTR ulptrCompletionKey; for (;;) { fResult = GetQueuedCompletionStatus(g_hIOCP, &dwNumberOfBytes, &ulptrCompletionKey, &po, INFINITE); if (!fResult) { // We've encountered an error. Shutdown our cleanup thread -- the next runas will queue another one. EnterCriticalSection(&csForProcessCount); g_fCleanupThreadActive = FALSE; LeaveCriticalSection(&csForProcessCount); goto GetQueuedCompletionStatusError; } // When waiting on job objects, the dwNumberOfBytes contains a message ID, indicating // the event which just occured. switch (dwNumberOfBytes) { case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: { BOOL fLastJob; // All of our processes have terminated. Call our cleanup function. SecondaryLogonCleanupJob((LPVOID)ulptrCompletionKey /*job index*/, &fLastJob); if (fLastJob) { // There are no more jobs -- we're done processing notification. goto CommonReturn; } else { // More jobs left to clean up. Keep processing... } } default:; // some message we don't care about. Try again. } } CommonReturn: dwResult = ERROR_SUCCESS; ErrorReturn: return dwResult; SET_DWRESULT(GetQueuedCompletionStatusError, GetLastError()); UNREFERENCED_PARAMETER(pvIgnored); } VOID SecondaryLogonCleanupJob( LPVOID pvslj, BOOL *pfLastJob ) /*++ Routine Description: This routine is a process cleanup handler when one of the secondary logon process goes away. Arguments: dwProcessIndex -- the actual index to the process, the pointer is cast back to dword. THIS IS SAFE IN SUNDOWN. fWaitStatus -- status of the wait done by one of services.exe threads. Return Value: always 0. --*/ { SecondaryLogonJob *pslj = (SecondaryLogonJob *)pvslj; EnterCriticalSection(&csForProcessCount); if (NULL != pslj->hJob) { CloseHandle(pslj->hJob); } if (NULL != pslj->hRegisteredProcessTerminated) { UnregisterWaitEx(pslj->hRegisteredProcessTerminated, 0 /*don't wait*/); } if (NULL != pslj->hProcess) { CloseHandle(pslj->hProcess); } if (NULL != pslj->hProfile) { UnloadUserProfile(pslj->hToken, pslj->hProfile); } if (NULL != pslj->hToken) { CloseHandle(pslj->hToken); } if (NULL != pslj->psli) { Free_SECONDARYLOGONINFOW(pslj->psli); } // Unlink us from the list of jobs RemoveEntryList(&pslj->list); // Free the list element, if it was allocated. if (pslj->fHeapAllocated) { HeapFree(GetProcessHeap(), 0, pslj); } // If the list is empty, we don't need the cleanup thread anymore. *pfLastJob = IsListEmpty(&g_state.JobListHead); // If it's the last job, the cleanup thread terminates: g_fCleanupThreadActive = !(*pfLastJob) && g_fCleanupThreadActive; // Update the service status to reflect whether there is a runas'd process alive. MySetServiceStatus(SERVICE_RUNNING, 0, 0, 0); LeaveCriticalSection(&csForProcessCount); return; } void WINAPI TS_SecondaryLogonCleanupProcess(PVOID pvIndex, BOOLEAN bIgnored) { BOOL bIgnored2; SecondaryLogonCleanupJob(pvIndex, &bIgnored2); UNREFERENCED_PARAMETER(bIgnored); } DWORD APIENTRY SecondaryLogonProcessWatchdogNewProcess( PSECONDARYLOGONWATCHINFO dwParam ) /*++ Routine Description: This routine puts the secondary logon process created on the wait queue such that cleanup can be done after the process dies. Arguments: dwParam -- the pointer to the process information. Return Value: none. --*/ { BOOL fFailedAllocation = FALSE; BOOL fEnteredCriticalSection = FALSE; DWORD dwResult; JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp; LUID ProcessLogonId; PLIST_ENTRY ple; SecondaryLogonJob *psljNew = NULL; SecondaryLogonJob sljDummy; ZeroMemory(&sljDummy, sizeof(sljDummy)); __try { if (dwParam != NULL) { PSECONDARYLOGONWATCHINFO pslwi = (PSECONDARYLOGONWATCHINFO) dwParam; EnterCriticalSection(&csForProcessCount); fEnteredCriticalSection = TRUE; psljNew = (SecondaryLogonJob *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SecondaryLogonJob)); if (NULL == psljNew) { // Couldn't allocate a new SecondaryLogonJob object in which to hold cleanup info. Use a dummy // stack object and perform the cleanup immediately. fFailedAllocation = TRUE; psljNew = &sljDummy; } // Insert the new SecondaryLogonJob into the list of jobs to clean up. If we fail to fully register // for cleanup, we'll remove it from the list ourselves InsertTailList(&g_state.JobListHead, &psljNew->list); psljNew->hProcess = pslwi->hProcess; psljNew->hToken = pslwi->hToken; psljNew->hProfile = pslwi->hProfile; psljNew->psli = pslwi->psli; psljNew->fHeapAllocated = !fFailedAllocation; if (fFailedAllocation) { goto MemoryError; } // Initialize this job with the logon ID of the client process. // If this is a recursive runas, we'll override this value in the following loop psljNew->RootLogonId.LowPart = pslwi->LogonId.LowPart; psljNew->RootLogonId.HighPart = pslwi->LogonId.HighPart; // Search this list of active jobs and determine which logon session the new process should be associated with. for (ple = g_state.JobListHead.Flink; ple != &g_state.JobListHead; ple = ple->Flink) { SecondaryLogonJob *pslj = CONTAINING_RECORD(ple, SecondaryLogonJob, list); SlpGetClientLogonId(pslj->hProcess, &ProcessLogonId); if(ProcessLogonId.LowPart == pslwi->LogonId.LowPart && ProcessLogonId.HighPart == pslwi->LogonId.HighPart) { psljNew->RootLogonId.LowPart = pslj->RootLogonId.LowPart; psljNew->RootLogonId.HighPart = pslj->RootLogonId.HighPart; break; } } // Now determine what kind of cleanup we're going to do. In the non-TS (session 0) case, we'll // add the process to a job and cleanup when the process count goes to 0. In the TS case, // we'll wait on the process handle and cleanup when it terminates. This is the best we can do, // as there is no support for cross-session jobs. if (0 == pslwi->dwClientSessionId) { // The console login case psljNew->hJob = CreateJobObject(NULL, NULL); if (NULL == psljNew->hJob) goto CreateJobObjectError; if (!AssignProcessToJobObject(psljNew->hJob, psljNew->hProcess)) goto AssignProcessToJobObjectError; // Register our IO completion port to wait for events from this job: joacp.CompletionKey = (LPVOID)psljNew; joacp.CompletionPort = g_hIOCP; if (!SetInformationJobObject(psljNew->hJob, JobObjectAssociateCompletionPortInformation, &joacp, sizeof(joacp))) goto SetInformationJobObjectError; // If we don't already have a cleanup thread running, start one now: if (!g_fCleanupThreadActive) { // NOTE: it's acceptable for this to fail -- we'll just cleanup on the next runas g_fCleanupThreadActive = QueueUserWorkItem(WaitForNextJobTermination, NULL, WT_EXECUTELONGFUNCTION); } } else { // The TS case // We can't perform cleanup if we can't add the process to a job. The best we can do // for now is to hope that this is a termsrv client (highly likely), and that csrss will // do the cleanup for us. // // csrss won't unload user profiles, so we'll still have to do that. Wait until the // process terminates and unload then. We might unload profiles when an application doesn't // want it unloaded, but they can prevent this by holding open a key in the hive. // // This must be doc'd in CPWL documentation. // if (!RegisterWaitForSingleObject (&psljNew->hRegisteredProcessTerminated, psljNew->hProcess, TS_SecondaryLogonCleanupProcess, (PVOID)psljNew, INFINITE, WT_EXECUTEONLYONCE)) { goto RegisterWaitForSingleObjectError; } } // we've registered cleanup for this, we don't need to free it here anymore psljNew = NULL; // Update the service status to reflect that there is a runas'd process // This prevents the service from receiving SERVICE_STOP controls // while runas'd processes are alive. // NOTE: this will only be correct if called *AFTER* InsertTailList. It bases its // decision as to whether to allow a SERVICE_STOP control on whether there are any active // processes in this list. MySetServiceStatus(SERVICE_RUNNING, 0, 0, 0); } else { // // We were just awakened in order to terminate the service (nothing to do) // } } __except (EXCEPTION_EXECUTE_HANDLER) { dwResult = ERROR_INVALID_DATA; // don't give an attacker exception codes goto ExceptionError; } dwResult = ERROR_SUCCESS; ErrorReturn: if (NULL != psljNew) { BOOL bIgnored; // An error has occured. Terminate the process and cleanup after ourselves. TerminateProcess(psljNew->hProcess, dwResult); // NOTE: we want to make this call while holding the critsec, in case winlogon tries to shut us down while we're // performing this cleanup. SecondaryLogonCleanupJob(psljNew, &bIgnored); } if (fEnteredCriticalSection) { LeaveCriticalSection(&csForProcessCount); } return dwResult; SET_DWRESULT(AssignProcessToJobObjectError, GetLastError()); SET_DWRESULT(CreateJobObjectError, GetLastError()); SET_DWRESULT(ExceptionError, dwResult); SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY); SET_DWRESULT(RegisterWaitForSingleObjectError, GetLastError()); SET_DWRESULT(SetInformationJobObjectError, GetLastError()); } DWORD ServiceStop(BOOL fShutdown, DWORD dwExitCode) { DWORD dwCheckPoint = 0; DWORD dwResult; // Don't want the process count to change while we're shutting down the service! EnterCriticalSection(&csForProcessCount); // Only stop if we have no runas'd processes, or if we're shutting down if (fShutdown || IsListEmpty(&g_state.JobListHead)) { dwResult = MySetServiceStatus(SERVICE_STOP_PENDING, dwCheckPoint++, 0, 0); _JumpCondition(ERROR_SUCCESS != dwResult && !fShutdown, MySetServiceStatusError); // We shouldn't hold the critical section while we're shutting down the RPC server, // because RPC threads may be trying to acquire it. LeaveCriticalSection(&csForProcessCount); dwResult = SeclStopRpcServer(); _JumpCondition(ERROR_SUCCESS != dwResult && !fShutdown, SeclStopRpcServerError); dwResult = MySetServiceStatus(SERVICE_STOP_PENDING, dwCheckPoint++, 0, 0); _JumpCondition(ERROR_SUCCESS != dwResult && !fShutdown, MySetServiceStatusError); if (g_fIsCsInitialized) { DeleteCriticalSection(&csForProcessCount); g_fIsCsInitialized = FALSE; } if (NULL != g_hIOCP) { CloseHandle(g_hIOCP); g_hIOCP = NULL; } // Unlike MySetServiceStatus, this routine doesn't access any // global state which could have been freed: dwResult = MySetServiceStopped(dwExitCode); _JumpCondition(ERROR_SUCCESS != dwResult && !fShutdown, MySetServiceStopped); } dwResult = ERROR_SUCCESS; ErrorReturn: return dwResult; SET_DWRESULT(MySetServiceStatusError, dwResult); SET_DWRESULT(MySetServiceStopped, dwResult); SET_DWRESULT(SeclStopRpcServerError, dwResult); } void WINAPI ServiceHandler( DWORD fdwControl ) /*++ Routine Description: Service handler which wakes up the main service thread when ever service controller needs to send a message. Arguments: fdwControl -- the control from the service controller. Return Value: none. --*/ { DWORD dwNextState = g_state.serviceStatus.dwCurrentState; DWORD dwResult; switch (fdwControl) { case SERVICE_CONTROL_CONTINUE: dwResult = MySetServiceStatus(SERVICE_CONTINUE_PENDING, 0, 0, 0); _JumpCondition(ERROR_SUCCESS != dwResult, MySetServiceStatusError); dwResult = SeclStartRpcServer(); _JumpCondition(ERROR_SUCCESS != dwResult, StartRpcServerError); dwNextState = SERVICE_RUNNING; break; case SERVICE_CONTROL_INTERROGATE: break; case SERVICE_CONTROL_PAUSE: dwResult = MySetServiceStatus(SERVICE_PAUSE_PENDING, 0, 0, 0); _JumpCondition(ERROR_SUCCESS != dwResult, MySetServiceStatusError); dwResult = SeclStopRpcServer(); _JumpCondition(ERROR_SUCCESS != dwResult, StopRpcServerError); dwNextState = SERVICE_PAUSED; break; case SERVICE_CONTROL_STOP: dwResult = ServiceStop(FALSE /*fShutdown*/, ERROR_SUCCESS); _JumpCondition(ERROR_SUCCESS != dwResult, ServiceStopError); return ; // All global state has been freed, just exit. case SERVICE_CONTROL_SHUTDOWN: dwResult = ServiceStop(TRUE /*fShutdown*/, ERROR_SUCCESS); _JumpCondition(ERROR_SUCCESS != dwResult, ServiceStopError); return ; // All global state has been freed, just exit. default: // Unhandled service control! goto ErrorReturn; } CommonReturn: // Restore the original state on error, set the new state on success. dwResult = MySetServiceStatus(dwNextState, 0, 0, 0); return; ErrorReturn: goto CommonReturn; SET_ERROR(MySetServiceStatusError, dwResult); SET_ERROR(ServiceStopError, dwResult); SET_ERROR(StartRpcServerError, dwResult); SET_ERROR(StopRpcServerError, dwResult); } VOID SlrCreateProcessWithLogon (IN RPC_BINDING_HANDLE hRPCBinding, IN PSECONDARYLOGONINFOW *ppsli, OUT PSECONDARYLOGONRETINFO pslri) /*++ Routine Description: The core routine -- it handles a client request to start a secondary logon process. Arguments: psli -- the input structure with client request information pslri -- the output structure with response back to the client. Return Value: none. --*/ { HANDLE hCurrentThreadToken = NULL; HANDLE hToken = NULL; HANDLE hProfile = NULL; HANDLE hProcessClient = NULL; BOOL fCreatedEnvironmentBlock = FALSE; BOOL fIsImpersonatingRpcClient = FALSE; BOOL fIsImpersonatingClient = FALSE; BOOL fInheritHandles = FALSE; BOOL fOpenedSTDIN = FALSE; BOOL fOpenedSTDOUT = FALSE; BOOL fOpenedSTDERR = FALSE; SECURITY_ATTRIBUTES sa; PSECONDARYLOGONINFOW psli = *ppsli; SECONDARYLOGONWATCHINFO slwi; DWORD dwResult = ERROR_INVALID_PARAMETER; // Correct error code if we fail immediately DWORD SessionId; DWORD dwLogonProvider; SECURITY_LOGON_TYPE LogonType; PSID pLogonSid = NULL; LPWSTR pwszProfilePath = NULL; PROFILEINFO pi; WCHAR szTemp [ UNLEN + 1 ]; LPWSTR pszUserName = NULL; HANDLE hClientProcessToken ; PRIVILEGE_SET ImpersonatePrivilege ; BOOL PrivilegeTest ; ZeroMemory(&pi, sizeof(pi)); __try { // // Do some security checks: // // 1) We should impersonate the client and then try to open // the process so that we are assured that they didn't // give us some fake id. // dwResult = RpcImpersonateClient(hRPCBinding); _JumpCondition(RPC_S_OK != dwResult, leave_with_last_error); fIsImpersonatingRpcClient = TRUE; hProcessClient = OpenProcess(PROCESS_ALL_ACCESS, FALSE, psli->dwProcessId); _JumpCondition(hProcessClient == NULL, leave_with_last_error); #if 0 // // 2) Check that the client is not running from a restricted account. // hCurrentThread = GetCurrentThread(); // Doesn't need to be freed with CloseHandle(). _JumpCondition(NULL == hCurrentThread, leave_with_last_error); _JumpCondition(FALSE == OpenThreadToken(hCurrentThread, TOKEN_QUERY | TOKEN_DUPLICATE, TRUE, &hCurrentThreadToken), leave_with_last_error); #endif dwResult = RpcRevertToSelfEx(hRPCBinding); if (RPC_S_OK != dwResult) { __leave; } fIsImpersonatingRpcClient = FALSE; #if 0 if (TRUE == IsTokenUntrusted(hCurrentThreadToken)) { dwResult = ERROR_ACCESS_DENIED; __leave; } #endif // // We should get the session id from process id // we will set this up in the token so that create process // happens on the correct session. // _JumpCondition(!ProcessIdToSessionId(psli->dwProcessId, &SessionId), leave_with_last_error); // // Get the unique logonId. // we will use this to cleanup any running processes // when the logoff happens. // dwResult = SlpGetClientLogonId(hProcessClient, &slwi.LogonId); if(dwResult != ERROR_SUCCESS) { __leave; } dwResult = SlpGetClientSessionId(hProcessClient, &slwi.dwClientSessionId); if (dwResult != ERROR_SUCCESS) { __leave; } if ((psli->lpStartupInfo->dwFlags & STARTF_USESTDHANDLES) != 0) { _JumpCondition(!DuplicateHandle (hProcessClient, psli->lpStartupInfo->hStdInput, GetCurrentProcess(), &psli->lpStartupInfo->hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS), leave_with_last_error); fOpenedSTDIN = TRUE; _JumpCondition(!DuplicateHandle (hProcessClient, psli->lpStartupInfo->hStdOutput, GetCurrentProcess(), &psli->lpStartupInfo->hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS), leave_with_last_error); fOpenedSTDOUT = TRUE; _JumpCondition(!DuplicateHandle (hProcessClient, psli->lpStartupInfo->hStdError, GetCurrentProcess(), &psli->lpStartupInfo->hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS), leave_with_last_error); fOpenedSTDERR = TRUE; fInheritHandles = TRUE; } else { psli->lpStartupInfo->hStdInput = INVALID_HANDLE_VALUE; psli->lpStartupInfo->hStdOutput = INVALID_HANDLE_VALUE; psli->lpStartupInfo->hStdError = INVALID_HANDLE_VALUE; } if(psli->dwLogonFlags & LOGON_NETCREDENTIALS_ONLY) { LogonType = (SECURITY_LOGON_TYPE)LOGON32_LOGON_NEW_CREDENTIALS; dwLogonProvider = LOGON32_PROVIDER_WINNT50; } else { LogonType = (SECURITY_LOGON_TYPE) LOGON32_LOGON_INTERACTIVE; dwLogonProvider = LOGON32_PROVIDER_DEFAULT; } // LogonUser does not return profile information, we need to grab // that out of band after the logon has completed. // dwResult = RpcImpersonateClient(hRPCBinding); _JumpCondition(RPC_S_OK != dwResult, leave_with_last_error); fIsImpersonatingRpcClient = TRUE; if (0 == (SECLOGON_CALLER_SPECIFIED_DESKTOP & psli->dwSeclogonFlags)) { // BUG 477613: // If the caller did not specify their own desktop, it is our responsibility // to grant the user access to the default desktop. We'll do this by // adding the logon sid dwResult = GetLogonSid(&pLogonSid); if (ERROR_SUCCESS != dwResult) __leave; } if( psli->hToken != NULL ) { // // Caller has supplied a token. Verify that the caller can impersonate // before proceeding // if ( OpenProcessToken(hProcessClient, TOKEN_QUERY | TOKEN_IMPERSONATE, &hClientProcessToken ) ) { ImpersonatePrivilege.PrivilegeCount = 1; ImpersonatePrivilege.Privilege[ 0 ].Luid.HighPart = 0; ImpersonatePrivilege.Privilege[ 0 ].Luid.LowPart = SE_IMPERSONATE_PRIVILEGE ; if ( !PrivilegeCheck( hClientProcessToken,&ImpersonatePrivilege,&PrivilegeTest ) ) { PrivilegeTest = FALSE ; } CloseHandle( hClientProcessToken ); if ( !PrivilegeTest ) { dwResult = ERROR_PRIVILEGE_NOT_HELD ; __leave ; } } else { dwResult = GetLastError() ; __leave ; } // // duplicate the token from the caller, and use that. // if(!DuplicateHandle( hProcessClient, psli->hToken, GetCurrentProcess(), &hToken, 0, FALSE, DUPLICATE_SAME_ACCESS )) { dwResult = GetLastError(); __leave; } dwResult = RpcRevertToSelfEx(hRPCBinding); if (RPC_S_OK != dwResult) { __leave; } fIsImpersonatingRpcClient = FALSE; if(psli->dwLogonFlags & LOGON_WITH_PROFILE) { DWORD cchUserName = sizeof(szTemp) / sizeof(WCHAR); pszUserName = szTemp; // // impersonate the token to get the username, for the profile path. // if(!ImpersonateLoggedOnUser( hToken )) { dwResult = GetLastError(); __leave; } if(!GetUserNameW(szTemp, &cchUserName)) { dwResult = GetLastError(); } RevertToSelf(); if( ERROR_SUCCESS != dwResult ) { __leave; } // // TODO: pwszProfilePath ? // } dwResult = ERROR_SUCCESS; } else { pszUserName = psli->lpUsername; dwResult = LogonUserWrap( psli->lpUsername, psli->lpDomain, psli->uszPassword, LogonType, dwLogonProvider, pLogonSid, &hToken, &pwszProfilePath); if (ERROR_SUCCESS != dwResult) __leave; dwResult = RpcRevertToSelfEx(hRPCBinding); if (RPC_S_OK != dwResult) { __leave; } fIsImpersonatingRpcClient = FALSE; } if(psli->dwLogonFlags & LOGON_WITH_PROFILE) { // Load the user's profile: pi.dwSize = sizeof(pi); pi.lpUserName = pszUserName; pi.lpProfilePath = pwszProfilePath; if (!LoadUserProfile(hToken, &pi)) goto leave_with_last_error; // Save the profile handle so we can unload it later hProfile = pi.hProfile; } // Let us set the SessionId in the Token. _JumpCondition(!SetTokenInformation(hToken, TokenSessionId, &SessionId, sizeof(DWORD)), leave_with_last_error); // we should now impersonate the user. // _JumpCondition(!ImpersonateLoggedOnUser(hToken), leave_with_last_error); fIsImpersonatingClient = TRUE; // Query Default Owner/ACL from token. Make SD with this stuff, pass for sa.nLength = sizeof(sa); sa.bInheritHandle = FALSE; sa.lpSecurityDescriptor = NULL; // // We should set the console control handler so CtrlC is correctly // handled by the new process. // // SetConsoleCtrlHandler(NULL, FALSE); // // if lpEnvironment is NULL, we create new one for this user // using CreateEnvironmentBlock // if(NULL == (psli->lpEnvironment)) { if(FALSE == CreateEnvironmentBlock( &(psli->lpEnvironment), hToken, FALSE )) { psli->lpEnvironment = NULL; } else { // Successfully created environment block. fCreatedEnvironmentBlock = TRUE; } } // Create process. // NOTE: we want the primary thread to be suspended until we // register the thread for cleanup. In case the caller didn't specify it, // mask CREATE_SUSPENDED in. _JumpCondition(!CreateProcessAsUser(hToken, psli->lpApplicationName, psli->lpCommandLine, &sa, &sa, fInheritHandles, psli->dwCreationFlags | (fCreatedEnvironmentBlock ? CREATE_UNICODE_ENVIRONMENT : 0) | CREATE_SUSPENDED, psli->lpEnvironment, psli->lpCurrentDirectory, psli->lpStartupInfo, &pslri->pi), leave_with_last_error); SetLastError(NO_ERROR); leave_with_last_error: dwResult = GetLastError(); __leave; } __finally { pslri->dwErrorCode = dwResult; if (fCreatedEnvironmentBlock) { DestroyEnvironmentBlock(psli->lpEnvironment); } if (fIsImpersonatingClient) { RevertToSelf(); /* Ignore retval: nothing we can do on failure! */ } if (fIsImpersonatingRpcClient) { RpcRevertToSelfEx(hRPCBinding); /* Ignore retval: nothing we can do on failure! */ } if (fOpenedSTDIN) { CloseHandle(psli->lpStartupInfo->hStdInput); } if (fOpenedSTDOUT) { CloseHandle(psli->lpStartupInfo->hStdOutput); } if (fOpenedSTDERR) { CloseHandle(psli->lpStartupInfo->hStdError); } if (NULL != pLogonSid) { HeapFree(GetProcessHeap(), 0, pLogonSid); } if (NULL != pwszProfilePath) { HeapFree(GetProcessHeap(), 0, pwszProfilePath); } if(pslri->dwErrorCode != NO_ERROR) { if (NULL != hProfile) { UnloadUserProfile(hToken, hProfile); } if (NULL != hToken) { CloseHandle(hToken); } } else { // Start the watchdog process last so it won't delete psli before we're done with it. slwi.hProcess = pslri->pi.hProcess; slwi.hToken = hToken; slwi.hProfile = hProfile; // LogonId was already filled up.. right at the begining. slwi.psli = psli; // Register for cleanup: SecondaryLogonProcessWatchdogNewProcess MUST BE IN an EH! // The cleanup method will cleanup all process info, and terminate the process, on failure. dwResult = SecondaryLogonProcessWatchdogNewProcess(&slwi); if (ERROR_SUCCESS == dwResult) { if (0 == (psli->dwCreationFlags & CREATE_SUSPENDED)) { // The caller doesn't want to create the process suspended. Resume the primary thread: ResumeThread(pslri->pi.hThread); } // SetConsoleCtrlHandler(NULL, TRUE); // // Have the watchdog watch this newly added process so that // cleanup will occur correctly when the process terminates. // // Set up the windowstation and desktop for the process DuplicateHandle(GetCurrentProcess(), pslri->pi.hProcess, hProcessClient, &pslri->pi.hProcess, 0, FALSE, DUPLICATE_SAME_ACCESS); DuplicateHandle(GetCurrentProcess(), pslri->pi.hThread, hProcessClient, &pslri->pi.hThread, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); } else { pslri->dwErrorCode = dwResult; // return the error to the caller } *ppsli = NULL; // cleanup method will clean this up now. } if (NULL != hProcessClient) { CloseHandle(hProcessClient); } if (NULL != hCurrentThreadToken) { CloseHandle(hCurrentThreadToken); } } } void WINAPI ServiceMain (IN DWORD dwArgc, IN WCHAR ** lpszArgv) /*++ Routine Description: The main service handler thread routine. Arguments: Return Value: none. --*/ { DWORD dwResult; __try { InitializeCriticalSection(&csForProcessCount); g_fIsCsInitialized = TRUE; } __except (EXCEPTION_EXECUTE_HANDLER) { return; // We can't do anything if we can't initialize this critsec } g_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0,0); _JumpCondition(NULL == g_hIOCP, CreateIoCompletionPortError); dwResult = InitGlobalState(); _JumpCondition(ERROR_SUCCESS != dwResult, InitGlobalStateError); // NOTE: hSS does not have to be closed. g_state.hServiceStatus = RegisterServiceCtrlHandler(wszSvcName, ServiceHandler); _JumpCondition(NULL == g_state.hServiceStatus, RegisterServiceCtrlHandlerError); dwResult = SeclStartRpcServer(); _JumpCondition(ERROR_SUCCESS != dwResult, StartRpcServerError); // Tell the SCM we're up and running: dwResult = MySetServiceStatus(SERVICE_RUNNING, 0, 0, ERROR_SUCCESS); _JumpCondition(ERROR_SUCCESS != dwResult, MySetServiceStatusError); SetLastError(ERROR_SUCCESS); ErrorReturn: // Shut down the service if we couldn't fully start: if (ERROR_SUCCESS != GetLastError()) { ServiceStop(TRUE /*fShutdown*/, GetLastError()); } return; SET_ERROR(InitGlobalStateError, dwResult) SET_ERROR(MySetServiceStatusError, dwResult); SET_ERROR(RegisterServiceCtrlHandlerError, dwResult); SET_ERROR(StartRpcServerError, dwResult); TRACE_ERROR(CreateIoCompletionPortError); UNREFERENCED_PARAMETER(dwArgc); UNREFERENCED_PARAMETER(lpszArgv); } DWORD InstallService() /*++ Routine Description: It installs the service with service controller, basically creating the service object. Arguments: none. Return Value: several - as returned by the service controller. --*/ { // TCHAR *szModulePathname; TCHAR AppName[MAX_PATH]; LPTSTR ptszAppName = NULL; SC_HANDLE hService; DWORD dw; HANDLE hMod; // // Open the SCM on this machine. // SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); if(hSCM == NULL) { dw = GetLastError(); return dw; } // // Let us give the service a useful description // This is not earth shattering... if it works fine, if it // doesn't it is just too bad :-) // hMod = GetModuleHandle(L"seclogon.dll"); // // we'll try to get the localized name for the service, // if it fails, we'll just put an english string... // if(hMod != NULL) { LoadString(hMod, SECLOGON_STRING_NAME, AppName, MAX_PATH ); ptszAppName = AppName; } else ptszAppName = L"RunAs Service"; // // Add this service to the SCM's database. // hService = CreateService (hSCM, wszSvcName, ptszAppName, SERVICE_ALL_ACCESS, SERVICE_WIN32_SHARE_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, L"%SystemRoot%\\system32\\svchost.exe -k netsvcs", NULL, NULL, NULL, NULL, NULL); if(hService == NULL) { dw = GetLastError(); CloseServiceHandle(hSCM); return dw; } if(hMod != NULL) { WCHAR DescString[500]; SERVICE_DESCRIPTION SvcDesc; LoadString( hMod, SECLOGON_STRING_DESCRIPTION, DescString, 500 ); SvcDesc.lpDescription = DescString; ChangeServiceConfig2( hService, SERVICE_CONFIG_DESCRIPTION, &SvcDesc ); } // // Close the service and the SCM // CloseServiceHandle(hService); CloseServiceHandle(hSCM); return S_OK; } DWORD RemoveService() /*++ Routine Description: deinstalls the service. Arguments: none. Return Value: as returned by service controller apis. --*/ { DWORD dw; SC_HANDLE hService; // // Open the SCM on this machine. // SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if(hSCM == NULL) { dw = GetLastError(); return dw; } // // Open this service for DELETE access // hService = OpenService(hSCM, wszSvcName, DELETE); if(hService == NULL) { dw = GetLastError(); CloseServiceHandle(hSCM); return dw; } // // Remove this service from the SCM's database. // DeleteService(hService); // // Close the service and the SCM // CloseServiceHandle(hService); CloseServiceHandle(hSCM); return S_OK; } void SvchostPushServiceGlobals(PSVCHOST_GLOBAL_DATA pGlobalData) { // this entry point is called by svchost.exe GlobalData=pGlobalData; } void SvcEntry_Seclogon (IN DWORD argc, IN WCHAR **argv) /*++ Routine Description: Entry point for the service dll when running in svchost.exe Arguments: Return Value: --*/ { ServiceMain(0,NULL); UNREFERENCED_PARAMETER(argc); UNREFERENCED_PARAMETER(argv); } STDAPI DllRegisterServer(void) /*++ Routine Description: Arguments: Return Value: --*/ { return InstallService(); } STDAPI DllUnregisterServer(void) /*++ Routine Description: Arguments: Return Value: --*/ { return RemoveService(); } DWORD InitGlobalState() { BOOLEAN bWasEnabled; DWORD dwResult; LSA_STRING lsastr_LogonProcessName; LSA_STRING lsastr_PackageName; NTSTATUS ntStatus; ULONG ulSecurityMode; ZeroMemory(&g_state, sizeof(g_state)); // LsaRegisterLogonProcess() requires TCB privilege RtlAdjustPrivilege(SE_TCB_PRIVILEGE, TRUE, FALSE, &bWasEnabled); RtlInitString(&lsastr_LogonProcessName, "Secondary Logon Service"); ntStatus = LsaRegisterLogonProcess(&lsastr_LogonProcessName, &g_state.hLSA, &ulSecurityMode); if (!NT_SUCCESS(ntStatus)) goto LsaRegisterLogonProcessError; RtlAdjustPrivilege(SE_TCB_PRIVILEGE, bWasEnabled, FALSE, &bWasEnabled); RtlInitString(&lsastr_PackageName, "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0"); ntStatus = LsaLookupAuthenticationPackage(g_state.hLSA, &lsastr_PackageName, &g_state.hMSVPackage); if (!NT_SUCCESS(ntStatus)) goto LsaLookupAuthenticationPackageError; RtlInitString(&lsastr_PackageName, NEGOSSP_NAME_A); ntStatus = LsaLookupAuthenticationPackage(g_state.hLSA, &lsastr_PackageName, &g_state.hKerbPackage); if (!NT_SUCCESS(ntStatus)) goto LsaLookupAuthenticationPackageError; // Initialize the list of seclogon processes: InitializeListHead(&g_state.JobListHead); dwResult = ERROR_SUCCESS; ErrorReturn: return dwResult; SET_DWRESULT(LsaLookupAuthenticationPackageError, RtlNtStatusToDosError(ntStatus)); SET_DWRESULT(LsaRegisterLogonProcessError, RtlNtStatusToDosError(ntStatus)); } DWORD MySetServiceStatus(DWORD dwCurrentState, DWORD dwCheckPoint, DWORD dwWaitHint, DWORD dwExitCode) { BOOL fResult; DWORD dwResult; DWORD dwAcceptStop; EnterCriticalSection(&csForProcessCount); dwAcceptStop = IsListEmpty(&g_state.JobListHead) ? SERVICE_ACCEPT_STOP : 0; g_state.serviceStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS; g_state.serviceStatus.dwCurrentState = dwCurrentState; switch (dwCurrentState) { case SERVICE_STOPPED: case SERVICE_STOP_PENDING: g_state.serviceStatus.dwControlsAccepted = 0; break; case SERVICE_RUNNING: case SERVICE_PAUSED: g_state.serviceStatus.dwControlsAccepted = // SERVICE_ACCEPT_SHUTDOWN SERVICE_ACCEPT_PAUSE_CONTINUE | dwAcceptStop; break; case SERVICE_START_PENDING: case SERVICE_CONTINUE_PENDING: case SERVICE_PAUSE_PENDING: g_state.serviceStatus.dwControlsAccepted = // SERVICE_ACCEPT_SHUTDOWN dwAcceptStop; break; } g_state.serviceStatus.dwWin32ExitCode = dwExitCode; g_state.serviceStatus.dwCheckPoint = dwCheckPoint; g_state.serviceStatus.dwWaitHint = dwWaitHint; fResult = SetServiceStatus(g_state.hServiceStatus, &g_state.serviceStatus); _JumpCondition(FALSE == fResult, SetServiceStatusError); dwResult = ERROR_SUCCESS; CommonReturn: LeaveCriticalSection(&csForProcessCount); return dwResult; ErrorReturn: goto CommonReturn; SET_DWRESULT(SetServiceStatusError, GetLastError()); } DWORD MySetServiceStopped(DWORD dwExitCode) { BOOL fResult; DWORD dwResult; g_state.serviceStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS; g_state.serviceStatus.dwCurrentState = SERVICE_STOPPED; g_state.serviceStatus.dwControlsAccepted = 0; g_state.serviceStatus.dwWin32ExitCode = dwExitCode; g_state.serviceStatus.dwCheckPoint = 0; g_state.serviceStatus.dwWaitHint = 0; fResult = SetServiceStatus(g_state.hServiceStatus, &g_state.serviceStatus); _JumpCondition(FALSE == fResult, SetServiceStatusError); dwResult = ERROR_SUCCESS; CommonReturn: return dwResult; ErrorReturn: goto CommonReturn; SET_DWRESULT(SetServiceStatusError, GetLastError()); } //////////////////////////////////////////////////////////////////////// // // Implementation of RPC interface: // //////////////////////////////////////////////////////////////////////// // Make sure that the caller is local (we don't want to allow remote machines to call us!) // This should be done as soon as possible (so we don't waste resources on hackers). // Therefore, we put this call in a security callback -- if the callback fails, the RPC // runtime won't allocate memory for the hacker's parameters. // long __stdcall SeclSecurityCallback(void *Interface, void *Context) { RPC_STATUS rpcStatus; unsigned int fClientIsLocal; rpcStatus = I_RpcBindingIsClientLocal(NULL, &fClientIsLocal); if (RPC_S_OK != rpcStatus) goto error; if (!fClientIsLocal) { rpcStatus = RPC_S_ACCESS_DENIED; goto error; } rpcStatus = RPC_S_OK; error: return rpcStatus; UNREFERENCED_PARAMETER(Interface); UNREFERENCED_PARAMETER(Context); } void WINAPI SeclCreateProcessWithLogonW (IN handle_t hRPCBinding, IN SECL_SLI *pSeclSli, OUT SECL_SLRI *pSeclSlri) { BOOL fEnteredCriticalSection = FALSE; BOOL fIsImpersonatingClient = FALSE; DWORD dwResult; HANDLE hHeap = NULL; PLIST_ENTRY ple; PSECONDARYLOGONINFOW psli = NULL; SECL_SLRI SeclSlri; SECONDARYLOGONRETINFO slri; ZeroMemory(&SeclSlri, sizeof(SeclSlri)); ZeroMemory(&slri, sizeof(slri)); // We don't want the service to be stopped while we're creating a process. EnterCriticalSection(&csForProcessCount); fEnteredCriticalSection = TRUE; // Service isn't running anymore ... don't create the process. _JumpCondition(SERVICE_RUNNING != g_state.serviceStatus.dwCurrentState, ServiceStoppedError); hHeap = GetProcessHeap(); _JumpCondition(NULL == hHeap, MemoryError); __try { dwResult = To_SECONDARYLOGONINFOW(pSeclSli, &psli); _JumpCondition(ERROR_SUCCESS != dwResult, To_SECONDARYLOGONINFOW_Error); if (psli->LogonIdHighPart != 0 || psli->LogonIdLowPart != 0) { // This is probably a notification from winlogon.exe that // a client is logging off. If so, we must clean up all processes // they've left running. LUID LogonId; // // We should impersonate the client, // check it is LocalSystem and only then proceed. // fIsImpersonatingClient = RPC_S_OK == RpcImpersonateClient((RPC_BINDING_HANDLE)hRPCBinding); if(FALSE == fIsImpersonatingClient || FALSE == IsSystemProcess()) { slri.dwErrorCode = ERROR_INVALID_PARAMETER; ZeroMemory(&slri.pi, sizeof(slri.pi)); } else { LogonId.HighPart = psli->LogonIdHighPart; LogonId.LowPart = psli->LogonIdLowPart; // Loop over the list of active jobs and find the ones associated with the terminating logon session. // NOTE: there could be more than one. for (ple = g_state.JobListHead.Flink; ple != &g_state.JobListHead; ple = ple->Flink) { SecondaryLogonJob *pslj = CONTAINING_RECORD(ple, SecondaryLogonJob, list); if(pslj->RootLogonId.HighPart == LogonId.HighPart && pslj->RootLogonId.LowPart == LogonId.LowPart) { // This will be NULL in the terminal services case (we don't use job objects in this case). // That's OK though, as csrss will clean things up for us. if (NULL != pslj->hJob) { TerminateJobObject(pslj->hJob, 0); } } } slri.dwErrorCode = ERROR_SUCCESS; ZeroMemory(&slri.pi, sizeof(slri.pi)); } if (fIsImpersonatingClient) { // Ignore error: nothing we can do on failure! if (RPC_S_OK == RpcRevertToSelfEx((RPC_BINDING_HANDLE)hRPCBinding)) { fIsImpersonatingClient = FALSE; } } if (NULL != psli) { Free_SECONDARYLOGONINFOW(psli); psli = NULL; } } else { // Ok, this isn't notification from winlogon, it's really a user // trying to use the service. Create a process for them. // SlrCreateProcessWithLogon((RPC_BINDING_HANDLE)hRPCBinding, &psli, &slri); } // If we've errored out, jump to the error handler. _JumpCondition(NO_ERROR != slri.dwErrorCode, UnspecifiedSeclogonError); } __except (EXCEPTION_EXECUTE_HANDLER) { // If anything goes wrong, return the exception code to the client dwResult = GetExceptionCode(); goto ExceptionError; } CommonReturn: // Do not free slri: this will be freed by the watchdog! SeclSlri.hProcess = (unsigned __int64)slri.pi.hProcess; SeclSlri.hThread = (unsigned __int64)slri.pi.hThread; SeclSlri.ulProcessId = slri.pi.dwProcessId; SeclSlri.ulThreadId = slri.pi.dwThreadId; SeclSlri.ulErrorCode = slri.dwErrorCode; if (fEnteredCriticalSection) LeaveCriticalSection(&csForProcessCount); // Assign the OUT parameter: *pSeclSlri = SeclSlri; return; ErrorReturn: ZeroMemory(&slri.pi, sizeof(slri.pi)); if (NULL != psli) { Free_SECONDARYLOGONINFOW(psli); } slri.dwErrorCode = dwResult; goto CommonReturn; SET_DWRESULT(ExceptionError, dwResult); SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY); SET_DWRESULT(ServiceStoppedError, ERROR_SERVICE_NOT_ACTIVE); SET_DWRESULT(To_SECONDARYLOGONINFOW_Error, dwResult); SET_DWRESULT(UnspecifiedSeclogonError, slri.dwErrorCode); } //////////////////////////////////////////////////////////////////////// // // RPC Utility methods: // //////////////////////////////////////////////////////////////////////// DWORD SeclStartRpcServer() { DWORD dwResult; RPC_STATUS RpcStatus; EnterCriticalSection(&csForProcessCount); if (!g_state.fRPCServerActive) { RpcStatus = RpcServerUseProtseqEpW(L"ncalrpc", RPC_C_PROTSEQ_MAX_REQS_DEFAULT, wszSeclogonSharedProcEndpointName, NULL); if (RPC_S_DUPLICATE_ENDPOINT == RpcStatus) RpcStatus = RPC_S_OK; if (RPC_S_OK != RpcStatus) goto RpcServerUseProtseqEpWError; RpcStatus = RpcServerRegisterIfEx(ISeclogon_v1_0_s_ifspec, NULL, NULL, RPC_IF_AUTOLISTEN | RPC_IF_ALLOW_SECURE_ONLY, RPC_C_PROTSEQ_MAX_REQS_DEFAULT, SeclSecurityCallback); if (RPC_S_OK != RpcStatus) goto StartRpcServerError; g_state.fRPCServerActive = TRUE; } dwResult = ERROR_SUCCESS; CommonReturn: LeaveCriticalSection(&csForProcessCount); return dwResult; ErrorReturn: goto CommonReturn; SET_DWRESULT(RpcServerUseProtseqEpWError, RpcStatus); SET_DWRESULT(StartRpcServerError, RpcStatus); } DWORD SeclStopRpcServer() { DWORD dwResult; RPC_STATUS RpcStatus; EnterCriticalSection(&csForProcessCount); if (g_state.fRPCServerActive) { RpcStatus = RpcServerUnregisterIf(ISeclogon_v1_0_s_ifspec, 0, 0); if (RPC_S_OK != RpcStatus) goto RpcServerUnregisterIfError; g_state.fRPCServerActive = FALSE; } dwResult = ERROR_SUCCESS; CommonReturn: LeaveCriticalSection(&csForProcessCount); return dwResult; ErrorReturn: goto CommonReturn; SET_DWRESULT(RpcServerUnregisterIfError, RpcStatus); } void Free_SECONDARYLOGONINFOW(IN PSECONDARYLOGONINFOW psli) { HANDLE hHeap = GetProcessHeap(); if (NULL == hHeap) return; if (NULL != psli) { if (NULL != psli->lpStartupInfo) { HeapFree(hHeap, 0, psli->lpStartupInfo); } HeapFree(hHeap, 0, psli); } } DWORD To_LPWSTR(IN SECL_STRING *pss, OUT LPWSTR *ppwsz) { DWORD dwResult; if (NULL != pss->pwsz) { // Ensure that the string is NULL-terminated where the caller says it is: if (pss->ccSize <= pss->ccLength) { goto InvalidParameterError; } // NULL-terminate the string ourself pss->pwsz[pss->ccLength] = L'\0'; } *ppwsz = pss->pwsz; dwResult = ERROR_SUCCESS; ErrorReturn: return dwResult; SET_DWRESULT(InvalidParameterError, ERROR_INVALID_PARAMETER); } DWORD To_SECONDARYLOGONINFOW(IN PSECL_SLI pSeclSli, OUT PSECONDARYLOGONINFOW *ppsli) { DWORD dwAllocFlags = HEAP_ZERO_MEMORY; DWORD dwIndex; DWORD dwResult; HANDLE hHeap = NULL; PSECONDARYLOGONINFOW psli = NULL; hHeap = GetProcessHeap(); _JumpCondition(NULL == hHeap, GetProcessHeapError); psli = (PSECONDARYLOGONINFOW)HeapAlloc(hHeap, dwAllocFlags, sizeof(SECONDARYLOGONINFOW)); _JumpCondition(NULL == psli, MemoryError); psli->lpStartupInfo = (LPSTARTUPINFO)HeapAlloc(hHeap, dwAllocFlags, sizeof(STARTUPINFO)); _JumpCondition(NULL == psli->lpStartupInfo, MemoryError); __try { { struct { SECL_STRING *pss; LPWSTR *ppwsz; } rg_StringsToMap[] = { { &(pSeclSli->ssDesktop), /* Is mapped to ----> */ &(psli->lpStartupInfo->lpDesktop) }, { &(pSeclSli->ssTitle), /* Is mapped to ----> */ &(psli->lpStartupInfo->lpTitle) }, { &(pSeclSli->ssUsername), /* Is mapped to ----> */ &(psli->lpUsername) }, { &(pSeclSli->ssDomain), /* Is mapped to ----> */ &(psli->lpDomain) }, { &(pSeclSli->ssApplicationName), /* Is mapped to ----> */ &(psli->lpApplicationName) }, { &(pSeclSli->ssCommandLine), /* Is mapped to ----> */ &(psli->lpCommandLine) }, { &(pSeclSli->ssCurrentDirectory), /* Is mapped to ----> */ (LPWSTR *)&(psli->lpCurrentDirectory) } }; for (dwIndex = 0; dwIndex < ARRAYSIZE(rg_StringsToMap); dwIndex++) { dwResult = To_LPWSTR(rg_StringsToMap[dwIndex].pss, rg_StringsToMap[dwIndex].ppwsz); _JumpCondition(ERROR_SUCCESS != dwResult, To_LPWSTR_Error); } } // Get the (possibly encrypted) passwd: psli->uszPassword.Buffer = pSeclSli->ssPassword.pwsz; psli->uszPassword.Length = pSeclSli->ssPassword.ccLength; psli->uszPassword.MaximumLength = pSeclSli->ssPassword.ccSize; if (NULL != psli->uszPassword.Buffer && psli->uszPassword.MaximumLength > 0) { psli->uszPassword.Buffer[psli->uszPassword.MaximumLength-1] = L'\0'; } // Grab the environment from the SECL_SLI block: psli->lpEnvironment = pSeclSli->sbEnvironment.pb; // Ensure that our environment parameter is NULL-terminated: if (NULL != psli->lpEnvironment) { // The environment block is terminated by 2 NULL chars. DWORD cbTerm = (CREATE_UNICODE_ENVIRONMENT & pSeclSli->ulCreationFlags) ? sizeof(WCHAR)*2 : sizeof(CHAR)*2; // ensure we have a large enough buffer to contain the NULL termination chars: if (pSeclSli->sbEnvironment.cb < cbTerm) goto InvalidParameterError; // add terminated NULLs to the end of the environment block for (; cbTerm > 0; cbTerm--) { pSeclSli->sbEnvironment.pb[pSeclSli->sbEnvironment.cb - cbTerm] = 0; } } } __except (EXCEPTION_EXECUTE_HANDLER) { // Don't give the caller too much information: the input was bad, that's all they need to know. dwResult = ERROR_INVALID_PARAMETER; goto ExceptionError; } psli->dwProcessId = pSeclSli->ulProcessId; psli->LogonIdLowPart = pSeclSli->ulLogonIdLowPart; psli->LogonIdHighPart = pSeclSli->lLogonIdHighPart; psli->dwLogonFlags = pSeclSli->ulLogonFlags; psli->dwCreationFlags = pSeclSli->ulCreationFlags; psli->dwSeclogonFlags = pSeclSli->ulSeclogonFlags; psli->hToken = (HANDLE)((ULONG_PTR)pSeclSli->hToken); *ppsli = psli; dwResult = ERROR_SUCCESS; CommonReturn: return dwResult; ErrorReturn: Free_SECONDARYLOGONINFOW(psli); goto CommonReturn; SET_DWRESULT(ExceptionError, dwResult); SET_DWRESULT(GetProcessHeapError, GetLastError()); SET_DWRESULT(InvalidParameterError, ERROR_INVALID_PARAMETER); SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY); SET_DWRESULT(To_LPWSTR_Error, dwResult); } //////////////////////////////// End Of File /////////////////////////////////