//+------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1997. // // File: security.cxx // // Contents: // //-------------------------------------------------------------------------- #include "act.hxx" // the constant generic mapping structure GENERIC_MAPPING sGenericMapping = { READ_CONTROL, READ_CONTROL, READ_CONTROL, READ_CONTROL}; //------------------------------------------------------------------------- // // CheckForAccess // // Checks whether the given token has COM_RIGHTS_EXECUTE access in the // given security descriptor. // //------------------------------------------------------------------------- BOOL CheckForAccess( IN CToken * pToken, IN SECURITY_DESCRIPTOR * pSD ) { // if we have an empty SD, deny everyone if ( ! pSD ) return FALSE; // // pToken is NULL during an unsecure activation, in which case we check // if EVERYONE is granted access in the security descriptor. // if ( pToken ) { HANDLE hToken = pToken->GetToken(); BOOL fAccess = FALSE; BOOL fSuccess = FALSE; DWORD dwGrantedAccess; PRIVILEGE_SET sPrivilegeSet; DWORD dwSetLen = sizeof( sPrivilegeSet ); sPrivilegeSet.PrivilegeCount = 1; sPrivilegeSet.Control = 0; fSuccess = AccessCheck( (PSECURITY_DESCRIPTOR) pSD, hToken, COM_RIGHTS_EXECUTE, &sGenericMapping, &sPrivilegeSet, &dwSetLen, &dwGrantedAccess, &fAccess ); if ( fSuccess && fAccess ) return TRUE; if ( !fSuccess ) { CairoleDebugOut((DEB_ERROR, "Bad Security Descriptor 0x%08x, Access Check returned 0x%x\n", pSD, GetLastError() )); } return FALSE; } else { BOOL bStatus; BOOL bDaclPresent; BOOL bDaclDefaulted; DWORD Index; HRESULT hr; PACL pDacl; PACCESS_ALLOWED_ACE pAllowAce; SID SidEveryone = { SID_REVISION, 1, SECURITY_WORLD_SID_AUTHORITY, 0 }; pDacl = 0; bStatus = GetSecurityDescriptorDacl( (void *)pSD, &bDaclPresent, &pDacl, &bDaclDefaulted ); if ( ! bStatus ) return FALSE; // Bug 95306: Can assume dacl exists only if both // bDaclPresent is TRUE and pDacl is not NULL if ( (!pDacl) || (!bDaclPresent) ) return TRUE; bStatus = FALSE; for ( Index = 0; Index < pDacl->AceCount; Index++ ) { if ( ! GetAce( pDacl, Index, (void **) &pAllowAce ) ) break; if ( pAllowAce->Header.AceType != ACCESS_ALLOWED_ACE_TYPE ) continue; if ( ! (pAllowAce->Mask & COM_RIGHTS_EXECUTE) ) continue; if ( EqualSid( (PSID)(&pAllowAce->SidStart), &SidEveryone ) ) { bStatus = TRUE; break; } } return bStatus; } } HANDLE GetRunAsToken( DWORD clsctx, WCHAR *pwszAppID, WCHAR *pwszRunAsDomainName, WCHAR *pwszRunAsUserName ) { LSA_OBJECT_ATTRIBUTES sObjAttributes; HANDLE hPolicy = NULL; LSA_UNICODE_STRING sKey; WCHAR wszKey[CLSIDSTR_MAX+5]; PLSA_UNICODE_STRING psPassword; HANDLE hToken; if ( !pwszAppID ) { // if we have a RunAs, we'd better have an appid.... return 0; } // formulate the access key lstrcpyW(wszKey, L"SCM:"); lstrcatW(wszKey, pwszAppID ); // UNICODE_STRING length fields are in bytes and include the NULL // terminator sKey.Length = (USHORT)((lstrlenW(wszKey) + 1) * sizeof(WCHAR)); sKey.MaximumLength = (CLSIDSTR_MAX + 5) * sizeof(WCHAR); sKey.Buffer = wszKey; // Open the local security policy InitializeObjectAttributes(&sObjAttributes, NULL, 0L, NULL, NULL); if (!NT_SUCCESS(LsaOpenPolicy(NULL, &sObjAttributes, POLICY_GET_PRIVATE_INFORMATION, &hPolicy))) { return 0; } // Read the user's password if (!NT_SUCCESS(LsaRetrievePrivateData(hPolicy, &sKey, &psPassword))) { LsaClose(hPolicy); return 0; } // Close the policy handle, we're done with it now. LsaClose(hPolicy); // Possible for LsaRetrievePrivateData to return success but with a NULL // psPassword. If this happens we fail. if (!psPassword) { return 0; } // Log the specifed user on if (!LogonUserW(pwszRunAsUserName, pwszRunAsDomainName, psPassword->Buffer, LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &hToken)) { memset(psPassword->Buffer, 0, psPassword->Length); LsaFreeMemory( psPassword ); // a-sergiv (Sergei O. Ivanov), 6-17-99 // Fix for com+ 9383/nt 272085 // Apply event filters DWORD dwActLogLvl = GetActivationFailureLoggingLevel(); if(dwActLogLvl == 2) return 0; if(dwActLogLvl != 1 && clsctx & CLSCTX_NO_FAILURE_LOG) return 0; // for this message, // %1 is the error number string // %2 is the domain name // %3 is the user name // %4 is the CLSID HANDLE LogHandle; LPWSTR Strings[4]; // array of message strings. WCHAR wszErrnum[20]; WCHAR wszClsid[GUIDSTR_MAX]; // Save the error number wsprintf(wszErrnum, L"%lu",GetLastError() ); Strings[0] = wszErrnum; // Put in the RunAs identity Strings[1] = pwszRunAsDomainName; Strings[2] = pwszRunAsUserName; // Get the clsid Strings[3] = pwszAppID; // Get the log handle, then report then event. LogHandle = RegisterEventSource( NULL, SCM_EVENT_SOURCE ); if ( LogHandle ) { ReportEvent( LogHandle, EVENTLOG_ERROR_TYPE, 0, // event category EVENT_RPCSS_RUNAS_CANT_LOGIN, NULL, // SID 4, // 4 strings passed 0, // 0 bytes of binary (LPCTSTR *)Strings, // array of strings NULL ); // no raw data // clean up the event log handle DeregisterEventSource(LogHandle); } return 0; } // Clear the password memset(psPassword->Buffer, 0, psPassword->Length); LsaFreeMemory( psPassword ); return hToken; } /***************************************************************************\ * CreateAndSetProcessToken * * Set the primary token of the specified process * If the specified token is NULL, this routine does nothing. * * It assumed that the handles in ProcessInformation are the handles returned * on creation of the process and therefore have all access. * * Returns TRUE on success, FALSE on failure. * * 01-31-91 Davidc Created. * 31-Mar-94 AndyH Started from Winlogon; added SetToken \***************************************************************************/ BOOL CreateAndSetProcessToken( PPROCESS_INFORMATION ProcessInformation, HANDLE hUserToken, PSID psidUserSid ) { NTSTATUS NtStatus, NtAdjustStatus; PROCESS_ACCESS_TOKEN PrimaryTokenInfo; HANDLE hTokenToAssign; OBJECT_ATTRIBUTES ObjectAttributes; BOOLEAN fWasEnabled; PSECURITY_DESCRIPTOR psdNewProcessTokenSD; // // Check for a NULL token. (No need to do anything) // The process will run in the parent process's context and inherit // the default ACL from the parent process's token. // if (hUserToken == NULL) { return(TRUE); } // // Create the security descriptor that we want to put in the Token. // Need to destroy it before we leave this function. // CAccessInfo AccessInfo(psidUserSid); psdNewProcessTokenSD = AccessInfo.IdentifyAccess( FALSE, TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | READ_CONTROL, TOKEN_QUERY ); if (psdNewProcessTokenSD == NULL) { CairoleDebugOut((DEB_ERROR, "Failed to create SD for process token\n")); return(FALSE); } // // A primary token can only be assigned to one process. // Duplicate the logon token so we can assign one to the new // process. // InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, psdNewProcessTokenSD ); NtStatus = NtDuplicateToken( hUserToken, // Duplicate this token TOKEN_ASSIGN_PRIMARY, // Give me this access to the resulting token &ObjectAttributes, FALSE, // EffectiveOnly TokenPrimary, // TokenType &hTokenToAssign // Duplicate token handle stored here ); if (!NT_SUCCESS(NtStatus)) { CairoleDebugOut((DEB_ERROR, "CreateAndSetProcessToken failed to duplicate primary token for new user process, status = 0x%lx\n", NtStatus)); return(FALSE); } // // Set the process's primary token // // // Enable the required privilege // NtStatus = RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE, TRUE, FALSE, &fWasEnabled); if (NT_SUCCESS(NtStatus)) { PrimaryTokenInfo.Token = hTokenToAssign; PrimaryTokenInfo.Thread = ProcessInformation->hThread; NtStatus = NtSetInformationProcess( ProcessInformation->hProcess, ProcessAccessToken, (PVOID)&PrimaryTokenInfo, (ULONG)sizeof(PROCESS_ACCESS_TOKEN) ); // // if we just started the Shared WOW, the handle we get back // is really just a handle to an event. // if (STATUS_OBJECT_TYPE_MISMATCH == NtStatus) { HANDLE hRealProcess = OpenProcess( PROCESS_SET_INFORMATION | PROCESS_TERMINATE | SYNCHRONIZE, FALSE, ProcessInformation->dwProcessId); if (hRealProcess) { NtStatus = NtSetInformationProcess( hRealProcess, ProcessAccessToken, (PVOID)&PrimaryTokenInfo, (ULONG)sizeof(PROCESS_ACCESS_TOKEN) ); CloseHandle(hRealProcess); } } // // Restore the privilege to its previous state // NtAdjustStatus = RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE, fWasEnabled, FALSE, &fWasEnabled); if (!NT_SUCCESS(NtAdjustStatus)) { CairoleDebugOut((DEB_ERROR, "failed to restore assign-primary-token privilege to previous enabled state\n")); } if (NT_SUCCESS(NtStatus)) { NtStatus = NtAdjustStatus; } } else { CairoleDebugOut((DEB_ERROR, "failed to enable assign-primary-token privilege\n")); } // // We're finished with the token handle and the SD // CloseHandle(hTokenToAssign); if (!NT_SUCCESS(NtStatus)) { CairoleDebugOut((DEB_ERROR, "CreateAndSetProcessToken failed to set primary token for new user process, Status = 0x%lx\n", NtStatus)); } return (NT_SUCCESS(NtStatus)); } BOOL DuplicateTokenForSessionUse( HANDLE hUserToken, HANDLE *hDuplicate ) { OBJECT_ATTRIBUTES ObjectAttributes; PSECURITY_DESCRIPTOR psdNewProcessTokenSD; NTSTATUS NtStatus; if (hUserToken == NULL) { return(TRUE); } *hDuplicate = NULL; InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL ); NtStatus = NtDuplicateToken( hUserToken, // Duplicate this token TOKEN_ALL_ACCESS, //Give me this access to the resulting token &ObjectAttributes, FALSE, // EffectiveOnly TokenPrimary, // TokenType hDuplicate // Duplicate token handle stored here ); if (!NT_SUCCESS(NtStatus)) { CairoleDebugOut((DEB_ERROR, "CreateAndSetProcessToken failed to duplicate primary token for new user process, status = 0x%lx\n", NtStatus)); return(FALSE); } return TRUE; } /***************************************************************************\ * GetUserSid * * Allocs space for the user sid, fills it in and returns a pointer. * The sid should be freed by calling DeleteUserSid. * * Note the sid returned is the user's real sid, not the per-logon sid. * * Returns pointer to sid or NULL on failure. * * History: * 26-Aug-92 Davidc Created. * 31-Mar-94 AndyH Copied from Winlogon, changed arg from pGlobals \***************************************************************************/ PSID GetUserSid( HANDLE hUserToken ) { BYTE achBuffer[100]; PTOKEN_USER pUser = (PTOKEN_USER) &achBuffer; PSID pSid; DWORD dwBytesRequired; NTSTATUS NtStatus; BOOL fAllocatedBuffer = FALSE; NtStatus = NtQueryInformationToken( hUserToken, // Handle TokenUser, // TokenInformationClass pUser, // TokenInformation sizeof(achBuffer), // TokenInformationLength &dwBytesRequired // ReturnLength ); if (!NT_SUCCESS(NtStatus)) { if (NtStatus != STATUS_BUFFER_TOO_SMALL) { Win4Assert(NtStatus == STATUS_BUFFER_TOO_SMALL); return NULL; } // // Allocate space for the user info // pUser = (PTOKEN_USER) PrivMemAlloc(dwBytesRequired); if (pUser == NULL) { CairoleDebugOut((DEB_ERROR, "Failed to allocate %d bytes\n", dwBytesRequired)); Win4Assert(pUser != NULL); return NULL; } fAllocatedBuffer = TRUE; // // Read in the UserInfo // NtStatus = NtQueryInformationToken( hUserToken, // Handle TokenUser, // TokenInformationClass pUser, // TokenInformation dwBytesRequired, // TokenInformationLength &dwBytesRequired // ReturnLength ); if (!NT_SUCCESS(NtStatus)) { CairoleDebugOut((DEB_ERROR, "Failed to query user info from user token, status = 0x%lx\n", NtStatus)); Win4Assert(NtStatus == STATUS_SUCCESS); PrivMemFree((HANDLE)pUser); return NULL; } } // Alloc buffer for copy of SID dwBytesRequired = RtlLengthSid(pUser->User.Sid); pSid = (PSID) PrivMemAlloc(dwBytesRequired); if (pSid == NULL) { CairoleDebugOut((DEB_ERROR, "Failed to allocate %d bytes\n", dwBytesRequired)); if (fAllocatedBuffer == TRUE) { PrivMemFree((HANDLE)pUser); } return NULL; } // Copy SID NtStatus = RtlCopySid(dwBytesRequired, pSid, pUser->User.Sid); if (fAllocatedBuffer == TRUE) { PrivMemFree((HANDLE)pUser); } if (!NT_SUCCESS(NtStatus)) { CairoleDebugOut((DEB_ERROR, "RtlCopySid failed, status = 0x%lx\n", NtStatus)); Win4Assert(NtStatus != STATUS_SUCCESS); PrivMemFree(pSid); pSid = NULL; } return pSid; } // Initialzed in InitializeSCM during boot. CRITICAL_SECTION ShellQueryCS; HANDLE GetShellProcessToken( ULONG ulSessionId ) { NTSTATUS NtStatus; BOOL bStatus; HKEY hReg; LONG RegStatus; DWORD RegSize, RegType; WCHAR * pwszImageName; WCHAR * pwszSearch; WCHAR * pwszNext; WCHAR * pNull; DWORD Pid; DWORD n; BYTE StackInfoBuffer[4096]; PBYTE pProcessInfoBuffer; ULONG ProcessInfoBufferSize; ULONG TotalOffset; HANDLE hProcess; HANDLE hToken; PSYSTEM_PROCESS_INFORMATION pProcessInfo; static HANDLE hShellProcess = 0; static HANDLE hShellProcessToken = 0; static WCHAR * pwszShellRegValue = 0; static WCHAR ** apwszShells = 0; static DWORD nShells = 0; EnterCriticalSection( &ShellQueryCS ); if ( ! pwszShellRegValue ) { nShells = 0; // // This code follows logic similar to userinit for finding the name of // the shell process. // RegStatus = RegOpenKeyEx( HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", 0, KEY_READ, &hReg ); if ( ERROR_SUCCESS == RegStatus ) { RegStatus = ReadStringValue( hReg, L"Shell", &pwszShellRegValue ); RegCloseKey( hReg ); } if ( RegStatus != ERROR_SUCCESS ) pwszShellRegValue = L"explorer.exe"; pwszSearch = pwszShellRegValue; for ( ;; ) { if ( ! FindExeComponent( pwszSearch, L" ,", &pNull, &pwszNext ) ) break; nShells++; while ( *pwszNext && *pwszNext != L',' ) pwszNext++; pwszSearch = pwszNext; } apwszShells = (WCHAR **) PrivMemAlloc( nShells * sizeof(WCHAR **) ); if ( ! apwszShells ) { LeaveCriticalSection( &ShellQueryCS ); return NULL; } for ( pwszSearch = pwszShellRegValue, n = 0; n < nShells; n++ ) { FindExeComponent( pwszSearch, L" ,", &apwszShells[n], &pwszNext ); pNull = pwszNext; while ( *pwszNext && *pwszNext != L',' ) pwszNext++; // // When using the const string L"explorer.exe" we can't // automatically write a null or we'll AV on the read only memory. // if ( *pNull ) *pNull = 0; pwszSearch = pwszNext; } } // // Make sure the shell process is still alive before using its token. // if ( hShellProcess && !ulSessionId ) { if ( WaitForSingleObject( hShellProcess, 0 ) == WAIT_TIMEOUT ) { LeaveCriticalSection( &ShellQueryCS ); return hShellProcessToken; } CloseHandle( hShellProcessToken ); CloseHandle( hShellProcess ); hShellProcessToken = 0; hShellProcess = 0; } Pid = 0; pProcessInfoBuffer = StackInfoBuffer; ProcessInfoBufferSize = sizeof(StackInfoBuffer); for (;;) { NtStatus = NtQuerySystemInformation( SystemProcessInformation, pProcessInfoBuffer, ProcessInfoBufferSize, NULL ); if ( NtStatus == STATUS_INFO_LENGTH_MISMATCH ) { ProcessInfoBufferSize += 4096; if ( pProcessInfoBuffer != StackInfoBuffer ) PrivMemFree( pProcessInfoBuffer ); pProcessInfoBuffer = (PBYTE) PrivMemAlloc( ProcessInfoBufferSize ); if ( ! pProcessInfoBuffer ) goto AllDone; continue; } if ( ! NT_SUCCESS(NtStatus) ) goto AllDone; break; } pProcessInfo = (PSYSTEM_PROCESS_INFORMATION) pProcessInfoBuffer; TotalOffset = 0; for (;;) { if ( pProcessInfo->ImageName.Buffer && ( pProcessInfo->SessionId == ulSessionId ) ) { pwszImageName = &pProcessInfo->ImageName.Buffer[pProcessInfo->ImageName.Length / sizeof(WCHAR)]; while ( (pwszImageName != pProcessInfo->ImageName.Buffer) && (pwszImageName[-1] != '\\') ) pwszImageName--; for ( n = 0; n < nShells; n++ ) { if ( lstrcmpiW( apwszShells[n], pwszImageName ) == 0 ) { Pid = PtrToUlong(pProcessInfo->UniqueProcessId); break; } } } if ( pProcessInfo->NextEntryOffset == 0 ) break; TotalOffset += pProcessInfo->NextEntryOffset; pProcessInfo = (PSYSTEM_PROCESS_INFORMATION) &pProcessInfoBuffer[TotalOffset]; } AllDone: if ( pProcessInfoBuffer != StackInfoBuffer ) PrivMemFree( pProcessInfoBuffer ); hProcess = 0; hToken = 0; if ( Pid != 0 ) { hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, Pid ); if ( hProcess ) { bStatus = OpenProcessToken( hProcess, TOKEN_ALL_ACCESS, &hToken ); if ( !bStatus ) { CloseHandle( hProcess ); hProcess = 0; hToken = 0; } } } if ( ulSessionId ) { if ( hProcess ) CloseHandle( hProcess ); LeaveCriticalSection( &ShellQueryCS ); return hToken; } hShellProcess = hProcess; hShellProcessToken = hToken; LeaveCriticalSection( &ShellQueryCS ); // Callers should not close this token unless they want to hose us! return hShellProcessToken; } // Global default launch permissions CSecDescriptor* gpDefaultLaunchPermissions; CSecDescriptor* GetDefaultLaunchPermissions() { CSecDescriptor* pSD = NULL; gpClientLock->LockShared(); pSD = gpDefaultLaunchPermissions; if (pSD) pSD->IncRefCount(); gpClientLock->UnlockShared(); return pSD; } void SetDefaultLaunchPermissions(CSecDescriptor* pNewLaunchPerms) { CSecDescriptor* pOldSD = NULL; gpClientLock->LockExclusive(); pOldSD = gpDefaultLaunchPermissions; gpDefaultLaunchPermissions = pNewLaunchPerms; if (gpDefaultLaunchPermissions) gpDefaultLaunchPermissions->IncRefCount(); gpClientLock->UnlockExclusive(); if (pOldSD) pOldSD->DecRefCount(); return; } CSecDescriptor::CSecDescriptor(SECURITY_DESCRIPTOR* pSD) : _lRefs(1) { ASSERT(pSD); _pSD = pSD; // we own it now } CSecDescriptor::~CSecDescriptor() { ASSERT(_lRefs == 0); ASSERT(_pSD); PrivMemFree(_pSD); } void CSecDescriptor::IncRefCount() { ASSERT(_lRefs > 0); LONG lRefs = InterlockedIncrement(&_lRefs); } void CSecDescriptor::DecRefCount() { ASSERT(_lRefs > 0); LONG lRefs = InterlockedDecrement(&_lRefs); if (lRefs == 0) { delete this; } } SECURITY_DESCRIPTOR* CSecDescriptor::GetSD() { ASSERT(_pSD); ASSERT(_lRefs > 0); return _pSD; }