//+------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1997. // // File: security.cxx // // Contents: // //-------------------------------------------------------------------------- #include "act.hxx" #include #include #define SECURITY_WIN32 #define SECURITY_KERBEROS #include #include // the constant generic mapping structure GENERIC_MAPPING sGenericMapping = { READ_CONTROL, READ_CONTROL, READ_CONTROL, READ_CONTROL}; // Well-known low-privilege service account sids const SID sidLocalSystem = {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID }; const SID sidLocalService = {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SERVICE_RID }; const SID sidNetworkService = {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_NETWORK_SERVICE_RID }; //------------------------------------------------------------------------- // // 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; // // Some history here: we used to say, if pToken is NULL (implying an // unsecure activation), parse the security descriptor to see if // Everyone is granted access, and if so, allow the activation to // proceed. This was a DCOM-specific policy decision. In .NET Server // we changed instead to mapping an unsecure (unauthenticated) client // to the Anonymous identity. (Another DCOM-specific policy decision, // but it probably makes more sense than the original one). And so we // no longer parse the security descriptor, instead we just do a // straight-forward access check. // 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; } HRESULT IsLowPrivilegeServiceAccount (LPCWSTR pwszDomain, LPCWSTR pwszName, BOOL* pfSvcAccount) { if (!pwszName || !pfSvcAccount) { return E_POINTER; } *pfSvcAccount = FALSE; HRESULT hr = S_OK; BYTE pbSid [sizeof (sidLocalService)] = {0}; DWORD cbSid = sizeof (pbSid); WCHAR wszDomain [DNLEN + 1] = {0}; DWORD cchDomain = sizeof (wszDomain) / sizeof (wszDomain[0]); SID_NAME_USE eUse; LPCWSTR pwszFullName = pwszName; LPWSTR pwszCopyFullName = NULL; // Build the full domain\account string if necessary if (pwszDomain) { SIZE_T cchFullNameLen = lstrlenW (pwszDomain) + 1 + lstrlenW (pwszName) + 1; SafeAllocaAllocate (pwszCopyFullName, cchFullNameLen * sizeof (WCHAR)); if (!pwszCopyFullName) { return E_OUTOFMEMORY; } _snwprintf (pwszCopyFullName, cchFullNameLen, L"%s\\%s", pwszDomain, pwszName); pwszCopyFullName [cchFullNameLen - 1] = L'\0'; pwszFullName = pwszCopyFullName; } // Lookup the sid for the account if (!LookupAccountName(NULL, pwszFullName, pbSid, &cbSid, wszDomain, &cchDomain, &eUse)) { // Either our sid was too small, or a failure occurred. // If the former, returning *pfSvcAccount = FALSE is correct // If the latter, the subsequent call to LogonUser will catch it hr = S_FALSE; } else { // Compare to well known service sids if (eUse == SidTypeWellKnownGroup && (EqualSid (pbSid, (PSID) &sidLocalService) || EqualSid (pbSid, (PSID) &sidNetworkService))) { *pfSvcAccount = TRUE; } } SafeAllocaFree (pwszCopyFullName); return hr; } HRESULT IsLocalSystemAccount(LPCWSTR pwszDomain, LPCWSTR pwszName, BOOL* pfLocalSystemAccount) { if (!pwszName || !pfLocalSystemAccount) { return E_POINTER; } *pfLocalSystemAccount = FALSE; HRESULT hr = S_OK; BYTE pbSid [sizeof (sidLocalSystem)] = {0}; DWORD cbSid = sizeof (pbSid); WCHAR wszDomain [DNLEN + 1] = {0}; DWORD cchDomain = sizeof (wszDomain) / sizeof (wszDomain[0]); SID_NAME_USE eUse; LPCWSTR pwszFullName = pwszName; LPWSTR pwszCopyFullName = NULL; // Build the full domain\account string if necessary if (pwszDomain) { SIZE_T cchFullNameLen = lstrlenW (pwszDomain) + 1 + lstrlenW (pwszName) + 1; SafeAllocaAllocate (pwszCopyFullName, cchFullNameLen * sizeof (WCHAR)); if (!pwszCopyFullName) { return E_OUTOFMEMORY; } _snwprintf (pwszCopyFullName, cchFullNameLen, L"%s\\%s", pwszDomain, pwszName); pwszCopyFullName [cchFullNameLen - 1] = L'\0'; pwszFullName = pwszCopyFullName; } // Lookup the sid for the account if (!LookupAccountName(NULL, pwszFullName, pbSid, &cbSid, wszDomain, &cchDomain, &eUse)) { // Either our sid was too small, or a failure occurred. // If the former, returning *pfLocalSystemAccount = FALSE is correct // If the latter, the subsequent call to LogonUser will catch it hr = S_FALSE; } else { // Compare to well known service sids if (eUse == SidTypeWellKnownGroup && (EqualSid (pbSid, (PSID) &sidLocalSystem))) { *pfLocalSystemAccount = TRUE; } } SafeAllocaFree (pwszCopyFullName); return hr; } HANDLE GetRunAsToken( DWORD clsctx, WCHAR *pwszAppID, WCHAR *pwszRunAsDomainName, WCHAR *pwszRunAsUserName, BOOL fForLaunch) { LSA_OBJECT_ATTRIBUTES sObjAttributes; HANDLE hPolicy = NULL; LSA_UNICODE_STRING sKey; WCHAR wszKey[CLSIDSTR_MAX+5]; PLSA_UNICODE_STRING psPassword; HANDLE hToken; NTSTATUS Status; PKERB_INTERACTIVE_LOGON LogonInfo; ULONG LogonInfoSize = sizeof(KERB_INTERACTIVE_LOGON); STRING Name; ULONG PackageId; TOKEN_SOURCE SourceContext; PKERB_INTERACTIVE_PROFILE Profile = NULL; ULONG ProfileSize; LUID LogonId; QUOTA_LIMITS Quotas; NTSTATUS SubStatus; PUCHAR Where; UNICODE_STRING usRunAsUserName, usRunAsDomainName; PTOKEN_GROUPS TokenGroups = NULL; HRESULT hr = E_FAIL; if ( !pwszAppID ) { // if we have a RunAs, we'd better have an appid.... return 0; } ASSERT(gLSAHandle); ASSERT(gSidService); ASSERT(gpwszDefaultDomainName[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; } // // Special case of NT AUTHORITY\System - cannot use LsaLogonUser to obtain such a // token, so we use our own process token. Note that we currently only support // this on the server registration code-path, and not the server-launch code path. // if (!fForLaunch) { BOOL fLocalSystemAccount = FALSE; hr = IsLocalSystemAccount(pwszRunAsDomainName, pwszRunAsUserName, &fLocalSystemAccount); if (SUCCEEDED(hr)) // keep going here in case of errors, LsaLogonUser will just fail below. { // If SYSTEM and user has supplied a blank password (for compatibility // reasons), use our own token if (fLocalSystemAccount && !lstrcmpiW(psPassword->Buffer, L"")) { BOOL fResult = FALSE; HANDLE hProcess = NULL; HANDLE hProcessToken = NULL; hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId()); if (hProcess) { fResult = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hProcessToken); ASSERT(!fResult || hProcessToken); CloseHandle(hProcess); } return hProcessToken; } } } BOOLEAN b = RtlCreateUnicodeString(&usRunAsUserName, pwszRunAsUserName); if (FALSE == b) { SecureZeroMemory(psPassword->Buffer, psPassword->Length); LsaFreeMemory( psPassword ); return 0; } if ( (pwszRunAsDomainName[0] == L'.') && (pwszRunAsDomainName[1] == L'\0')) b = RtlCreateUnicodeString(&usRunAsDomainName, gpwszDefaultDomainName); else b = RtlCreateUnicodeString(&usRunAsDomainName, pwszRunAsDomainName); if (FALSE == b) { RtlFreeUnicodeString(&usRunAsUserName); SecureZeroMemory(psPassword->Buffer, psPassword->Length); LsaFreeMemory( psPassword ); return 0; } LogonInfoSize += usRunAsUserName.MaximumLength + usRunAsDomainName.MaximumLength + psPassword->MaximumLength ; LogonInfo = (PKERB_INTERACTIVE_LOGON) LocalAlloc(LMEM_ZEROINIT, LogonInfoSize); if (!LogonInfo) { RtlFreeUnicodeString(&usRunAsUserName); RtlFreeUnicodeString(&usRunAsDomainName); SecureZeroMemory(psPassword->Buffer, psPassword->Length); LsaFreeMemory( psPassword ); return NULL; } LogonInfo->MessageType = KerbInteractiveLogon; Where = (PUCHAR) (LogonInfo + 1); LogonInfo->UserName.Buffer = (LPWSTR) Where; LogonInfo->UserName.MaximumLength = usRunAsUserName.MaximumLength ; LogonInfo->UserName.Length = usRunAsUserName.Length ; RtlCopyMemory( LogonInfo->UserName.Buffer, usRunAsUserName.Buffer, usRunAsUserName.MaximumLength ); Where += LogonInfo->UserName.Length + sizeof(WCHAR); LogonInfo->LogonDomainName.Buffer = (LPWSTR) Where ; LogonInfo->LogonDomainName.MaximumLength = usRunAsDomainName.MaximumLength; LogonInfo->LogonDomainName.Length = usRunAsDomainName.Length ; RtlCopyMemory( LogonInfo->LogonDomainName.Buffer, usRunAsDomainName.Buffer, usRunAsDomainName.MaximumLength ); Where += LogonInfo->LogonDomainName.Length + sizeof(WCHAR); LogonInfo->Password.Buffer = (LPWSTR) Where; LogonInfo->Password.MaximumLength = psPassword->MaximumLength; LogonInfo->Password.Length = psPassword->Length - sizeof(WCHAR); // The LSA API retrives length=maxlength RtlCopyMemory( LogonInfo->Password.Buffer, psPassword->Buffer, LogonInfo->Password.MaximumLength ); // Clear the password SecureZeroMemory(psPassword->Buffer, psPassword->Length); LsaFreeMemory( psPassword ); RtlFreeUnicodeString(&usRunAsUserName); RtlFreeUnicodeString(&usRunAsDomainName); Where += LogonInfo->Password.Length + sizeof(WCHAR); strncpy( SourceContext.SourceName, "DCOMSCM",sizeof(SourceContext.SourceName) ); Status = NtAllocateLocallyUniqueId( &SourceContext.SourceIdentifier ); if (!NT_SUCCESS(Status)) { SecureZeroMemory(LogonInfo, LogonInfoSize); LocalFree(LogonInfo); return NULL ; } RtlInitString( &Name, NEGOSSP_NAME_A); Status = LsaLookupAuthenticationPackage( gLSAHandle, &Name, &PackageId ); if (!NT_SUCCESS(Status)) { SecureZeroMemory(LogonInfo, LogonInfoSize); LocalFree(LogonInfo); return NULL ; } // // Now call LsaLogonUser // RtlInitString( &Name, "DCOMSCM" ); BOOL fSvcAccount = FALSE; if (FAILED (IsLowPrivilegeServiceAccount (pwszRunAsDomainName, pwszRunAsUserName, &fSvcAccount))) { SecureZeroMemory(LogonInfo, LogonInfoSize); LocalFree(LogonInfo); return NULL; } // Service accounts should have blank passwords ASSERT (!fSvcAccount || (psPassword->Buffer && !psPassword->Buffer[0])); #define TOKEN_GROUP_COUNT 1 // if local/network service, no need to add the serivce SID if (!fSvcAccount) { TokenGroups = (PTOKEN_GROUPS)LocalAlloc(LMEM_ZEROINIT, sizeof(TOKEN_GROUPS) + (TOKEN_GROUP_COUNT - ANYSIZE_ARRAY) * sizeof(SID_AND_ATTRIBUTES)); if (TokenGroups == NULL) { SecureZeroMemory(LogonInfo, LogonInfoSize); LocalFree(LogonInfo); return NULL ; } // Add the service SID to the token so the resulting // process can impersonate TokenGroups->GroupCount = TOKEN_GROUP_COUNT; TokenGroups->Groups[0].Sid = gSidService; TokenGroups->Groups[0].Attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT; } Status = LsaLogonUser( gLSAHandle, &Name, fSvcAccount ? Service : Batch, PackageId, LogonInfo, LogonInfoSize, TokenGroups, //NULL for service accounts &SourceContext, (PVOID *) &Profile, &ProfileSize, &LogonId, &hToken, &Quotas, &SubStatus ); SecureZeroMemory(LogonInfo, LogonInfoSize); LocalFree(LogonInfo); LocalFree(TokenGroups); // Log the specifed user on if (!NT_SUCCESS(Status)) { // 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; } else { if (Profile) { LsaFreeReturnBuffer(Profile); } } return hToken; } BOOL DuplicateTokenAsPrimary( HANDLE hUserToken, PSID psidUserSid, HANDLE *hPrimaryToken ) { OBJECT_ATTRIBUTES ObjectAttributes; PSECURITY_DESCRIPTOR psdNewProcessTokenSD; NTSTATUS NtStatus; *hPrimaryToken = NULL; if (hUserToken == NULL) { return(FALSE); } // // Create the security descriptor that we want to put in the Token. // 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); } InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, psdNewProcessTokenSD ); NtStatus = NtDuplicateToken( hUserToken, // Duplicate this token TOKEN_ALL_ACCESS, // Give me this access to the resulting token &ObjectAttributes, FALSE, // EffectiveOnly TokenPrimary, // TokenType hPrimaryToken // 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; } 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) { ASSERT(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)); ASSERT(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)); ASSERT(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)); ASSERT(NtStatus != STATUS_SUCCESS); PrivMemFree(pSid); pSid = NULL; } return pSid; } HANDLE GetUserTokenForSession( ULONG ulSessionId ) { BOOL fRet = FALSE; HANDLE hToken = NULL; // // We used to have a lot of complicated code for doing this // logic. The WTSQueryUserToken api replaces all of that // quite nicely. Only downside is that wtsapi32.dll has a // moderately large dependency list. Since under normal // conditions we won't need to use this api until after someone // logs on, we delay-load link to wtsapi32 to avoid the load // hit during boot. // fRet = WTSQueryUserToken(ulSessionId, &hToken); if (!fRet) { return NULL; } ASSERT(hToken); return hToken; } // 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; }