//depot/main/DS/security/winsafer/safecat.c#8 - integrate change 7547 (text) /*++ Copyright (c) 1997-2000 Microsoft Corporation Module Name: safecat.cpp (SAFER SaferComputeTokenFromLevel) Abstract: This module implements the WinSAFER APIs to compute a new restricted token from a more privileged one, utilizing an "Code Authorization Level Object", which specifies the actions to perform to apply the restrictions. Author: Jeffrey Lawson (JLawson) - Nov 1999 Environment: User mode only. Exported Functions: CodeAuthzpGetTokenInformation (private) CodeAuthzpSidInSidAndAttributes (private) CodeAuthzpModifyTokenPermissions (private) CodeAuthzpInvertPrivs (private) SaferComputeTokenFromLevel CompareCodeAuthzObjectWithToken CodeAuthzpGetAuthzObjectRestrictions (private) Revision History: Created - Nov 1999 --*/ #include "pch.h" #pragma hdrstop #include // needed for sertlp.h #include // RtlpDaclAddrSecurityDescriptor #include #include #include "saferp.h" // // Internal prototypes of other functions defined locally within this file. // NTSTATUS NTAPI CodeAuthzpModifyTokenPermissions( IN HANDLE hToken, IN PSID pExplicitSid, IN DWORD dwExplicitPerms, IN PSID pExplicitSid2 OPTIONAL, IN DWORD dwExplicitPerms2 OPTIONAL ); NTSTATUS NTAPI CodeAuthzpModifyTokenOwner( IN HANDLE hToken, IN PSID NewOwnerSid ); BOOL IsSaferDisabled( void ) { static int g_nDisableSafer = -1; // -1 means we didn't check yet // 0 means safer is enabled // 1 means safer is disabled static const UNICODE_STRING KeyNameSafeBoot = RTL_CONSTANT_STRING(L"\\Registry\\MACHINE\\System\\CurrentControlSet\\Control\\SafeBoot\\Option"); static const UNICODE_STRING ValueNameSafeBoot = RTL_CONSTANT_STRING(L"OptionValue"); static const OBJECT_ATTRIBUTES objaSafeBoot = RTL_CONSTANT_OBJECT_ATTRIBUTES(&KeyNameSafeBoot, OBJ_CASE_INSENSITIVE); HANDLE hKey; BYTE ValueBuffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(DWORD)]; PKEY_VALUE_PARTIAL_INFORMATION pKeyValueInformation = (PKEY_VALUE_PARTIAL_INFORMATION)ValueBuffer; DWORD ValueLength; NTSTATUS Status; // // First see if we already checked the registry // if (g_nDisableSafer == 1) { return TRUE; } if (g_nDisableSafer == 0) { return FALSE; } // // This is the only time we check for safeboot by going to the registry // Opening the key for "write" tells us if we are an admin. // Status = NtOpenKey(&hKey, KEY_QUERY_VALUE | KEY_SET_VALUE, (POBJECT_ATTRIBUTES) &objaSafeBoot); if (NT_SUCCESS(Status)) { Status = NtQueryValueKey(hKey, (PUNICODE_STRING) &ValueNameSafeBoot, KeyValuePartialInformation, pKeyValueInformation, sizeof(ValueBuffer), &ValueLength); NtClose(hKey); if (NT_SUCCESS(Status) && pKeyValueInformation->Type == REG_DWORD && pKeyValueInformation->DataLength == sizeof(DWORD)) { // // If the value exists and it's not 0 then we are in one of SafeBoot modes. // Return TRUE in this case to disable the shim infrastructure // if (*((PDWORD) pKeyValueInformation->Data) > 0) { g_nDisableSafer = 1; return TRUE; } } } g_nDisableSafer = 0; return FALSE; } LPVOID NTAPI CodeAuthzpGetTokenInformation( IN HANDLE TokenHandle, IN TOKEN_INFORMATION_CLASS TokenInformationClass ) /*++ Routine Description: Returns a pointer to allocated memory containing a specific type of information class about the specified token. This wrapper function around GetTokenInformation() handles the allocation of memory of the appropriate size needed. Arguments: TokenHandle - specifies the token that should be used to obtain the specified information from. TokenInformationClass - specifies the information class wanted. Return Value: Returns NULL on error. Otherwise caller must free the returned structure with RtlFreeHeap(). --*/ { DWORD dwSize = 128; LPVOID pTokenInfo = NULL; if (ARGUMENT_PRESENT(TokenHandle)) { pTokenInfo = (LPVOID)RtlAllocateHeap(RtlProcessHeap(), 0, dwSize); if (pTokenInfo != NULL) { DWORD dwNewSize; NTSTATUS Status; Status = NtQueryInformationToken( TokenHandle, TokenInformationClass, pTokenInfo, dwSize, &dwNewSize); if (Status == STATUS_BUFFER_TOO_SMALL) { RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) pTokenInfo); pTokenInfo = (LPVOID)RtlAllocateHeap(RtlProcessHeap(), 0, dwNewSize); if (pTokenInfo != NULL) { Status = NtQueryInformationToken( TokenHandle, TokenInformationClass, pTokenInfo, dwNewSize, &dwNewSize); } } if (!NT_SUCCESS(Status)) { RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) pTokenInfo); pTokenInfo = NULL; } } } return pTokenInfo; } BOOLEAN NTAPI CodeAuthzpSidInSidAndAttributes ( IN PSID_AND_ATTRIBUTES SidAndAttributes, IN ULONG SidCount, OPTIONAL IN PSID SePrincipalSelfSid, OPTIONAL IN PSID PrincipalSelfSid, IN PSID Sid, BOOLEAN HonorEnabledAttribute ) /*++ Routine Description: Checks to see if a given SID is in the given token. N.B. The code to compute the length of a SID and test for equality is duplicated from the security runtime since this is such a frequently used routine. This function is mostly copied from the SepSidInSidAndAttributes found in ntos\se\tokendup.c, except it handles PrincipalSelfSid within the list as well as the passed in Sid. SePrincipalSelfSid is also a parameter here, instead of an ntoskrnl global. also the HonorEnabledAttribute argument was added. Arguments: SidAndAttributes - Pointer to the sid and attributes to be examined SidCount - Number of entries in the SidAndAttributes array. SePrincipalSelfSid - This parameter should optionally be the SID that will be replaced with the PrincipalSelfSid if this SID is encountered in any ACE. This SID should be generated from SECURITY_PRINCIPAL_SELF_RID The parameter should be NULL if the object does not represent a principal. PrincipalSelfSid - If the object being access checked is an object which represents a principal (e.g., a user object), this parameter should be the SID of the object. Any ACE containing the constant SECURITY_PRINCIPAL_SELF_RID is replaced by this SID. The parameter should be NULL if the object does not represent a principal. Sid - Pointer to the SID of interest HonorEnabledAttribute - If this argument is TRUE, then only Sids in the SidsAndAttributes array that have the Attribute SE_GROUP_ENABLED set will be processed during the evaluation. Return Value: A value of TRUE indicates that the SID is in the token, FALSE otherwise. --*/ { ULONG i; PISID MatchSid; ULONG SidLength; PSID_AND_ATTRIBUTES TokenSid; ULONG UserAndGroupCount; if (!ARGUMENT_PRESENT( SidAndAttributes ) ) { return(FALSE); } ASSERT(Sid != NULL); // // If Sid is the constant PrincipalSelfSid, // replace it with the passed in PrincipalSelfSid. // if ( ARGUMENT_PRESENT(PrincipalSelfSid) && ARGUMENT_PRESENT(SePrincipalSelfSid) && RtlEqualSid( SePrincipalSelfSid, Sid ) ) { ASSERT(!RtlEqualSid(SePrincipalSelfSid, PrincipalSelfSid)); Sid = PrincipalSelfSid; } // // Get the length of the source SID since this only needs to be computed // once. // SidLength = 8 + (4 * ((PISID)Sid)->SubAuthorityCount); // // Get address of user/group array and number of user/groups. // ASSERT(SidAndAttributes != NULL); TokenSid = SidAndAttributes; UserAndGroupCount = SidCount; // // Scan through the user/groups and attempt to find a match with the // specified SID. // for (i = 0 ; i < UserAndGroupCount ; i++) { if (!HonorEnabledAttribute || (TokenSid->Attributes & SE_GROUP_ENABLED) != 0) { MatchSid = (PISID)TokenSid->Sid; ASSERT(MatchSid != NULL); // // If the SID is the principal self SID, then replace it. // if ( ARGUMENT_PRESENT(SePrincipalSelfSid) && ARGUMENT_PRESENT(PrincipalSelfSid) && RtlEqualSid(SePrincipalSelfSid, MatchSid)) { MatchSid = (PISID) PrincipalSelfSid; } // // If the SID revision and length matches, then compare the SIDs // for equality. // if ((((PISID)Sid)->Revision == MatchSid->Revision) && (SidLength == (8 + (4 * (ULONG)MatchSid->SubAuthorityCount)))) { if (RtlEqualMemory(Sid, MatchSid, SidLength)) { return TRUE; } } } TokenSid++; } return FALSE; } NTSTATUS NTAPI CodeAuthzpModifyTokenPermissions( IN HANDLE hToken, IN PSID pExplicitSid, IN DWORD dwExplicitPerms, IN PSID pExplicitSid2 OPTIONAL, IN DWORD dwExplicitPerms2 OPTIONAL ) /*++ Routine Description: An internal function to make some additional permission modifications on a newly created restricted token. Arguments: hToken - token to modify pExplicitSid - explicitly named SID to add to the token's DACL. dwExplicitPerms - permissions given to the explicitly named SID when it is added to the DACL. pExplicitSid2 - (optional) secondary named SID to add to the DACL. dwExplicitPerms2 - (optional) secondary permissions given to the secondary SID when it is added to the DACL. Return Value: A value of TRUE indicates that the operation was successful, FALSE otherwise. --*/ { NTSTATUS Status = STATUS_SUCCESS; PACL pTokenDacl = NULL; PUCHAR Buffer = NULL; TOKEN_DEFAULT_DACL TokenDefDacl = {0}; ULONG BufferLength = 0; ULONG AclLength = 0; // // Verify that our arguments were supplied. Since this is // an internal function, we just assert instead of doing // real argument checking. // ASSERT(ARGUMENT_PRESENT(hToken)); ASSERT(ARGUMENT_PRESENT(pExplicitSid) && RtlValidSid(pExplicitSid)); ASSERT(!ARGUMENT_PRESENT(pExplicitSid2) || RtlValidSid(pExplicitSid2)); // // Retrieve the default acl in the token. // Status = NtQueryInformationToken( hToken, TokenDefaultDacl, NULL, 0, (PULONG) &BufferLength ); if (Status == STATUS_BUFFER_TOO_SMALL) { // // Allocate memory for the buffer. // Buffer = (PUCHAR) RtlAllocateHeap(RtlProcessHeap(), 0, BufferLength); if (!Buffer) { Status = STATUS_NO_MEMORY; goto ExitHandler; } // // Perform the query again and actually get it. // Status = NtQueryInformationToken( hToken, TokenDefaultDacl, Buffer, BufferLength, (PULONG) &BufferLength ); if (!NT_SUCCESS(Status)) { goto ExitHandler; } AclLength = ((PTOKEN_DEFAULT_DACL) Buffer)->DefaultDacl->AclSize; // // Calculate how much size we might need in the worst case where // we have to enlarge the DACL. // AclLength += (sizeof(ACCESS_ALLOWED_ACE) + RtlLengthSid(pExplicitSid) - sizeof(DWORD)); if (ARGUMENT_PRESENT(pExplicitSid2)) { AclLength += (sizeof(ACCESS_ALLOWED_ACE) + RtlLengthSid(pExplicitSid2) - sizeof(DWORD)); } // // Allocate memory to hold the new acl. // pTokenDacl = (PACL) RtlAllocateHeap(RtlProcessHeap(), 0, AclLength); if (!pTokenDacl) { Status = STATUS_NO_MEMORY; goto ExitHandler; } // // Copy the old acl into allocated memory. // RtlCopyMemory( pTokenDacl, ((PTOKEN_DEFAULT_DACL) Buffer)->DefaultDacl, ((PTOKEN_DEFAULT_DACL) Buffer)->DefaultDacl->AclSize ); // // Set the acl size to the new size. // pTokenDacl->AclSize = (USHORT) AclLength; } else if (!NT_SUCCESS(Status)) { goto ExitHandler; } else { // // If we get here, there's a bug in Nt code. // ASSERT(FALSE); Status = STATUS_UNSUCCESSFUL; goto ExitHandler; } ASSERT(RtlValidAcl(pTokenDacl)); // // Create the new DACL that includes the extra ACEs that we want. // Status = RtlAddAccessAllowedAceEx( pTokenDacl, ACL_REVISION, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, dwExplicitPerms, pExplicitSid ); if (!NT_SUCCESS(Status)) { ASSERT(Status != STATUS_ALLOTTED_SPACE_EXCEEDED); goto ExitHandler; } if (ARGUMENT_PRESENT(pExplicitSid2)) { Status = RtlAddAccessAllowedAceEx( pTokenDacl, ACL_REVISION, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, dwExplicitPerms2, pExplicitSid2 ); if (!NT_SUCCESS(Status)) { ASSERT(Status != STATUS_ALLOTTED_SPACE_EXCEEDED); goto ExitHandler; } } ASSERT(RtlValidAcl(pTokenDacl)); // // Set the Default DACL within the token to the DACL that we built. // RtlZeroMemory(&TokenDefDacl, sizeof(TOKEN_DEFAULT_DACL)); TokenDefDacl.DefaultDacl = pTokenDacl; Status = NtSetInformationToken( hToken, TokenDefaultDacl, &TokenDefDacl, sizeof(TOKEN_DEFAULT_DACL) ); if (!NT_SUCCESS(Status)) { goto ExitHandler; } Status = STATUS_SUCCESS; // success ExitHandler: if (pTokenDacl != NULL) { RtlFreeHeap(RtlProcessHeap(), 0, pTokenDacl); } if (Buffer != NULL) { RtlFreeHeap(RtlProcessHeap(), 0, Buffer); } return Status; } NTSTATUS NTAPI CodeAuthzpModifyTokenOwner( IN HANDLE hToken, IN PSID NewOwnerSid ) { NTSTATUS Status; TOKEN_OWNER tokenowner; // // Verify that we have our arguments. // if (!ARGUMENT_PRESENT(hToken) || !ARGUMENT_PRESENT(NewOwnerSid)) { Status = STATUS_INVALID_PARAMETER; goto ExitHandler; } // // Set the owner of the Token. // RtlZeroMemory(&tokenowner, sizeof(TOKEN_OWNER)); tokenowner.Owner = NewOwnerSid; Status = NtSetInformationToken(hToken, TokenOwner, &tokenowner, sizeof(TOKEN_OWNER)); ExitHandler: return Status; } BOOLEAN NTAPI CodeAuthzpInvertPrivs( IN HANDLE InAccessToken, IN DWORD dwNumInvertedPrivs, IN PLUID_AND_ATTRIBUTES pInvertedPrivs, OUT PDWORD dwOutNumPrivs, OUT PLUID_AND_ATTRIBUTES *pResultingPrivs ) /*++ Routine Description: Arguments: InAccessToken - dwNumInvertedPrivs - pInvertedPrivs - dwOutNumPrivs - pResultingPrivs - Return Value: Returns FALSE on error, TRUE on success. --*/ { PTOKEN_PRIVILEGES pTokenPrivileges; DWORD Index, InnerIndex; // // Obtain the list of currently held privileges. // ASSERT( ARGUMENT_PRESENT(InAccessToken) ); pTokenPrivileges = (PTOKEN_PRIVILEGES) CodeAuthzpGetTokenInformation(InAccessToken, TokenPrivileges); if (!pTokenPrivileges) goto ExitHandler; // // Squeeze out any privileges that were specified to us, // leaving only those privileges that weren't specified. // ASSERT( ARGUMENT_PRESENT(pInvertedPrivs) ); for (Index = 0; Index < pTokenPrivileges->PrivilegeCount; Index++) { for (InnerIndex = 0; InnerIndex < dwNumInvertedPrivs; InnerIndex++) { if (RtlEqualMemory(&pTokenPrivileges->Privileges[Index].Luid, &pInvertedPrivs[InnerIndex].Luid, sizeof(LUID)) ) { pTokenPrivileges->PrivilegeCount--; RtlMoveMemory(&pTokenPrivileges->Privileges[Index], &pTokenPrivileges->Privileges[Index + 1], pTokenPrivileges->PrivilegeCount - Index); Index--; break; } } } // // Return the number of final privileges. Also, convert the // TOKEN_PRIVILEGES structure into just a LUID_AND_ATTRIBUTES array. // There will be some unused slack at the end of the used portion // of the array, but that is fine (some array entries have probably // already been squeezed out). // *dwOutNumPrivs = pTokenPrivileges->PrivilegeCount; RtlMoveMemory(pTokenPrivileges, &pTokenPrivileges->Privileges[0], pTokenPrivileges->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES) ); *pResultingPrivs = (PLUID_AND_ATTRIBUTES) pTokenPrivileges; return TRUE; ExitHandler: return FALSE; } NTSTATUS NTAPI __CodeAuthzpComputeAccessTokenFromCodeAuthzObject ( IN PAUTHZLEVELTABLERECORD pLevelRecord, IN HANDLE InAccessToken OPTIONAL, OUT PHANDLE OutAccessToken, IN DWORD dwFlags, IN LPVOID lpReserved, IN DWORD dwSaferIdentFlags OPTIONAL ) /*++ Routine Description: Uses the specified WinSafer Level to apply various restrictions or modifications to the specified InAccessToken to produce a Restricted Token that can be used to execute processes with. Alternatively, the returned Restricted Token can be used for thread impersonation to selectively perform operations within a less-privileged environment. Arguments: pLevelRecord - the record structure of the Level to evaluate. InAccessToken - Optionally specifies the input Token that will be modified with restrictions. If this argument is NULL, then the Token for the currently executing process will be opened and used. OutAccessToken - Specifies the memory region to receive the resulting Restricted Token. dwFlags - Specifies additional flags that can be used to control the restricted token creation: SAFER_TOKEN_MAKE_INERT - SAFER_TOKEN_NULL_IF_EQUAL - SAFER_TOKEN_WANT_FLAGS - lpReserved - extra parameter used for some dwFlag combinations. dwSaferIdentFlags - extra SaferFlags bits derived from the matched Code Identifier record entry. These extra bits are ORed to combine them with the SaferFlags associated with the Level. Return Value: Returns -1 if the input Level record is the Disallowed level. Returns STATUS_SUCCESS on a successful operation, otherwise the errorcode of the failure that occurred. --*/ { SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY; NTSTATUS Status; BOOL InAccessTokenWasSupplied = FALSE; HANDLE RestrictedToken = NULL; DWORD FinalFilterFlags; DWORD SaferFlags; BOOL InertStateChanged = FALSE; PSID restrictedSid = NULL; PTOKEN_USER pTokenUser = NULL; PSID principalSelfSid = NULL; DWORD FinalDisabledSidCount; PSID_AND_ATTRIBUTES FinalSidsToDisable = NULL; BOOL FreeFinalDisabledSids = FALSE; DWORD FinalRestrictedSidCount; PSID_AND_ATTRIBUTES FinalSidsToRestrict = NULL; BOOL FreeFinalRestrictedSids = FALSE; DWORD FinalPrivsToDeleteCount; PLUID_AND_ATTRIBUTES FinalPrivsToDelete = NULL; BOOL FreeFinalPrivsToDelete = FALSE; OBJECT_ATTRIBUTES ObjAttr = {0}; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService = {0}; SECURITY_DESCRIPTOR sd = {0}; // // Verify that our input arguments were supplied. // if (!ARGUMENT_PRESENT(pLevelRecord)) { Status = STATUS_INVALID_PARAMETER_1; goto ExitHandler; } if (!ARGUMENT_PRESENT(OutAccessToken)) { Status = STATUS_ACCESS_VIOLATION; goto ExitHandler; } // // Ensure that we have the parent token that will be // used for the creation of the restricted token. // if (ARGUMENT_PRESENT(InAccessToken)) { InAccessTokenWasSupplied = TRUE; } else { Status = NtOpenThreadToken(NtCurrentThread(), TOKEN_DUPLICATE | READ_CONTROL | TOKEN_QUERY, TRUE, &InAccessToken); if (!NT_SUCCESS(Status)) { Status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_DUPLICATE | READ_CONTROL | TOKEN_QUERY, &InAccessToken); if (!NT_SUCCESS(Status)) { goto ExitHandler; // could not obtain default token } } } // // Figure out the combined effect of the "SaferFlags". // Also figure out what flags we'll pass to NtFilterToken. // Note that all of the bits within the SaferFlags can be // combined by bitwise-OR, except for the JOBID portion. // FinalFilterFlags = (pLevelRecord->DisableMaxPrivileges ? DISABLE_MAX_PRIVILEGE : 0); if ((dwSaferIdentFlags & SAFER_POLICY_JOBID_MASK) != 0) { SaferFlags = dwSaferIdentFlags | (pLevelRecord->SaferFlags & ~SAFER_POLICY_JOBID_MASK); } else { SaferFlags = pLevelRecord->SaferFlags | dwSaferIdentFlags; } if ((dwFlags & SAFER_TOKEN_MAKE_INERT) != 0 || (SaferFlags & SAFER_POLICY_SANDBOX_INERT) != 0) { SaferFlags |= SAFER_POLICY_SANDBOX_INERT; FinalFilterFlags |= SANDBOX_INERT; } // // Retrieve the User's personal SID. // (user's SID is accessible afterwards with "pTokenUser->User.Sid") // pTokenUser = (PTOKEN_USER) CodeAuthzpGetTokenInformation( InAccessToken, TokenUser ); if (pTokenUser == NULL) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler; } // // Quick check to see if we can expect a change in the // token's "Sandbox Inert" state to occur. // { ULONG bIsInert = 0; ULONG ulReturnLength; Status = NtQueryInformationToken( InAccessToken, TokenSandBoxInert, &bIsInert, sizeof(bIsInert), &ulReturnLength); if (NT_SUCCESS(Status) && bIsInert) { if ( (dwFlags & SAFER_TOKEN_NULL_IF_EQUAL) != 0) { // The output token was not made any more restrictive during // this operation, so pass back NULL and return success. *OutAccessToken = NULL; Status = STATUS_SUCCESS; goto ExitHandler; } else { SecurityQualityOfService.Length = sizeof( SECURITY_QUALITY_OF_SERVICE ); SecurityQualityOfService.ImpersonationLevel = SecurityAnonymous; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; Status = RtlCreateSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION ); if (!NT_SUCCESS(Status)) { goto ExitHandler; } Status = RtlSetOwnerSecurityDescriptor( &sd, pTokenUser->User.Sid, FALSE ); if (!NT_SUCCESS(Status)) { goto ExitHandler; } InitializeObjectAttributes( &ObjAttr, NULL, OBJ_INHERIT, NULL, &sd ); ObjAttr.SecurityQualityOfService = &SecurityQualityOfService; Status = NtDuplicateToken( InAccessToken, TOKEN_ALL_ACCESS, &ObjAttr, FALSE, TokenPrimary, OutAccessToken ); goto ExitHandler; } } else { if ((FinalFilterFlags & SANDBOX_INERT) != 0) { // the input token was not "SandBox Inert" and // we're being requested to make it. InertStateChanged = TRUE; } } } // // If this is not allowed to execute, then break out now. // if (pLevelRecord->DisallowExecution) { Status = -1; // special status code goto ExitHandler; } // // Process PrivsToDelete inversion. // if (pLevelRecord->InvertDeletePrivs != FALSE) { if (!CodeAuthzpInvertPrivs( InAccessToken, pLevelRecord->DeletePrivilegeUsedCount, pLevelRecord->PrivilegesToDelete, &FinalPrivsToDeleteCount, &FinalPrivsToDelete)) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler; } FreeFinalPrivsToDelete = TRUE; } else { FinalPrivsToDeleteCount = pLevelRecord->DeletePrivilegeUsedCount; FinalPrivsToDelete = pLevelRecord->PrivilegesToDelete; } // // Process SidsToDisable inversion. // if (pLevelRecord->InvertDisableSids != FALSE) { if (!CodeAuthzpInvertAndAddSids( InAccessToken, pTokenUser->User.Sid, pLevelRecord->DisableSidUsedCount, pLevelRecord->SidsToDisable, 0, NULL, &FinalDisabledSidCount, &FinalSidsToDisable)) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler; } FreeFinalDisabledSids = TRUE; } else { if (pLevelRecord->DisableSidUsedCount == 0 || pLevelRecord->SidsToDisable == NULL) { FinalSidsToDisable = NULL; FinalDisabledSidCount = 0; FreeFinalDisabledSids = FALSE; } else { if (!CodeAuthzpExpandWildcardList( InAccessToken, pTokenUser->User.Sid, pLevelRecord->DisableSidUsedCount, pLevelRecord->SidsToDisable, &FinalDisabledSidCount, &FinalSidsToDisable)) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler; } FreeFinalDisabledSids = TRUE; } } // // Process RestrictingSids inversion. // if (pLevelRecord->RestrictedSidsInvUsedCount != 0) { if (!CodeAuthzpInvertAndAddSids( InAccessToken, pTokenUser->User.Sid, pLevelRecord->RestrictedSidsInvUsedCount, pLevelRecord->RestrictedSidsInv, pLevelRecord->RestrictedSidsAddedUsedCount, pLevelRecord->RestrictedSidsAdded, &FinalRestrictedSidCount, &FinalSidsToRestrict)) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler; } FreeFinalRestrictedSids = TRUE; } else { FinalRestrictedSidCount = pLevelRecord->RestrictedSidsAddedUsedCount; FinalSidsToRestrict = pLevelRecord->RestrictedSidsAdded; } // // In some cases, we can bail out early if we were called with // the compare-only flag, and we know that there should not be // any actual changes being made to the token. // if (!InertStateChanged && FinalDisabledSidCount == 0 && FinalPrivsToDeleteCount == 0 && FinalRestrictedSidCount == 0 && (FinalFilterFlags & DISABLE_MAX_PRIVILEGE) == 0) { if ( (dwFlags & SAFER_TOKEN_NULL_IF_EQUAL) != 0) { // The output token was not made any more restrictive during // this operation, so pass back NULL and return success. *OutAccessToken = NULL; Status = STATUS_SUCCESS; goto ExitHandler; } else { // OPTIMIZATION: for this case we can consider using DuplicateToken } } // // Create the actual restricted token. // if (!CreateRestrictedToken( InAccessToken, // handle to existing token FinalFilterFlags, // privilege options and inert FinalDisabledSidCount, // number of deny-only SIDs FinalSidsToDisable, // deny-only SIDs FinalPrivsToDeleteCount, // number of privileges FinalPrivsToDelete, // privileges FinalRestrictedSidCount, // number of restricting SIDs FinalSidsToRestrict, // list of restricting SIDs &RestrictedToken // handle to new token )) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler; } // // If the caller requested SAFER_TOKEN_NULL_IF_EQUAL // then do the evaluation now. // Notice that NtCompareTokens intentionally does not // consider possible differences in the SandboxInert // flag, so we have to handle that case ourself. // if ( (dwFlags & SAFER_TOKEN_NULL_IF_EQUAL) != 0 && !InertStateChanged ) { BOOLEAN bResult = FALSE; Status = NtCompareTokens(InAccessToken, RestrictedToken, &bResult); if (!NT_SUCCESS(Status)) { // An error occurred during the comparison. goto ExitHandler; } if (bResult) { // The output token was not made any more restrictive during // this operation, so pass back NULL and return success. *OutAccessToken = NULL; Status = STATUS_SUCCESS; goto ExitHandler; } } // // Build the "Restricted Code" SID. // Status = RtlAllocateAndInitializeSid( &SIDAuth, 1, SECURITY_RESTRICTED_CODE_RID, 0, 0, 0, 0, 0, 0, 0, &restrictedSid); if (! NT_SUCCESS(Status) ) goto ExitHandler; // // Build the "Principal Self" SID. // Status = RtlAllocateAndInitializeSid( &SIDAuth, 1, SECURITY_PRINCIPAL_SELF_RID, 0, 0, 0, 0, 0, 0, 0, &principalSelfSid); if (! NT_SUCCESS(Status) ) goto ExitHandler; // // Duplicate the token into a primary token and simultaneously // update the owner to the user's personal SID, instead of the // user of the current thread token. // { OBJECT_ATTRIBUTES ObjA; HANDLE NewTokenHandle; // // Initialize a SECURITY_ATTRIBUTES and SECURITY_DESCRIPTOR // to force the owner to the personal user SID. // Status = RtlCreateSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION); if (!NT_SUCCESS(Status)) { goto ExitHandler; } Status = RtlSetOwnerSecurityDescriptor( &sd, pTokenUser->User.Sid, FALSE); if (!NT_SUCCESS(Status)) { goto ExitHandler; } // // Only a primary token can be assigned to a process, so // we must duplicate the restricted token so we can ensure // the we can assign it to the new process. // SecurityQualityOfService.Length = sizeof( SECURITY_QUALITY_OF_SERVICE ); SecurityQualityOfService.ImpersonationLevel = SecurityAnonymous; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; InitializeObjectAttributes( &ObjA, NULL, OBJ_INHERIT, NULL, &sd ); ObjA.SecurityQualityOfService = &SecurityQualityOfService; Status = NtDuplicateToken( RestrictedToken, // handle to token to duplicate TOKEN_ALL_ACCESS, // access rights of new token &ObjA, // attributes FALSE, TokenPrimary, // primary or impersonation token &NewTokenHandle // handle to duplicated token ); if (Status == STATUS_INVALID_OWNER) { // If we failed once, then it might be because the new owner // that was specified in the Security Descriptor could not // be set, so retry but without the SD specified. ObjA.SecurityDescriptor = NULL; Status = NtDuplicateToken( RestrictedToken, // handle to token to duplicate TOKEN_ALL_ACCESS, // access rights of new token &ObjA, // attributes FALSE, TokenPrimary, // primary or impersonation token &NewTokenHandle // handle to duplicated token ); } if (!NT_SUCCESS(Status)) { goto ExitHandler; } ASSERT(NewTokenHandle != NULL); NtClose(RestrictedToken); RestrictedToken = NewTokenHandle; } // // Modify permissions on the token. This involves: // 1) edit the DACL on the token to explicitly grant the special // permissions to the User SID and to the Restricted SID. // 2) optionally change owner to specified SID. // { PSID defaultOwner = ( (pLevelRecord->DefaultOwner != NULL && RtlEqualSid(pLevelRecord->DefaultOwner, principalSelfSid)) ? pTokenUser->User.Sid : pLevelRecord->DefaultOwner); Status = CodeAuthzpModifyTokenPermissions( RestrictedToken, // token to modify. pTokenUser->User.Sid, // explicitly named SID to add to the DACL. GENERIC_ALL, (pLevelRecord->dwLevelId < SAFER_LEVELID_NORMALUSER ? restrictedSid : NULL), // optional secondary named SID to add to the DACL GENERIC_ALL ); if (NT_SUCCESS(Status) && defaultOwner != NULL) { Status = CodeAuthzpModifyTokenOwner( RestrictedToken, defaultOwner); } if (!NT_SUCCESS(Status)) { NtClose(RestrictedToken); goto ExitHandler; } } // // Return the result. // ASSERT(OutAccessToken != NULL); *OutAccessToken = RestrictedToken; RestrictedToken = NULL; Status = STATUS_SUCCESS; // // Cleanup and epilogue code. // ExitHandler: if (RestrictedToken != NULL) NtClose(RestrictedToken); if (pTokenUser != NULL) RtlFreeHeap(RtlProcessHeap(), 0, pTokenUser); if (restrictedSid != NULL) RtlFreeSid(restrictedSid); if (principalSelfSid != NULL) RtlFreeSid(principalSelfSid); if (FreeFinalDisabledSids) RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) FinalSidsToDisable); if (FreeFinalRestrictedSids) RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) FinalSidsToRestrict); if (FreeFinalPrivsToDelete) RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) FinalPrivsToDelete); // // If the caller specified SAFER_TOKEN_WANT_SAFERFLAGS then we // need to copy the JobFlags value into the lpReserved parameter. // if ( Status == STATUS_SUCCESS && (dwFlags & SAFER_TOKEN_WANT_FLAGS) != 0 ) { if (ARGUMENT_PRESENT(lpReserved)) { *((LPDWORD)lpReserved) = SaferFlags; } } // // Close the process token if it wasn't supplied and we opened it. // if (!InAccessTokenWasSupplied && InAccessToken != NULL) NtClose(InAccessToken); return Status; } NTSTATUS NTAPI __CodeAuthzpCompareCodeAuthzLevelWithToken( IN PAUTHZLEVELTABLERECORD pLevelRecord, IN HANDLE InAccessToken OPTIONAL, IN LPDWORD lpResultWord ) /*++ Routine Description: Performs a "light-weight" evaluation of the token manipulations that would be performed if the InAccessToken were restricted with the specified WinSafer Level. The return code indicates if any modifications would actually be done to the token (ie: a distinctly less-privileged token would be created). This function is intended to be used to decide if a DLL (with the specified WinSafer Level) is authorized enough to be loaded into the specified process context handle, but without actually having to create a restricted token since a separate token won't actually be needed. Arguments: pLevelRecord - the record structure of the Level to evaluate. InAccessToken - optionally the access token to use as a parent token. If this argument is not supplied, then the current process token will be opened and used. lpResultWord - receives the result of the evaluation when function is successful (value is left indeterminate if not successful). This result will be value 1 if the level is equal or more privileged than the InAccessToken, or value -1 if the level is less privileged (more restrictions necessary). Return Value: Returns STATUS_SUCCESS on successful evaluation, otherwise returns the error status code. When successful, lpResultWord receives the result of the evaluation. --*/ { SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY; NTSTATUS Status; BOOLEAN TokenWasSupplied = FALSE; DWORD Index; PTOKEN_USER pTokenUser = NULL; PSID principalSelfSid = NULL; PTOKEN_PRIVILEGES pTokenPrivs = NULL; PTOKEN_GROUPS pTokenGroups = NULL; PTOKEN_GROUPS pTokenRestrictedSids = NULL; DWORD FinalDisabledSidCount; PSID_AND_ATTRIBUTES FinalSidsToDisable; BOOLEAN FreeFinalDisabledSids = FALSE; DWORD FinalRestrictedSidCount; PSID_AND_ATTRIBUTES FinalSidsToRestrict; BOOLEAN FreeFinalRestrictedSids = FALSE; DWORD FinalPrivsToDeleteCount; PLUID_AND_ATTRIBUTES FinalPrivsToDelete; BOOLEAN FreeFinalPrivsToDelete = FALSE; ULONG bIsInert = 0; ULONG ulReturnLength; // // Ensure that we have a place to write the result. // if (!ARGUMENT_PRESENT(pLevelRecord)) { Status = STATUS_INVALID_PARAMETER_1; goto ExitHandler; } if (!ARGUMENT_PRESENT(lpResultWord)) { Status = STATUS_ACCESS_VIOLATION; goto ExitHandler; } // // Ensure that we have the token that will be // used for the comparison test. // if (ARGUMENT_PRESENT(InAccessToken)) { TokenWasSupplied = TRUE; } else { Status = NtOpenThreadToken(NtCurrentThread(), TOKEN_DUPLICATE | READ_CONTROL | TOKEN_QUERY, TRUE, &InAccessToken); if (!NT_SUCCESS(Status)) { Status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_DUPLICATE | READ_CONTROL | TOKEN_QUERY, &InAccessToken); if (!NT_SUCCESS(Status)) { goto ExitHandler; // could not obtain default token } } } // // check if token is inert - if so, this object is definitely not more restrictive // Status = NtQueryInformationToken( InAccessToken, TokenSandBoxInert, &bIsInert, sizeof(bIsInert), &ulReturnLength); if (NT_SUCCESS(Status)) { if ( bIsInert ) { *lpResultWord = +1; goto ExitHandler2; } } else { Status = STATUS_UNSUCCESSFUL; goto ExitHandler2; } // // If this is not allowed to execute, then break out now and return LESS. // if (pLevelRecord->DisallowExecution) { *lpResultWord = (DWORD) -1; // Less priv'ed. Status = STATUS_SUCCESS; goto ExitHandler2; } // // Evaluate the privileges that should be deleted. // if (pLevelRecord->InvertDeletePrivs != FALSE) { if (!CodeAuthzpInvertPrivs( InAccessToken, pLevelRecord->DeletePrivilegeUsedCount, pLevelRecord->PrivilegesToDelete, &FinalPrivsToDeleteCount, &FinalPrivsToDelete)) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler2; } FreeFinalPrivsToDelete = TRUE; // // If there are any Privileges that need to be deleted, then // this object definitely less restricted than the token. // if (FinalPrivsToDeleteCount != 0) { *lpResultWord = (DWORD) -1; // Less priv'ed. Status = STATUS_SUCCESS; goto ExitHandler3; } } else { // // Get the list of privileges held by the token. // pTokenPrivs = (PTOKEN_PRIVILEGES) CodeAuthzpGetTokenInformation( InAccessToken, TokenPrivileges); if (!pTokenPrivs) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler2; } // // if PrivsToRemove includes a Privilege not yet disabled, // then return LESS. // for (Index = 0; Index < pLevelRecord->DeletePrivilegeUsedCount; Index++) { DWORD InnerLoop; PLUID pLuid = &pLevelRecord->PrivilegesToDelete[Index].Luid; for (InnerLoop = 0; InnerLoop < pTokenPrivs->PrivilegeCount; InnerLoop++) { if ( RtlEqualMemory(&pTokenPrivs->Privileges[InnerLoop].Luid, pLuid, sizeof(LUID)) ) { *lpResultWord = (DWORD) -1; // Less priv'ed. Status = STATUS_SUCCESS; goto ExitHandler3; } } } } // // Retrieve the User's personal SID. // (user's SID is accessible afterwards with "pTokenUser->User.Sid") // pTokenUser = (PTOKEN_USER) CodeAuthzpGetTokenInformation( InAccessToken, TokenUser); if (pTokenUser == NULL) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler3; } // // Process SidsToDisable inversion. // if (pLevelRecord->InvertDisableSids != FALSE) { if (!CodeAuthzpInvertAndAddSids( InAccessToken, pTokenUser->User.Sid, pLevelRecord->DisableSidUsedCount, pLevelRecord->SidsToDisable, 0, NULL, &FinalDisabledSidCount, &FinalSidsToDisable)) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler3; } FreeFinalDisabledSids = TRUE; } else { if (pLevelRecord->DisableSidUsedCount == 0 || pLevelRecord->SidsToDisable == NULL) { FinalSidsToDisable = NULL; FinalDisabledSidCount = 0; FreeFinalDisabledSids = FALSE; } else { if (!CodeAuthzpExpandWildcardList( InAccessToken, pTokenUser->User.Sid, pLevelRecord->DisableSidUsedCount, pLevelRecord->SidsToDisable, &FinalDisabledSidCount, &FinalSidsToDisable)) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler3; } FreeFinalDisabledSids = TRUE; } } // // Get the list of group membership from the token. // pTokenGroups = (PTOKEN_GROUPS) CodeAuthzpGetTokenInformation( InAccessToken, TokenGroups); if (!pTokenGroups) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler3; } // // Build the "Principal Self" SID. // Status = RtlAllocateAndInitializeSid( &SIDAuth, 1, SECURITY_PRINCIPAL_SELF_RID, 0, 0, 0, 0, 0, 0, 0, &principalSelfSid); if (! NT_SUCCESS(Status) ) { goto ExitHandler3; } // // if SidsToDisable includes a SID in Groups that is not // yet disabled, then return LESS. // for (Index = 0; Index < FinalDisabledSidCount; Index++) { if (CodeAuthzpSidInSidAndAttributes ( pTokenGroups->Groups, pTokenGroups->GroupCount, principalSelfSid, pTokenUser->User.Sid, FinalSidsToDisable[Index].Sid, TRUE)) // check only SIDs that are still enabled { Status = STATUS_SUCCESS; *lpResultWord = (DWORD) -1; // Less priv'ed. goto ExitHandler3; } } // // Process RestrictingSids inversion. // if (pLevelRecord->RestrictedSidsInvUsedCount != 0) { if (!CodeAuthzpInvertAndAddSids( InAccessToken, pTokenUser->User.Sid, pLevelRecord->RestrictedSidsInvUsedCount, pLevelRecord->RestrictedSidsInv, pLevelRecord->RestrictedSidsAddedUsedCount, pLevelRecord->RestrictedSidsAdded, &FinalRestrictedSidCount, &FinalSidsToRestrict)) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler3; } FreeFinalRestrictedSids = TRUE; } else { FinalRestrictedSidCount = pLevelRecord->RestrictedSidsAddedUsedCount; FinalSidsToRestrict = pLevelRecord->RestrictedSidsAdded; } // // Get the existing Restricted SIDs from the token. // pTokenRestrictedSids = (PTOKEN_GROUPS) CodeAuthzpGetTokenInformation( InAccessToken, TokenRestrictedSids); if (!pTokenRestrictedSids) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler3; } if (pTokenRestrictedSids->GroupCount != 0) { // // If there are currently no Restricting SIDs and we // have to add any, then return LESS. // if (pTokenRestrictedSids->GroupCount == 0 && FinalRestrictedSidCount != 0) { *lpResultWord = (DWORD) -1; // Less priv'ed. Status = STATUS_SUCCESS; goto ExitHandler3; } // // If the token already includes a Restricting SID that is // not in RestrictedSidsAdded then return LESS. // for (Index = 0; Index < pTokenRestrictedSids->GroupCount; Index++) { if (!CodeAuthzpSidInSidAndAttributes ( FinalSidsToRestrict, FinalRestrictedSidCount, principalSelfSid, pTokenUser->User.Sid, pTokenRestrictedSids->Groups[Index].Sid, FALSE)) // check all SIDs in the list { *lpResultWord = (DWORD) -1; // Less priv'ed. Status = STATUS_SUCCESS; goto ExitHandler3; } } } else { // // if RestrictedSidsAdded then return LESS. // if (FinalRestrictedSidCount != 0) { *lpResultWord = (DWORD) -1; // Less priv'ed. Status = STATUS_SUCCESS; goto ExitHandler3; } } // // If we got here, then the Level is equal or greater // privileged than the access token and is safe to run. // We could conceivably also want to return LESS if the // default owner needs to be changed from what it currently is. // *lpResultWord = +1; Status = STATUS_SUCCESS; // // Cleanup and epilogue code. // ExitHandler3: if (principalSelfSid != NULL) RtlFreeSid(principalSelfSid); if (pTokenRestrictedSids != NULL) RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) pTokenRestrictedSids); if (pTokenGroups != NULL) RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) pTokenGroups); if (pTokenPrivs != NULL) RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) pTokenPrivs); if (pTokenUser != NULL) RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) pTokenUser); if (FreeFinalDisabledSids) RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) FinalSidsToDisable); if (FreeFinalRestrictedSids) RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) FinalSidsToRestrict); if (FreeFinalPrivsToDelete) RtlFreeHeap(RtlProcessHeap(), 0, (LPVOID) FinalPrivsToDelete); ExitHandler2: ExitHandler: if (!TokenWasSupplied && InAccessToken != NULL) NtClose(InAccessToken); return Status; } BOOL WINAPI SaferComputeTokenFromLevel( IN SAFER_LEVEL_HANDLE hLevelObject, IN HANDLE InAccessToken OPTIONAL, OUT PHANDLE OutAccessToken, IN DWORD dwFlags, IN LPVOID lpReserved ) /*++ Routine Description: Uses the specified WinSafer Level handle to apply various restrictions or modifications to the specified InAccessToken to produce a Restricted Token that can be used to execute processes with. Arguments: hLevelObject - the WinSafer Level handle that specifies the restrictions that should be applied. InAccessToken - Optionally specifies the input Token that will be modified with restrictions. If this argument is NULL, then the Token for the currently executing process will be opened and used. OutAccessToken - Specifies the memory region to receive the resulting Restricted Token. dwFlags - Specifies additional flags that can be used to control the restricted token creation. lpReserved - reserved for future use, must be zero. Return Value: A value of TRUE indicates that the operation was successful, FALSE otherwise. --*/ { NTSTATUS Status; PAUTHZLEVELHANDLESTRUCT pLevelStruct; PAUTHZLEVELTABLERECORD pLevelRecord; OBJECT_ATTRIBUTES ObjAttr = {0}; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService = {0}; SECURITY_DESCRIPTOR sd; PTOKEN_USER pTokenUser = NULL; // // Verify our input arguments are minimally okay. // if (!g_bInitializedFirstTime) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler; } if (!ARGUMENT_PRESENT(hLevelObject)) { Status = STATUS_INVALID_HANDLE; goto ExitHandler; } if (IsSaferDisabled()) { Status = STATUS_SUCCESS; if ( (dwFlags & SAFER_TOKEN_NULL_IF_EQUAL) != 0) { // The output token was not made any more restrictive during // this operation, so pass back NULL and return success. *OutAccessToken = NULL; Status = STATUS_SUCCESS; } else { // // Retrieve the User's personal SID. // (user's SID is accessible afterwards with "pTokenUser->User.Sid") // pTokenUser = (PTOKEN_USER) CodeAuthzpGetTokenInformation( InAccessToken, TokenUser ); if (pTokenUser == NULL) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler; } SecurityQualityOfService.Length = sizeof( SECURITY_QUALITY_OF_SERVICE ); SecurityQualityOfService.ImpersonationLevel = SecurityAnonymous; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; Status = RtlCreateSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION ); if (!NT_SUCCESS(Status)) { goto ExitHandler; } Status = RtlSetOwnerSecurityDescriptor( &sd, pTokenUser->User.Sid, FALSE ); if (!NT_SUCCESS(Status)) { goto ExitHandler; } InitializeObjectAttributes( &ObjAttr, NULL, OBJ_INHERIT, NULL, &sd ); ObjAttr.SecurityQualityOfService = &SecurityQualityOfService; Status = NtDuplicateToken( InAccessToken, TOKEN_ALL_ACCESS, &ObjAttr, FALSE, TokenPrimary, OutAccessToken ); } goto ExitHandler; } // // Obtain the pointer to the level handle structure. // RtlEnterCriticalSection(&g_TableCritSec); Status = CodeAuthzHandleToLevelStruct(hLevelObject, &pLevelStruct); if (!NT_SUCCESS(Status)) { goto ExitHandler2; } ASSERT(pLevelStruct != NULL); pLevelRecord = CodeAuthzLevelObjpLookupByLevelId( &g_CodeLevelObjTable, pLevelStruct->dwLevelId); if (!pLevelRecord) { Status = STATUS_INVALID_HANDLE; goto ExitHandler2; } // // Perform the actual computation or comparison operation. // if ((dwFlags & SAFER_TOKEN_COMPARE_ONLY) != 0) { Status = __CodeAuthzpCompareCodeAuthzLevelWithToken( pLevelRecord, InAccessToken, (LPDWORD) lpReserved); } else { Status = __CodeAuthzpComputeAccessTokenFromCodeAuthzObject ( pLevelRecord, InAccessToken, OutAccessToken, dwFlags, lpReserved, pLevelStruct->dwSaferFlags); } // // Cleanup and return code handling. // ExitHandler2: RtlLeaveCriticalSection(&g_TableCritSec); ExitHandler: if (pTokenUser) { LocalFree(pTokenUser); } if (Status == -1) { SetLastError(ERROR_ACCESS_DISABLED_BY_POLICY); return FALSE; } if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); return FALSE; } return TRUE; } BOOL WINAPI IsTokenUntrusted( IN HANDLE hToken ) /*++ Routine Description: Indicate if the token does is not able to access a DACL against the Token User SID. This is typically the case in these situations: - the User SID is disabled (for deny-use only) - there are Restricting SIDs and the User SID is not one of them. The passed token handle must have been opened for TOKEN_QUERY and TOKEN_DUPLICATE access or else the evaluation will fail. Arguments: hToken - Specifies the input Token that will be analyzed. Return Value: Returns TRUE if the token is "untrusted", or FALSE if the token represents a "trusted" token. If an error occurs during the evaluation of this check, the result returned will be TRUE (assumed untrusted). --*/ { BOOL fTrusted = FALSE; DWORD dwStatus; DWORD dwACLSize; DWORD cbps = sizeof(PRIVILEGE_SET); PACL pACL = NULL; DWORD dwUserSidSize; PTOKEN_USER psidUser = NULL; PSECURITY_DESCRIPTOR psdUser = NULL; PRIVILEGE_SET ps; GENERIC_MAPPING gm; HANDLE hImpToken; const int TESTPERM_READ = 1; const int TESTPERM_WRITE = 2; // Prepare some memory ZeroMemory(&ps, sizeof(ps)); ZeroMemory(&gm, sizeof(gm)); // Get the User's SID. if (!GetTokenInformation(hToken, TokenUser, NULL, 0, &dwUserSidSize)) { psidUser = (PTOKEN_USER) LocalAlloc(LPTR, dwUserSidSize); if (psidUser != NULL) { if (GetTokenInformation(hToken, TokenUser, psidUser, dwUserSidSize, &dwUserSidSize)) { // Create the Security Descriptor (SD) psdUser = LocalAlloc(LPTR,SECURITY_DESCRIPTOR_MIN_LENGTH); if (psdUser != NULL) { if(InitializeSecurityDescriptor(psdUser,SECURITY_DESCRIPTOR_REVISION)) { // Compute size needed for the ACL then allocate the // memory for it dwACLSize = sizeof(ACCESS_ALLOWED_ACE) + 8 + GetLengthSid(psidUser->User.Sid) - sizeof(DWORD); pACL = (PACL)LocalAlloc(LPTR, dwACLSize); if (pACL != NULL) { // Initialize the new ACL if(InitializeAcl(pACL, dwACLSize, ACL_REVISION2)) { // Add the access-allowed ACE to the DACL if(AddAccessAllowedAce(pACL,ACL_REVISION2, (TESTPERM_READ | TESTPERM_WRITE),psidUser->User.Sid)) { // Set our DACL to the Administrator's SD if (SetSecurityDescriptorDacl(psdUser, TRUE, pACL, FALSE)) { // AccessCheck is downright picky about what is in the SD, // so set the group and owner SetSecurityDescriptorGroup(psdUser,psidUser->User.Sid,FALSE); SetSecurityDescriptorOwner(psdUser,psidUser->User.Sid,FALSE); // Initialize GenericMapping structure even though we // won't be using generic rights gm.GenericRead = TESTPERM_READ; gm.GenericWrite = TESTPERM_WRITE; gm.GenericExecute = 0; gm.GenericAll = TESTPERM_READ | TESTPERM_WRITE; if (ImpersonateLoggedOnUser(hToken) && OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hImpToken)) { if (!AccessCheck(psdUser, hImpToken, TESTPERM_READ, &gm, &ps,&cbps,&dwStatus,&fTrusted)) fTrusted = FALSE; CloseHandle(hImpToken); } } } } LocalFree(pACL); } } LocalFree(psdUser); } } LocalFree(psidUser); } } RevertToSelf(); return(!fTrusted); } BOOL WINAPI SaferiCompareTokenLevels ( IN HANDLE ClientAccessToken, IN HANDLE ServerAccessToken, OUT PDWORD pdwResult ) /*++ Routine Description: Private function provided to try to empiracally determine if the two access token have been restricted with comparable WinSafer authorization Levels. Arguments: ClientAccessToken - handle to the Access Token of the "client" ServerAccessToken - handle to the Access Token of the "server" pdwResult - When TRUE is returned, the pdwResult output parameter will receive any of the following values: -1 = Client's access token is more authorized than Server's. 0 = Client's access token is comparable level to Server's. 1 = Server's access token is more authorized than Clients's. Return Value: A value of TRUE indicates that the operation was successful, FALSE otherwise. --*/ { NTSTATUS Status; LPVOID RestartKey; PAUTHZLEVELTABLERECORD authzobj; DWORD dwCompareResult; // // Verify our input arguments are minimally okay. // if (!ARGUMENT_PRESENT(ClientAccessToken) || !ARGUMENT_PRESENT(ServerAccessToken)) { Status = STATUS_INVALID_HANDLE; goto ExitHandler; } if (!ARGUMENT_PRESENT(pdwResult)) { Status = STATUS_ACCESS_VIOLATION; goto ExitHandler; } // // Gain the critical section lock and load the tables as needed. // if (!g_bInitializedFirstTime) { Status = STATUS_UNSUCCESSFUL; goto ExitHandler; } RtlEnterCriticalSection(&g_TableCritSec); if (g_bNeedCacheReload) { Status = CodeAuthzpImmediateReloadCacheTables(); if (!NT_SUCCESS(Status)) { goto ExitHandler2; } } if (RtlIsGenericTableEmpty(&g_CodeLevelObjTable)) { Status = STATUS_NOT_FOUND; goto ExitHandler2; } // // Loop through the Authorization Levels and see where we // find the first difference in access rights. // dwCompareResult = 0; RestartKey = NULL; for (authzobj = (PAUTHZLEVELTABLERECORD) RtlEnumerateGenericTableWithoutSplaying( &g_CodeLevelObjTable, &RestartKey); authzobj != NULL; authzobj = (PAUTHZLEVELTABLERECORD) RtlEnumerateGenericTableWithoutSplaying( &g_CodeLevelObjTable, &RestartKey)) { DWORD dwClientResult, dwServerResult; Status = __CodeAuthzpCompareCodeAuthzLevelWithToken( authzobj, ClientAccessToken, &dwClientResult); if (!NT_SUCCESS(Status)) { goto ExitHandler2; } Status = __CodeAuthzpCompareCodeAuthzLevelWithToken( authzobj, ServerAccessToken, &dwServerResult); if (!NT_SUCCESS(Status)) { goto ExitHandler2; } if (dwClientResult == (DWORD) -1 && dwServerResult != (DWORD) -1) { dwCompareResult = (DWORD) -1; break; } else if (dwClientResult != (DWORD) -1 && dwServerResult == (DWORD) -1) { dwCompareResult = 1; break; } else if (dwClientResult != (DWORD) -1 && dwServerResult != (DWORD) -1) { dwCompareResult = 0; break; } } Status = STATUS_SUCCESS; *pdwResult = dwCompareResult; // // Cleanup and return code handling. // ExitHandler2: RtlLeaveCriticalSection(&g_TableCritSec); ExitHandler: if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); return FALSE; } return TRUE; }