/*++ Copyright (c) 1989 Microsoft Corporation Module Name: Accessck.c Abstract: This Module implements the access check procedures. Both NtAccessCheck and SeAccessCheck check to is if a user (denoted by an input token) can be granted the desired access rights to object protected by a security descriptor and an optional object owner. Both procedures use a common local procedure to do the test. Author: Robert Reichel (RobertRe) 11-30-90 Environment: Kernel Mode Revision History: Richard Ward (RichardW) 14-Apr-92 Changed ACE_HEADER --*/ #include "pch.h" #pragma hdrstop #include // // Define the local macros and procedure for this module // #if DBG extern BOOLEAN SepDumpSD; extern BOOLEAN SepDumpToken; BOOLEAN SepShowAccessFail; #endif // DBG VOID SepUpdateParentTypeList ( IN PIOBJECT_TYPE_LIST ObjectTypeList, IN ULONG ObjectTypeListLength, IN ULONG StartIndex ); typedef enum { UpdateRemaining, UpdateCurrentGranted, UpdateCurrentDenied } ACCESS_MASK_FIELD_TO_UPDATE; VOID SepAddAccessTypeList ( IN PIOBJECT_TYPE_LIST ObjectTypeList, IN ULONG ObjectTypeListLength, IN ULONG StartIndex, IN ACCESS_MASK AccessMask, IN ACCESS_MASK_FIELD_TO_UPDATE FieldToUpdate ); NTSTATUS SeAccessCheckByType ( IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN PSID PrincipalSelfSid, IN HANDLE ClientToken, IN ACCESS_MASK DesiredAccess, IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL, IN ULONG ObjectTypeListLength, IN PGENERIC_MAPPING GenericMapping, OUT PPRIVILEGE_SET PrivilegeSet, IN OUT PULONG PrivilegeSetLength, OUT PACCESS_MASK GrantedAccess, OUT PNTSTATUS AccessStatus, IN BOOLEAN ReturnResultList ); VOID SepMaximumAccessCheck( IN PTOKEN EToken, IN PTOKEN PrimaryToken, IN PACL Dacl, IN PSID PrincipalSelfSid, IN ULONG LocalTypeListLength, IN PIOBJECT_TYPE_LIST LocalTypeList, IN ULONG ObjectTypeListLength, IN BOOLEAN Restricted ); VOID SepNormalAccessCheck( IN ACCESS_MASK Remaining, IN PTOKEN EToken, IN PTOKEN PrimaryToken, IN PACL Dacl, IN PSID PrincipalSelfSid, IN ULONG LocalTypeListLength, IN PIOBJECT_TYPE_LIST LocalTypeList, IN ULONG ObjectTypeListLength, IN BOOLEAN Restricted ); BOOLEAN SepSidInTokenEx ( IN PACCESS_TOKEN AToken, IN PSID PrincipalSelfSid, IN PSID Sid, IN BOOLEAN DenyAce, IN BOOLEAN Restricted ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,SeCaptureObjectTypeList) #pragma alloc_text(PAGE,SeFreeCapturedObjectTypeList) #pragma alloc_text(PAGE,SepUpdateParentTypeList) #pragma alloc_text(PAGE,SepObjectInTypeList) #pragma alloc_text(PAGE,SepAddAccessTypeList) #pragma alloc_text(PAGE,SepSidInToken) #pragma alloc_text(PAGE,SepSidInTokenEx) #pragma alloc_text(PAGE,SepAccessCheck) #pragma alloc_text(PAGE,NtAccessCheck) #pragma alloc_text(PAGE,NtAccessCheckByType) #pragma alloc_text(PAGE,NtAccessCheckByTypeResultList) #pragma alloc_text(PAGE,SeAccessCheckByType) #pragma alloc_text(PAGE,SeFreePrivileges) #pragma alloc_text(PAGE,SeAccessCheck) #pragma alloc_text(PAGE,SePrivilegePolicyCheck) #pragma alloc_text(PAGE,SepTokenIsOwner) #pragma alloc_text(PAGE,SeFastTraverseCheck) #pragma alloc_text(PAGE,SepMaximumAccessCheck) #pragma alloc_text(PAGE,SepNormalAccessCheck) #pragma alloc_text(PAGE,SeMaximumAuditMask) #endif NTSTATUS SeCaptureObjectTypeList ( IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL, IN ULONG ObjectTypeListLength, IN KPROCESSOR_MODE RequestorMode, OUT PIOBJECT_TYPE_LIST *CapturedObjectTypeList ) /*++ Routine Description: This routine probes and captures a copy of any object type list that might have been provided via the ObjectTypeList argument. The object type list is converted to the internal form that explicitly specifies the hierarchical relationship between the entries. The object typs list is validated to ensure a valid hierarchical relationship is represented. Arguments: ObjectTypeList - The object type list from which the type list information is to be retrieved. ObjectTypeListLength - Number of elements in ObjectTypeList RequestorMode - Indicates the processor mode by which the access is being requested. CapturedObjectTypeList - Receives the captured type list which must be freed using SeFreeCapturedObjectTypeList(). Return Value: STATUS_SUCCESS indicates no exceptions were encountered. Any access violations encountered will be returned. --*/ { NTSTATUS Status = STATUS_SUCCESS; ULONG i; PIOBJECT_TYPE_LIST LocalTypeList = NULL; ULONG Levels[ACCESS_MAX_LEVEL+1]; PAGED_CODE(); // // Set default return // *CapturedObjectTypeList = NULL; if (RequestorMode != UserMode) { return STATUS_NOT_IMPLEMENTED; } try { if ( ObjectTypeListLength == 0 ) { // Drop through } else if ( !ARGUMENT_PRESENT(ObjectTypeList) ) { Status = STATUS_INVALID_PARAMETER; } else { if ( !IsValidElementCount( ObjectTypeListLength, IOBJECT_TYPE_LIST ) ) { Status = STATUS_INVALID_PARAMETER ; // // No more to do, get out of the try statement: // leave ; } ProbeForRead( ObjectTypeList, sizeof(OBJECT_TYPE_LIST) * ObjectTypeListLength, sizeof(ULONG) ); // // Allocate a buffer to copy into. // LocalTypeList = ExAllocatePoolWithTag( PagedPool, sizeof(IOBJECT_TYPE_LIST) * ObjectTypeListLength, 'tOeS' ); if ( LocalTypeList == NULL ) { Status = STATUS_INSUFFICIENT_RESOURCES; // // Copy the callers structure to the local structure. // } else { GUID * CapturedObjectType; for ( i=0; i ACCESS_MAX_LEVEL ) { Status = STATUS_INVALID_PARAMETER; break; } // // Copy data the caller passed in // LocalTypeList[i].Level = CurrentLevel; LocalTypeList[i].Flags = 0; CapturedObjectType = ObjectTypeList[i].ObjectType; ProbeForReadSmallStructure( CapturedObjectType, sizeof(GUID), sizeof(ULONG) ); LocalTypeList[i].ObjectType = *CapturedObjectType; LocalTypeList[i].Remaining = 0; LocalTypeList[i].CurrentGranted = 0; LocalTypeList[i].CurrentDenied = 0; // // Ensure that the level number is consistent with the // level number of the previous entry. // if ( i == 0 ) { if ( CurrentLevel != 0 ) { Status = STATUS_INVALID_PARAMETER; break; } } else { // // The previous entry is either: // my immediate parent, // my sibling, or // the child (or grandchild, etc.) of my sibling. // if ( CurrentLevel > LocalTypeList[i-1].Level + 1 ) { Status = STATUS_INVALID_PARAMETER; break; } // // Don't support two roots. // if ( CurrentLevel == 0 ) { Status = STATUS_INVALID_PARAMETER; break; } } // // If the above rules are maintained, // then my parent object is the last object seen that // has a level one less than mine. // if ( CurrentLevel == 0 ) { LocalTypeList[i].ParentIndex = -1; } else { LocalTypeList[i].ParentIndex = Levels[CurrentLevel-1]; } // // Save this obect as the last object seen at this level. // Levels[CurrentLevel] = i; } } } // end_if } except(EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } // end_try if ( NT_SUCCESS( Status ) ) { *CapturedObjectTypeList = LocalTypeList; } else { // // If we captured any proxy data, we need to free it now. // if ( LocalTypeList != NULL ) { ExFreePool( LocalTypeList ); } } return Status; } VOID SeFreeCapturedObjectTypeList( IN PVOID ObjectTypeList ) /*++ Routine Description: This routine frees the data associated with a captured ObjectTypeList structure. Arguments: ObjectTypeList - Points to a captured object type list structure. Return Value: None. --*/ { PAGED_CODE(); if ( ObjectTypeList != NULL ) { ExFreePool( ObjectTypeList ); } return; } BOOLEAN SepObjectInTypeList ( IN GUID *ObjectType, IN PIOBJECT_TYPE_LIST ObjectTypeList, IN ULONG ObjectTypeListLength, OUT PULONG ReturnedIndex ) /*++ Routine Description: This routine searches an ObjectTypeList to determine if the specified object type is in the list. Arguments: ObjectType - Object Type to search for. ObjectTypeList - The object type list to search. ObjectTypeListLength - Number of elements in ObjectTypeList ReturnedIndex - Index to the element ObjectType was found in Return Value: TRUE: ObjectType was found in list. FALSE: ObjectType was not found in list. --*/ { ULONG Index; GUID *LocalObjectType; PAGED_CODE(); ASSERT( sizeof(GUID) == sizeof(ULONG) * 4 ); for ( Index=0; IndexRevision) == FIELD_OFFSET (SID, SubAuthorityCount)); C_ASSERT (sizeof (((SID *)Sid)->Revision) + sizeof (((SID *)Sid)->SubAuthorityCount) == sizeof (USHORT)); PAGED_CODE(); #if DBG SepDumpTokenInfo(AToken); #endif // // If Sid is the constant PrincipalSelfSid, // replace it with the passed in PrincipalSelfSid. // if ( PrincipalSelfSid != NULL && RtlEqualSid( SePrincipalSelfSid, Sid ) ) { Sid = PrincipalSelfSid; } // // Get the length of the source SID since this only needs to be computed // once. // SidLength = 8 + (4 * ((PISID)Sid)->SubAuthorityCount); // // To speed up processing we compare the sub authority count and the revision at the same time. // TargetShort = *(USHORT *)&((PISID)Sid)->Revision; // // Get address of user/group array and number of user/groups. // Token = (PTOKEN)AToken; TokenSid = Token->UserAndGroups; UserAndGroupCount = Token->UserAndGroupCount; // // Scan through the user/groups and attempt to find a match with the // specified SID. // for (i = 0 ; i < UserAndGroupCount ; i += 1) { MatchSid = (PISID)TokenSid->Sid; // // If revision and sub authority count matches, then compare the SIDs // for equality. // if (*(USHORT *) &MatchSid->Revision == TargetShort) { if (RtlEqualMemory(Sid, MatchSid, SidLength)) { // // If this is the first one in the list, then it is the User, // and return success immediately. // // If this is not the first one, then it represents a group, // and we must make sure the group is currently enabled before // we can say that the group is "in" the token. // if ((i == 0) || (TokenSid->Attributes & SE_GROUP_ENABLED) || (DenyAce && (TokenSid->Attributes & SE_GROUP_USE_FOR_DENY_ONLY))) { return TRUE; } else { return FALSE; } } } TokenSid += 1; } return FALSE; } BOOLEAN SepSidInTokenEx ( IN PACCESS_TOKEN AToken, IN PSID PrincipalSelfSid, IN PSID Sid, IN BOOLEAN DenyAce, IN BOOLEAN Restricted ) /*++ Routine Description: Checks to see if a given restricted 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. Arguments: Token - Pointer to the token to be examined 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 PRINCIPAL_SELF_SID 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 DenyAce - The ACE being evaluated is a DENY or ACCESS DENIED ace Restricted - The access check being performed uses the restricted sids. Return Value: A value of TRUE indicates that the SID is in the token, FALSE otherwise. --*/ { ULONG i; PISID MatchSid; ULONG SidLength; PTOKEN Token; PSID_AND_ATTRIBUTES TokenSid; ULONG UserAndGroupCount; USHORT TargetShort; C_ASSERT (FIELD_OFFSET (SID, Revision) + sizeof (((SID *)Sid)->Revision) == FIELD_OFFSET (SID, SubAuthorityCount)); C_ASSERT (sizeof (((SID *)Sid)->Revision) + sizeof (((SID *)Sid)->SubAuthorityCount) == sizeof (USHORT)); PAGED_CODE(); #if DBG SepDumpTokenInfo(AToken); #endif // // If Sid is the constant PrincipalSelfSid, // replace it with the passed in PrincipalSelfSid. // if ( PrincipalSelfSid != NULL && RtlEqualSid( SePrincipalSelfSid, Sid ) ) { Sid = PrincipalSelfSid; } // // Get the length of the source SID since this only needs to be computed // once. // // // Get the length of the source SID since this only needs to be computed // once. // SidLength = 8 + (4 * ((PISID)Sid)->SubAuthorityCount); // // To speed up processing we compare the sub authority count and the revision at the same time. // TargetShort = *(USHORT *)&((PISID)Sid)->Revision; // // Get address of user/group array and number of user/groups. // Token = (PTOKEN)AToken; if (Restricted) { TokenSid = Token->RestrictedSids; UserAndGroupCount = Token->RestrictedSidCount; } else { TokenSid = Token->UserAndGroups; UserAndGroupCount = Token->UserAndGroupCount; } // // Scan through the user/groups and attempt to find a match with the // specified SID. // for (i = 0; i < UserAndGroupCount ; i += 1) { MatchSid = (PISID)TokenSid->Sid; // // If the SID revision and length matches, then compare the SIDs // for equality. // if (*(USHORT *) &MatchSid->Revision == TargetShort) { if (RtlEqualMemory(Sid, MatchSid, SidLength)) { // // If this is the first one in the list and not deny-only it // is not a restricted token then it is the User, and return // success immediately. // // If this is not the first one, then it represents a group, // and we must make sure the group is currently enabled before // we can say that the group is "in" the token. // if ((!Restricted && (i == 0) && ((TokenSid->Attributes & SE_GROUP_USE_FOR_DENY_ONLY) == 0)) || (TokenSid->Attributes & SE_GROUP_ENABLED) || (DenyAce && (TokenSid->Attributes & SE_GROUP_USE_FOR_DENY_ONLY))) { return TRUE; } else { return FALSE; } } } TokenSid += 1; } return FALSE; } VOID SepMaximumAccessCheck( IN PTOKEN EToken, IN PTOKEN PrimaryToken, IN PACL Dacl, IN PSID PrincipalSelfSid, IN ULONG LocalTypeListLength, IN PIOBJECT_TYPE_LIST LocalTypeList, IN ULONG ObjectTypeListLength, IN BOOLEAN Restricted ) /*++ Routine Description: Does an access check for maximum allowed or with a result list. If the Restricted flag is set, it is done for a restricted token. The sids checked are the restricted sids, not the users and groups. The current granted access is stored in the Remaining access and then another access check is run. Arguments: EToken - Effective token of caller. PrimaryToken - Process token of calling process Dacl - ACL to check PrincipalSelfSid - Sid to use in replacing the well-known self sid LocalTypeListLength - Length of list of types. LocalTypeList - List of types. ObjectTypeList - Length of caller-supplied list of object types. Return Value: none --*/ { ULONG i,j; PVOID Ace; ULONG AceCount; ULONG Index; ULONG ResultListIndex; // // The remaining bits are the granted bits for each object type on a // restricted check // if ( Restricted ) { for ( j=0; jAceCount; // // granted == NUL // denied == NUL // // for each ACE // // if grant // for each SID // if SID match, then add all that is not denied to grant mask // // if deny // for each SID // if SID match, then add all that is not granted to deny mask // for ( i = 0, Ace = FirstAce( Dacl ) ; i < AceCount ; i++, Ace = NextAce( Ace ) ) { if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) { if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) { if (SepSidInTokenEx( EToken, PrincipalSelfSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart, FALSE, Restricted )) { // // Only grant access types from this mask that have // not already been denied // // Optimize 'normal' case if ( LocalTypeListLength == 1 ) { LocalTypeList->CurrentGranted |= (((PACCESS_ALLOWED_ACE)Ace)->Mask & ~LocalTypeList->CurrentDenied); } else { // // The zeroeth object type represents the object itself. // SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list 0, // Element to update ((PACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted UpdateCurrentGranted ); } } // // Handle an object specific Access Allowed ACE // } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_OBJECT_ACE_TYPE) ) { GUID *ObjectTypeInAce; // // If no object type is in the ACE, // treat this as an ACCESS_ALLOWED_ACE. // ObjectTypeInAce = RtlObjectAceObjectType(Ace); if ( ObjectTypeInAce == NULL ) { if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE, Restricted ) ) { // Optimize 'normal' case if ( LocalTypeListLength == 1 ) { LocalTypeList->CurrentGranted |= (((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask & ~LocalTypeList->CurrentDenied); } else { SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list 0, // Element to update ((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted UpdateCurrentGranted ); } } // // If no object type list was passed, // don't grant access to anyone. // } else if ( ObjectTypeListLength == 0 ) { // Drop through // // If an object type is in the ACE, // Find it in the LocalTypeList before using the ACE. // } else { if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE, Restricted ) ) { if ( SepObjectInTypeList( ObjectTypeInAce, LocalTypeList, LocalTypeListLength, &Index ) ) { SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list Index, // Element already updated ((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted UpdateCurrentGranted ); } } } } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_COMPOUND_ACE_TYPE) ) { // // If we're impersonating, EToken is set to the Client, and if we're not, // EToken is set to the Primary. According to the DSA architecture, if // we're asked to evaluate a compound ACE and we're not impersonating, // pretend we are impersonating ourselves. So we can just use the EToken // for the client token, since it's already set to the right thing. // if ( SepSidInTokenEx(EToken, PrincipalSelfSid, RtlCompoundAceClientSid( Ace ), FALSE, Restricted) && SepSidInTokenEx(PrimaryToken, NULL, RtlCompoundAceServerSid( Ace ), FALSE, FALSE) ) { // // Only grant access types from this mask that have // not already been denied // // Optimize 'normal' case if ( LocalTypeListLength == 1 ) { LocalTypeList->CurrentGranted |= (((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask & ~LocalTypeList->CurrentDenied); } else { // // The zeroeth object type represents the object itself. // SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list 0, // Element to update ((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted UpdateCurrentGranted ); } } } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) { if ( SepSidInTokenEx( EToken, PrincipalSelfSid, &((PACCESS_DENIED_ACE)Ace)->SidStart, TRUE, Restricted )) { // // Only deny access types from this mask that have // not already been granted // // Optimize 'normal' case if ( LocalTypeListLength == 1 ) { LocalTypeList->CurrentDenied |= (((PACCESS_DENIED_ACE)Ace)->Mask & ~LocalTypeList->CurrentGranted); } else { // // The zeroeth object type represents the object itself. // SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list 0, // Element to update ((PACCESS_DENIED_ACE)Ace)->Mask, // Access denied UpdateCurrentDenied ); } } // // Handle an object specific Access Denied ACE // } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_OBJECT_ACE_TYPE) ) { if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), TRUE, Restricted ) ) { GUID *ObjectTypeInAce; // // If there is no object type in the ACE, // or if the caller didn't specify an object type list, // apply this deny ACE to the entire object. // ObjectTypeInAce = RtlObjectAceObjectType(Ace); if ( ObjectTypeInAce == NULL ) { if ( LocalTypeListLength == 1 ) { LocalTypeList->CurrentDenied |= (((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask & ~LocalTypeList->CurrentGranted); } else { // // The zeroeth object type represents the object itself. // SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list 0, ((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask, // Access denied UpdateCurrentDenied ); } // // If no object type list was passed, // don't grant access to anyone. // } else if ( ObjectTypeListLength == 0 ) { LocalTypeList->CurrentDenied |= (((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask & ~LocalTypeList->CurrentGranted); // // If an object type is in the ACE, // Find it in the LocalTypeList before using the ACE. // } else if ( SepObjectInTypeList( ObjectTypeInAce, LocalTypeList, LocalTypeListLength, &Index ) ) { SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list Index, // Element to update ((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask, // Access denied UpdateCurrentDenied ); } } } } } } VOID SepNormalAccessCheck( IN ACCESS_MASK Remaining, IN PTOKEN EToken, IN PTOKEN PrimaryToken, IN PACL Dacl, IN PSID PrincipalSelfSid, IN ULONG LocalTypeListLength, IN PIOBJECT_TYPE_LIST LocalTypeList, IN ULONG ObjectTypeListLength, IN BOOLEAN Restricted ) /*++ Routine Description: Does an access check when the caller isn't asking for MAXIMUM_ALLOWED or a type result list. If the Restricted flag is set, the sids checked are the restricted sids, not the users and groups. The Remaining field is reset to the original remaining value and then another access check is run. Arguments: Remaining - Remaining access desired after special checks EToken - Effective token of caller. PrimaryToken - Process token of calling process Dacl - ACL to check PrincipalSelfSid - Sid to use in replacing the well-known self sid LocalTypeListLength - Length of list of types. LocalTypeList - List of types. ObjectTypeList - Length of caller-supplied list of object types. Restricted - Use the restricted sids for the access check. Return Value: none --*/ { ULONG i,j; PVOID Ace; ULONG AceCount; ULONG Index; AceCount = Dacl->AceCount; // // The remaining bits are "remaining" at all levels // for ( j=0; jRemaining != 0 ) ; i++, Ace = NextAce( Ace ) ) { if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) { // // Handle an Access Allowed ACE // if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) { if ( SepSidInTokenEx( EToken, PrincipalSelfSid, &((PACCESS_ALLOWED_ACE )Ace)->SidStart, FALSE, Restricted ) ) { // Optimize 'normal' case if ( LocalTypeListLength == 1 ) { LocalTypeList->Remaining &= ~((PACCESS_ALLOWED_ACE)Ace)->Mask; } else { // // The zeroeth object type represents the object itself. // SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list 0, // Element to update ((PACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted UpdateRemaining ); } } // // Handle an object specific Access Allowed ACE // } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_OBJECT_ACE_TYPE) ) { GUID *ObjectTypeInAce; // // If no object type is in the ACE, // treat this as an ACCESS_ALLOWED_ACE. // ObjectTypeInAce = RtlObjectAceObjectType(Ace); if ( ObjectTypeInAce == NULL ) { if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE, Restricted ) ) { // Optimize 'normal' case if ( LocalTypeListLength == 1 ) { LocalTypeList->Remaining &= ~((PACCESS_ALLOWED_ACE)Ace)->Mask; } else { SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list 0, // Element to update ((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted UpdateRemaining ); } } // // If no object type list was passed, // don't grant access to anyone. // } else if ( ObjectTypeListLength == 0 ) { // Drop through // // If an object type is in the ACE, // Find it in the LocalTypeList before using the ACE. // } else { if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE, Restricted ) ) { if ( SepObjectInTypeList( ObjectTypeInAce, LocalTypeList, LocalTypeListLength, &Index ) ) { SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list Index, // Element already updated ((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted UpdateRemaining ); } } } // // Handle a compound Access Allowed ACE // } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_COMPOUND_ACE_TYPE) ) { // // See comment in MAXIMUM_ALLOWED case as to why we can use EToken here // for the client. // if ( SepSidInTokenEx(EToken, PrincipalSelfSid, RtlCompoundAceClientSid( Ace ), FALSE, Restricted) && SepSidInTokenEx(PrimaryToken, NULL, RtlCompoundAceServerSid( Ace ), FALSE, Restricted) ) { // Optimize 'normal' case if ( LocalTypeListLength == 1 ) { LocalTypeList->Remaining &= ~((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask; } else { SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list 0, // Element to update ((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted UpdateRemaining ); } } // // Handle an Access Denied ACE // } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) { if ( SepSidInTokenEx( EToken, PrincipalSelfSid, &((PACCESS_DENIED_ACE)Ace)->SidStart, TRUE, Restricted ) ) { // // The zeroeth element represents the object itself. // Just check that element. // if (LocalTypeList->Remaining & ((PACCESS_DENIED_ACE)Ace)->Mask) { break; } } // // Handle an object specific Access Denied ACE // } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_OBJECT_ACE_TYPE) ) { if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), TRUE, Restricted ) ) { GUID *ObjectTypeInAce; // // If there is no object type in the ACE, // or if the caller didn't specify an object type list, // apply this deny ACE to the entire object. // ObjectTypeInAce = RtlObjectAceObjectType(Ace); if ( ObjectTypeInAce == NULL || ObjectTypeListLength == 0 ) { // // The zeroeth element represents the object itself. // Just check that element. // if (LocalTypeList->Remaining & ((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask) { break; } // // Otherwise apply the deny ACE to the object specified // in the ACE. // } else if ( SepObjectInTypeList( ObjectTypeInAce, LocalTypeList, LocalTypeListLength, &Index ) ) { if (LocalTypeList[Index].Remaining & ((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask) { break; } } } } } } } VOID SeMaximumAuditMask( IN PACL Sacl, IN ACCESS_MASK GrantedAccess, IN PACCESS_TOKEN Token, OUT PACCESS_MASK pAuditMask ) /*++ Routine Description: This routine takes the passed security descriptor and applies the "MAXIMUM_ALLOWED" algorithm to the SACL contained in the security descriptor if one exists. This mask represents all the success audits that can occur from the passed subject context accessing the passed security descriptor. The code walks the SACL and for each SYSTEM_AUDIT_ACE found that matches the passed subject context, keeps a running total of the access bits in the ACE. The resulting mask is then masked by the passed GrantedAccess mask, since we're only interested in the bits that the object is actually being opened for. Arguments: Sacl - The Sacl to be examined. GrantedAccess - The access that has been granted to the object. Token - Supplies to effective token for the access attempt. pAuditMask - Returns the mask of bits to be audited (if any). Return Value: None --*/ { USHORT AceCount = 0; PACE_HEADER Ace = NULL; ACCESS_MASK AccessMask = (ACCESS_MASK)0; UCHAR AceFlags = 0; USHORT i; // // Initialize OUT parameters // *pAuditMask = (ACCESS_MASK)0; // // Determine if there is an SACL in the security descriptor. // If not, nothing to do. // if (0 == (AceCount = Sacl->AceCount)) { return; } // // Iterate through the ACEs on the Sacl until either we reach // the end or discover that we have to take all possible actions, // in which case it doesn't pay to look any further // for ( i = 0, Ace = FirstAce( Sacl ) ; (i < AceCount) ; i++, Ace = NextAce( Ace ) ) { if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) { if ( (((PACE_HEADER)Ace)->AceType == SYSTEM_AUDIT_ACE_TYPE) ) { AccessMask = ((PSYSTEM_AUDIT_ACE)Ace)->Mask; AceFlags = ((PACE_HEADER)Ace)->AceFlags; if ((AccessMask & GrantedAccess) && (AceFlags & SUCCESSFUL_ACCESS_ACE_FLAG)) { if ( SepSidInToken( (PACCESS_TOKEN)Token, NULL, &((PSYSTEM_AUDIT_ACE)Ace)->SidStart, FALSE ) ) { *pAuditMask |= (AccessMask & GrantedAccess); } } } } } } BOOLEAN SepAccessCheck ( IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN PSID PrincipalSelfSid, IN PTOKEN PrimaryToken, IN PTOKEN ClientToken OPTIONAL, IN ACCESS_MASK DesiredAccess, IN PIOBJECT_TYPE_LIST ObjectTypeList OPTIONAL, IN ULONG ObjectTypeListLength, IN PGENERIC_MAPPING GenericMapping, IN ACCESS_MASK PreviouslyGrantedAccess, IN KPROCESSOR_MODE PreviousMode, OUT PACCESS_MASK GrantedAccess, OUT PPRIVILEGE_SET *Privileges OPTIONAL, OUT PNTSTATUS AccessStatus, IN BOOLEAN ReturnResultList, OUT PBOOLEAN ReturnSomeAccessGranted, OUT PBOOLEAN ReturnSomeAccessDenied ) /*++ Routine Description: Worker routine for SeAccessCheck and NtAccessCheck. We actually do the access checking here. Whether or not we actually evaluate the DACL is based on the following interaction between the SE_DACL_PRESENT bit in the security descriptor and the value of the DACL pointer itself. SE_DACL_PRESENT SET CLEAR +-------------+-------------+ | | | NULL | GRANT | GRANT | | ALL | ALL | DACL | | | Pointer +-------------+-------------+ | | | !NULL | EVALUATE | GRANT | | ACL | ALL | | | | +-------------+-------------+ Arguments: SecurityDescriptor - Pointer to the security descriptor from the object being accessed. 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 PRINCIPAL_SELF_SID is replaced by this SID. The parameter should be NULL if the object does not represent a principal. Token - Pointer to user's token object. TokenLocked - Boolean describing whether or not there is a read lock on the token. DesiredAccess - Access mask describing the user's desired access to the object. This mask is assumed not to contain generic access types. ObjectTypeList - Supplies a list of GUIDs representing the object (and sub-objects) being accessed. If no list is present, AccessCheckByType behaves identically to AccessCheck. ObjectTypeListLength - Specifies the number of elements in the ObjectTypeList. GenericMapping - Supplies a pointer to the generic mapping associated with this object type. PreviouslyGrantedAccess - Access mask indicating any access' that have already been granted by higher level routines PrivilgedAccessMask - Mask describing access types that may not be granted without a privilege. GrantedAccess - Returns an access mask describing all granted access', or NULL. Privileges - Optionally supplies a pointer in which will be returned any privileges that were used for the access. If this is null, it will be assumed that privilege checks have been done already. AccessStatus - Returns STATUS_SUCCESS or other error code to be propogated back to the caller ReturnResultList - If true, GrantedAccess and AccessStatus is actually an array of entries ObjectTypeListLength elements long. ReturnSomeAccessGranted - Returns a value of TRUE to indicate that some access' were granted, FALSE otherwise. ReturnSomeAccessDenied - Returns a value of FALSE if some of the requested access was not granted. This will alway be an inverse of SomeAccessGranted unless ReturnResultList is TRUE. In that case, Return Value: A value of TRUE indicates that some access' were granted, FALSE otherwise. --*/ { NTSTATUS Status; ACCESS_MASK Remaining; BOOLEAN RetVal = TRUE; PACL Dacl; PVOID Ace; ULONG AceCount; ULONG i; ULONG j; ULONG Index; ULONG PrivilegeCount = 0; BOOLEAN Success = FALSE; BOOLEAN SystemSecurity = FALSE; BOOLEAN WriteOwner = FALSE; PTOKEN EToken; IOBJECT_TYPE_LIST FixedTypeList; PIOBJECT_TYPE_LIST LocalTypeList; ULONG LocalTypeListLength; ULONG ResultListIndex; PAGED_CODE(); #if DBG SepDumpSecurityDescriptor( SecurityDescriptor, "Input to SeAccessCheck\n" ); if (ARGUMENT_PRESENT( ClientToken )) { SepDumpTokenInfo( ClientToken ); } SepDumpTokenInfo( PrimaryToken ); #endif EToken = ARGUMENT_PRESENT( ClientToken ) ? ClientToken : PrimaryToken; // // Assert that there are no generic accesses in the DesiredAccess // SeAssertMappedCanonicalAccess( DesiredAccess ); Remaining = DesiredAccess; // // Check for ACCESS_SYSTEM_SECURITY here, // fail if he's asking for it and doesn't have // the privilege. // if ( Remaining & ACCESS_SYSTEM_SECURITY ) { // // Bugcheck if we weren't given a pointer to return privileges // into. Our caller was supposed to have taken care of this // in that case. // ASSERT( ARGUMENT_PRESENT( Privileges )); Success = SepSinglePrivilegeCheck ( SeSecurityPrivilege, EToken, PreviousMode ); if (!Success) { PreviouslyGrantedAccess = 0; Status = STATUS_PRIVILEGE_NOT_HELD; goto ReturnOneStatus; } // // Success, remove ACCESS_SYSTEM_SECURITY from remaining, add it // to PreviouslyGrantedAccess // Remaining &= ~ACCESS_SYSTEM_SECURITY; PreviouslyGrantedAccess |= ACCESS_SYSTEM_SECURITY; PrivilegeCount++; SystemSecurity = TRUE; if ( Remaining == 0 ) { Status = STATUS_SUCCESS; goto ReturnOneStatus; } } // // Get pointer to client SID's // Dacl = RtlpDaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ); // // If the SE_DACL_PRESENT bit is not set, the object has no // security, so all accesses are granted. If he's asking for // MAXIMUM_ALLOWED, return the GENERIC_ALL field from the generic // mapping. // // Also grant all access if the Dacl is NULL. // if ( !RtlpAreControlBitsSet( (PISECURITY_DESCRIPTOR)SecurityDescriptor, SE_DACL_PRESENT) || (Dacl == NULL)) { // // Restricted tokens treat a NULL dacl the same as a DACL with no // ACEs. // #ifdef SECURE_NULL_DACLS if (SeTokenIsRestricted( EToken )) { // // We know that Remaining != 0 here, because we // know it was non-zero coming into this routine, // and we've checked it against 0 every time we've // cleared a bit. // ASSERT( Remaining != 0 ); // // There are ungranted accesses. Since there is // nothing in the DACL, they will not be granted. // If, however, the only ungranted access at this // point is MAXIMUM_ALLOWED, and something has been // granted in the PreviouslyGranted mask, return // what has been granted. // if ( (Remaining == MAXIMUM_ALLOWED) && (PreviouslyGrantedAccess != (ACCESS_MASK)0) ) { Status = STATUS_SUCCESS; goto ReturnOneStatus; } else { PreviouslyGrantedAccess = 0; Status = STATUS_ACCESS_DENIED; goto ReturnOneStatus; } } #endif //SECURE_NULL_DACLS if (DesiredAccess & MAXIMUM_ALLOWED) { // // Give him: // GenericAll // Anything else he asked for // PreviouslyGrantedAccess = GenericMapping->GenericAll | (DesiredAccess | PreviouslyGrantedAccess) & ~MAXIMUM_ALLOWED; } else { PreviouslyGrantedAccess |= DesiredAccess; } Status = STATUS_SUCCESS; goto ReturnOneStatus; } // // There is security on this object. Check to see // if he's asking for WRITE_OWNER, and perform the // privilege check if so. // if ( (Remaining & WRITE_OWNER) && ARGUMENT_PRESENT( Privileges ) ) { Success = SepSinglePrivilegeCheck ( SeTakeOwnershipPrivilege, EToken, PreviousMode ); if (Success) { // // Success, remove WRITE_OWNER from remaining, add it // to PreviouslyGrantedAccess // Remaining &= ~WRITE_OWNER; PreviouslyGrantedAccess |= WRITE_OWNER; PrivilegeCount++; WriteOwner = TRUE; if ( Remaining == 0 ) { Status = STATUS_SUCCESS; goto ReturnOneStatus; } } } // // If the DACL is empty, // deny all access immediately. // if ((AceCount = Dacl->AceCount) == 0) { // // We know that Remaining != 0 here, because we // know it was non-zero coming into this routine, // and we've checked it against 0 every time we've // cleared a bit. // ASSERT( Remaining != 0 ); // // There are ungranted accesses. Since there is // nothing in the DACL, they will not be granted. // If, however, the only ungranted access at this // point is MAXIMUM_ALLOWED, and something has been // granted in the PreviouslyGranted mask, return // what has been granted. // if ( (Remaining == MAXIMUM_ALLOWED) && (PreviouslyGrantedAccess != (ACCESS_MASK)0) ) { Status = STATUS_SUCCESS; goto ReturnOneStatus; } else { PreviouslyGrantedAccess = 0; Status = STATUS_ACCESS_DENIED; goto ReturnOneStatus; } } // // Fake out a top level ObjectType list if none is passed by the caller. // if ( ObjectTypeListLength == 0 ) { LocalTypeList = &FixedTypeList; LocalTypeListLength = 1; RtlZeroMemory( &FixedTypeList, sizeof(FixedTypeList) ); FixedTypeList.ParentIndex = -1; } else { LocalTypeList = ObjectTypeList; LocalTypeListLength = ObjectTypeListLength; } // // If the caller wants the MAXIMUM_ALLOWED or the caller wants the // results on all objects and subobjects, use a slower algorithm // that traverses all the ACEs. // if ( (DesiredAccess & MAXIMUM_ALLOWED) != 0 || ReturnResultList ) { // // Do the normal maximum-allowed access check // SepMaximumAccessCheck( EToken, PrimaryToken, Dacl, PrincipalSelfSid, LocalTypeListLength, LocalTypeList, ObjectTypeListLength, FALSE ); // // If this is a restricted token, do the additional access check // if (SeTokenIsRestricted( EToken ) ) { SepMaximumAccessCheck( EToken, PrimaryToken, Dacl, PrincipalSelfSid, LocalTypeListLength, LocalTypeList, ObjectTypeListLength, TRUE ); } // // If the caller wants to know the individual results of each sub-object, // sub-object, // break it down for him. // if ( ReturnResultList ) { ACCESS_MASK GrantedAccessMask; ACCESS_MASK RequiredAccessMask; BOOLEAN SomeAccessGranted = FALSE; BOOLEAN SomeAccessDenied = FALSE; // // Compute mask of Granted access bits to tell the caller about. // If he asked for MAXIMUM_ALLOWED, // tell him everything, // otherwise // tell him what he asked about. // if (DesiredAccess & MAXIMUM_ALLOWED) { GrantedAccessMask = (ACCESS_MASK) ~MAXIMUM_ALLOWED; RequiredAccessMask = (DesiredAccess | PreviouslyGrantedAccess) & ~MAXIMUM_ALLOWED; } else { GrantedAccessMask = DesiredAccess | PreviouslyGrantedAccess; RequiredAccessMask = DesiredAccess | PreviouslyGrantedAccess; } // // Loop computing the access granted to each object and sub-object. // for ( ResultListIndex=0; ResultListIndexCurrentGranted); if (Remaining != 0) { Status = STATUS_ACCESS_DENIED; PreviouslyGrantedAccess = 0; goto ReturnOneStatus; } PreviouslyGrantedAccess |= LocalTypeList->CurrentGranted; Status = STATUS_SUCCESS; goto ReturnOneStatus; } } // if MAXIMUM_ALLOWED... #ifdef notdef // // The remaining bits are "remaining" at all levels for ( j=0; jRemaining != 0 ) ; i++, Ace = NextAce( Ace ) ) { if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) { // // Handle an Access Allowed ACE // if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) { if ( SepSidInToken( EToken, PrincipalSelfSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart, FALSE ) ) { // Optimize 'normal' case if ( LocalTypeListLength == 1 ) { LocalTypeList->Remaining &= ~((PACCESS_ALLOWED_ACE)Ace)->Mask; } else { // // The zeroeth object type represents the object itself. // SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list 0, // Element to update ((PACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted UpdateRemaining ); } } // // Handle an object specific Access Allowed ACE // } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_OBJECT_ACE_TYPE) ) { GUID *ObjectTypeInAce; // // If no object type is in the ACE, // treat this as an ACCESS_ALLOWED_ACE. // ObjectTypeInAce = RtlObjectAceObjectType(Ace); if ( ObjectTypeInAce == NULL ) { if ( SepSidInToken( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE ) ) { // Optimize 'normal' case if ( LocalTypeListLength == 1 ) { LocalTypeList->Remaining &= ~((PACCESS_ALLOWED_ACE)Ace)->Mask; } else { SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list 0, // Element to update ((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted UpdateRemaining ); } } // // If no object type list was passed, // don't grant access to anyone. // } else if ( ObjectTypeListLength == 0 ) { // Drop through // // If an object type is in the ACE, // Find it in the LocalTypeList before using the ACE. // } else { if ( SepSidInToken( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE ) ) { if ( SepObjectInTypeList( ObjectTypeInAce, LocalTypeList, LocalTypeListLength, &Index ) ) { SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list Index, // Element already updated ((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted UpdateRemaining ); } } } // // Handle a compound Access Allowed ACE // } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_COMPOUND_ACE_TYPE) ) { // // See comment in MAXIMUM_ALLOWED case as to why we can use EToken here // for the client. // if ( SepSidInToken(EToken, PrincipalSelfSid, RtlCompoundAceClientSid( Ace ), FALSE) && SepSidInToken(PrimaryToken, NULL, RtlCompoundAceServerSid( Ace ), FALSE) ) { // Optimize 'normal' case if ( LocalTypeListLength == 1 ) { LocalTypeList->Remaining &= ~((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask; } else { SepAddAccessTypeList( LocalTypeList, // List to modify LocalTypeListLength, // Length of list 0, // Element to update ((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted UpdateRemaining ); } } // // Handle an Access Denied ACE // } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) { if ( SepSidInToken( EToken, PrincipalSelfSid, &((PACCESS_DENIED_ACE)Ace)->SidStart, TRUE ) ) { // // The zeroeth element represents the object itself. // Just check that element. // if (LocalTypeList->Remaining & ((PACCESS_DENIED_ACE)Ace)->Mask) { break; } } // // Handle an object specific Access Denied ACE // } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_OBJECT_ACE_TYPE) ) { if ( SepSidInToken( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), TRUE ) ) { GUID *ObjectTypeInAce; // // If there is no object type in the ACE, // or if the caller didn't specify an object type list, // apply this deny ACE to the entire object. // ObjectTypeInAce = RtlObjectAceObjectType(Ace); if ( ObjectTypeInAce == NULL || ObjectTypeListLength == 0 ) { // // The zeroeth element represents the object itself. // Just check that element. // if (LocalTypeList->Remaining & ((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask) { break; } // // Otherwise apply the deny ACE to the object specified // in the ACE. // } else if ( SepObjectInTypeList( ObjectTypeInAce, LocalTypeList, LocalTypeListLength, &Index ) ) { if (LocalTypeList[Index].Remaining & ((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask) { break; } } } } } } #endif // // Do the normal access check first // SepNormalAccessCheck( Remaining, EToken, PrimaryToken, Dacl, PrincipalSelfSid, LocalTypeListLength, LocalTypeList, ObjectTypeListLength, FALSE ); if (LocalTypeList->Remaining != 0) { Status = STATUS_ACCESS_DENIED; PreviouslyGrantedAccess = 0; goto ReturnOneStatus; } // // If this is a restricted token, do the additional access check // if (SeTokenIsRestricted( EToken ) ) { SepNormalAccessCheck( Remaining, EToken, PrimaryToken, Dacl, PrincipalSelfSid, LocalTypeListLength, LocalTypeList, ObjectTypeListLength, TRUE ); } if (LocalTypeList->Remaining != 0) { Status = STATUS_ACCESS_DENIED; PreviouslyGrantedAccess = 0; goto ReturnOneStatus; } Status = STATUS_SUCCESS; PreviouslyGrantedAccess |= DesiredAccess; // // Return a single status code to the caller. // ReturnOneStatus: if ( Status == STATUS_SUCCESS && PreviouslyGrantedAccess == 0 ) { Status = STATUS_ACCESS_DENIED; } if ( NT_SUCCESS(Status) ) { if ( PrivilegeCount > 0 ) { SepAssemblePrivileges( PrivilegeCount, SystemSecurity, WriteOwner, Privileges ); if ( ( Privileges != NULL ) && ( *Privileges == NULL ) ) { RetVal = FALSE; Status = STATUS_NO_MEMORY; PreviouslyGrantedAccess = 0; } } } // // If the caller asked for a list of status', // duplicate the status all over. // if ( ReturnResultList ) { for ( ResultListIndex=0; ResultListIndex= sizeof(PRIVILEGE_SET) )) { PrivilegeSet->PrivilegeCount = 0; } ProbeForReadSmallStructure( GenericMapping, sizeof(GENERIC_MAPPING), sizeof(ULONG) ); LocalGenericMapping = *GenericMapping; } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS( Status ) ) { return( Status ); } if (DesiredAccess & ( GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL )) { Status = STATUS_GENERIC_NOT_MAPPED; goto Cleanup; } // // Obtain a pointer to the passed token // Status = ObReferenceObjectByHandle( ClientToken, // Handle (ACCESS_MASK)TOKEN_QUERY, // DesiredAccess SeTokenObjectType, // ObjectType PreviousMode, // AccessMode (PVOID *)&Token, // Object 0 // GrantedAccess ); if (!NT_SUCCESS(Status)) { Token = NULL; goto Cleanup; } // // It must be an impersonation token, and at impersonation // level of Identification or above. // if (Token->TokenType != TokenImpersonation) { Status = STATUS_NO_IMPERSONATION_TOKEN; goto Cleanup; } if ( Token->ImpersonationLevel < SecurityIdentification ) { Status = STATUS_BAD_IMPERSONATION_LEVEL; goto Cleanup; } // // Capture any Object type list // Status = SeCaptureObjectTypeList( ObjectTypeList, ObjectTypeListLength, PreviousMode, &LocalObjectTypeList ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Compare the DesiredAccess with the privileges in the // passed token, and see if we can either satisfy the requested // access with a privilege, or bomb out immediately because // we don't have a privilege we need. // Status = SePrivilegePolicyCheck( &DesiredAccess, &PreviouslyGrantedAccess, NULL, (PACCESS_TOKEN)Token, &Privileges, PreviousMode ); if (!NT_SUCCESS( Status )) { try { if ( ReturnResultList ) { for ( ResultListIndex=0; ResultListIndex LocalPrivilegeSetLength ) { try { *PrivilegeSetLength = SepPrivilegeSetSize( Privileges ); Status = STATUS_BUFFER_TOO_SMALL; } except ( EXCEPTION_EXECUTE_HANDLER ) { Status = GetExceptionCode(); } SeFreePrivileges( Privileges ); goto Cleanup; } else { try { RtlCopyMemory( PrivilegeSet, Privileges, SepPrivilegeSetSize( Privileges ) ); } except ( EXCEPTION_EXECUTE_HANDLER ) { SeFreePrivileges( Privileges ); Status = GetExceptionCode(); goto Cleanup; } } SeFreePrivileges( Privileges ); } else { // // No privileges were used, construct an empty privilege set // if ( LocalPrivilegeSetLength < sizeof(PRIVILEGE_SET) ) { try { *PrivilegeSetLength = sizeof(PRIVILEGE_SET); Status = STATUS_BUFFER_TOO_SMALL; } except ( EXCEPTION_EXECUTE_HANDLER ) { Status = GetExceptionCode(); } goto Cleanup; } try { PrivilegeSet->PrivilegeCount = 0; PrivilegeSet->Control = 0; } except ( EXCEPTION_EXECUTE_HANDLER ) { Status = GetExceptionCode(); goto Cleanup; } } // // Capture the PrincipalSelfSid. // if ( PrincipalSelfSid != NULL ) { Status = SeCaptureSid( PrincipalSelfSid, PreviousMode, NULL, 0, PagedPool, TRUE, &CapturedPrincipalSelfSid ); if (!NT_SUCCESS(Status)) { CapturedPrincipalSelfSid = NULL; goto Cleanup; } } // // Capture the passed security descriptor. // // SeCaptureSecurityDescriptor probes the input security descriptor, // so we don't have to // Status = SeCaptureSecurityDescriptor ( SecurityDescriptor, PreviousMode, PagedPool, FALSE, &CapturedSecurityDescriptor ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // If there's no security descriptor, then we've been // called without all the parameters we need. // Return invalid security descriptor. // if ( CapturedSecurityDescriptor == NULL ) { Status = STATUS_INVALID_SECURITY_DESCR; goto Cleanup; } // // A valid security descriptor must have an owner and a group // if ( RtlpOwnerAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor ) == NULL || RtlpGroupAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor ) == NULL ) { SeReleaseSecurityDescriptor ( CapturedSecurityDescriptor, PreviousMode, FALSE ); Status = STATUS_INVALID_SECURITY_DESCR; goto Cleanup; } SeCaptureSubjectContext( &SubjectContext ); SepAcquireTokenReadLock( Token ); // // If the user in the token is the owner of the object, we // must automatically grant ReadControl and WriteDac access // if desired. If the DesiredAccess mask is empty after // these bits are turned off, we don't have to do any more // access checking (ref section 4, DSA ACL Arch) // if ( DesiredAccess & (WRITE_DAC | READ_CONTROL | MAXIMUM_ALLOWED) ) { if (SepTokenIsOwner( Token, CapturedSecurityDescriptor, TRUE )) { if ( DesiredAccess & MAXIMUM_ALLOWED ) { PreviouslyGrantedAccess |= (WRITE_DAC | READ_CONTROL); } else { PreviouslyGrantedAccess |= (DesiredAccess & (WRITE_DAC | READ_CONTROL)); } DesiredAccess &= ~(WRITE_DAC | READ_CONTROL); } } if (DesiredAccess == 0) { try { if ( ReturnResultList ) { for ( ResultListIndex=0; ResultListIndexGenericAll; *GrantedAccess |= (DesiredAccess & ~MAXIMUM_ALLOWED); *GrantedAccess |= PreviouslyGrantedAccess; } else { *GrantedAccess = DesiredAccess | PreviouslyGrantedAccess; } *AccessStatus = STATUS_SUCCESS; return(TRUE); } // // If the object doesn't have a security descriptor (and it's supposed // to), return access denied. // if ( SecurityDescriptor == NULL) { *AccessStatus = STATUS_ACCESS_DENIED; return( FALSE ); } // // If we're impersonating a client, we have to be at impersonation level // of SecurityImpersonation or above. // if ( (SubjectSecurityContext->ClientToken != NULL) && (SubjectSecurityContext->ImpersonationLevel < SecurityImpersonation) ) { *AccessStatus = STATUS_BAD_IMPERSONATION_LEVEL; return( FALSE ); } if ( DesiredAccess == 0 ) { if ( PreviouslyGrantedAccess == 0 ) { *AccessStatus = STATUS_ACCESS_DENIED; return( FALSE ); } *GrantedAccess = PreviouslyGrantedAccess; *AccessStatus = STATUS_SUCCESS; *Privileges = NULL; return( TRUE ); } SeAssertMappedCanonicalAccess( DesiredAccess ); // // If the caller did not lock the subject context for us, // lock it here to keep lower level routines from having // to lock it. // if ( !SubjectContextLocked ) { SeLockSubjectContext( SubjectSecurityContext ); } // // If the user in the token is the owner of the object, we // must automatically grant ReadControl and WriteDac access // if desired. If the DesiredAccess mask is empty after // these bits are turned off, we don't have to do any more // access checking (ref section 4, DSA ACL Arch) // if ( DesiredAccess & (WRITE_DAC | READ_CONTROL | MAXIMUM_ALLOWED) ) { if ( SepTokenIsOwner( EffectiveToken( SubjectSecurityContext ), SecurityDescriptor, TRUE ) ) { if ( DesiredAccess & MAXIMUM_ALLOWED ) { PreviouslyGrantedAccess |= (WRITE_DAC | READ_CONTROL); } else { PreviouslyGrantedAccess |= (DesiredAccess & (WRITE_DAC | READ_CONTROL)); } DesiredAccess &= ~(WRITE_DAC | READ_CONTROL); } } if (DesiredAccess == 0) { if ( !SubjectContextLocked ) { SeUnlockSubjectContext( SubjectSecurityContext ); } *GrantedAccess = PreviouslyGrantedAccess; *AccessStatus = STATUS_SUCCESS; return( TRUE ); } else { BOOLEAN b = SepAccessCheck( SecurityDescriptor, NULL, // No PrincipalSelfSid SubjectSecurityContext->PrimaryToken, SubjectSecurityContext->ClientToken, DesiredAccess, NULL, // No object type list 0, // No object type list GenericMapping, PreviouslyGrantedAccess, AccessMode, GrantedAccess, Privileges, AccessStatus, FALSE, // Don't return a list &Success, NULL ); #if DBG if (!Success && SepShowAccessFail) { DbgPrint("SE: Access check failed, DesiredAccess = 0x%x\n", DesiredAccess); SepDumpSD = TRUE; SepDumpSecurityDescriptor( SecurityDescriptor, "Input to SeAccessCheck\n" ); SepDumpSD = FALSE; SepDumpToken = TRUE; SepDumpTokenInfo( EffectiveToken( SubjectSecurityContext ) ); SepDumpToken = FALSE; } #endif // // If we locked it in this routine, unlock it before we // leave. // if ( !SubjectContextLocked ) { SeUnlockSubjectContext( SubjectSecurityContext ); } // // We return failure if any of the following is TRUE // 1. The user was really not granted access. // 2. The resource manager asked for the list of privileges used to // determine granted access and we failed to allocate memory // required to return these. // return( b && Success ); } } NTSTATUS SePrivilegePolicyCheck( IN OUT PACCESS_MASK RemainingDesiredAccess, IN OUT PACCESS_MASK PreviouslyGrantedAccess, IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext OPTIONAL, IN PACCESS_TOKEN ExplicitToken OPTIONAL, OUT PPRIVILEGE_SET *PrivilegeSet, IN KPROCESSOR_MODE PreviousMode ) /*++ Routine Description: This routine implements privilege policy by examining the bits in a DesiredAccess mask and adjusting them based on privilege checks. Currently, a request for ACCESS_SYSTEM_SECURITY may only be satisfied by the caller having SeSecurityPrivilege. WRITE_OWNER may optionally be satisfied via SeTakeOwnershipPrivilege. Arguments: RemainingDesiredAccess - The desired access for the current operation. Bits may be cleared in this if the subject has particular privileges. PreviouslyGrantedAccess - Supplies an access mask describing any accesses that have already been granted. Bits may be set in here as a result of privilge checks. SubjectSecurityContext - Optionally provides the subject's security context. ExplicitToken - Optionally provides the token to be examined. PrivilegeSet - Supplies a pointer to a location in which will be returned a pointer to a privilege set. PreviousMode - The previous processor mode. Return Value: STATUS_SUCCESS - Any access requests that could be satisfied via privileges were done. STATUS_PRIVILEGE_NOT_HELD - An access type was being requested that requires a privilege, and the current subject did not have the privilege. --*/ { BOOLEAN Success; PTOKEN Token; BOOLEAN WriteOwner = FALSE; BOOLEAN SystemSecurity = FALSE; ULONG PrivilegeNumber = 0; ULONG PrivilegeCount = 0; ULONG SizeRequired; PAGED_CODE(); if (ARGUMENT_PRESENT( SubjectSecurityContext )) { Token = (PTOKEN)EffectiveToken( SubjectSecurityContext ); } else { Token = (PTOKEN)ExplicitToken; } if (*RemainingDesiredAccess & ACCESS_SYSTEM_SECURITY) { Success = SepSinglePrivilegeCheck ( SeSecurityPrivilege, Token, PreviousMode ); if (!Success) { return( STATUS_PRIVILEGE_NOT_HELD ); } PrivilegeCount++; SystemSecurity = TRUE; *RemainingDesiredAccess &= ~ACCESS_SYSTEM_SECURITY; *PreviouslyGrantedAccess |= ACCESS_SYSTEM_SECURITY; } if (*RemainingDesiredAccess & WRITE_OWNER) { Success = SepSinglePrivilegeCheck ( SeTakeOwnershipPrivilege, Token, PreviousMode ); if (Success) { PrivilegeCount++; WriteOwner = TRUE; *RemainingDesiredAccess &= ~WRITE_OWNER; *PreviouslyGrantedAccess |= WRITE_OWNER; } } if (PrivilegeCount > 0) { SizeRequired = sizeof(PRIVILEGE_SET) + (PrivilegeCount - ANYSIZE_ARRAY) * (ULONG)sizeof(LUID_AND_ATTRIBUTES); *PrivilegeSet = ExAllocatePoolWithTag( PagedPool, SizeRequired, 'rPeS' ); if ( *PrivilegeSet == NULL ) { return( STATUS_INSUFFICIENT_RESOURCES ); } (*PrivilegeSet)->PrivilegeCount = PrivilegeCount; (*PrivilegeSet)->Control = 0; if (WriteOwner) { (*PrivilegeSet)->Privilege[PrivilegeNumber].Luid = SeTakeOwnershipPrivilege; (*PrivilegeSet)->Privilege[PrivilegeNumber].Attributes = SE_PRIVILEGE_USED_FOR_ACCESS; PrivilegeNumber++; } if (SystemSecurity) { (*PrivilegeSet)->Privilege[PrivilegeNumber].Luid = SeSecurityPrivilege; (*PrivilegeSet)->Privilege[PrivilegeNumber].Attributes = SE_PRIVILEGE_USED_FOR_ACCESS; } } return( STATUS_SUCCESS ); } BOOLEAN SepTokenIsOwner( IN PACCESS_TOKEN EffectiveToken, IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN BOOLEAN TokenLocked ) /*++ Routine Description: This routine will determine of the Owner of the passed security descriptor is in the passed token. If the token is restricted it cannot be the owner. Arguments: Token - The token representing the current user. SecurityDescriptor - The security descriptor for the object being accessed. TokenLocked - A boolean describing whether the caller has taken a read lock for the token. Return Value: TRUE - The user of the token is the owner of the object. FALSE - The user of the token is not the owner of the object. --*/ { PSID Owner; BOOLEAN rc; PISECURITY_DESCRIPTOR ISecurityDescriptor; PTOKEN Token; PAGED_CODE(); ISecurityDescriptor = (PISECURITY_DESCRIPTOR)SecurityDescriptor; Token = (PTOKEN)EffectiveToken; Owner = RtlpOwnerAddrSecurityDescriptor( ISecurityDescriptor ); ASSERT( Owner != NULL ); if (!TokenLocked) { SepAcquireTokenReadLock( Token ); } rc = SepSidInToken( Token, NULL, Owner, FALSE ); // // For restricted tokens, check the restricted sids too. // if (rc && (Token->TokenFlags & TOKEN_IS_RESTRICTED) != 0) { rc = SepSidInTokenEx( Token, NULL, Owner, FALSE, TRUE ); } if (!TokenLocked) { SepReleaseTokenReadLock( Token ); } return( rc ); } #define WORLD_TRAVERSAL_INCLUDES_ANONYMOUS 1 BOOLEAN SeFastTraverseCheck( IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN PACCESS_STATE AccessState, IN ACCESS_MASK TraverseAccess, IN KPROCESSOR_MODE AccessMode ) /*++ Routine Description: This routine will perform a fast check against the DACL of the passed Security Descriptor to see if Traverse access would be granted. If so, no further access checking is necessary. Note that the SubjectContext for the client process does not have to be locked to make this call, since it does not examine any data structures in the Token. The caller has the following responsibilities: 1. It is the job of the caller to verify AccessMode is not KernelMode! 2. The *caller* is expected to check AccessState for TOKEN_HAS_TRAVERSE_PRIVILEGE if the override is applicable! Arguments: SecurityDescriptor - The Security Descriptor protecting the container object being traversed. AccessState - Running security access state containing caller token information for operation. TraverseAccess - Access mask describing Traverse access for this object type. There must be only one bit specified in the mask. AccessMode - Supplies the access mode to be used in the check Return Value: TRUE - if Traverse access to this container can be granted. FALSE otherwise. --*/ { PACL Dacl; ULONG i; PVOID Ace; ULONG AceCount; #if !WORLD_TRAVERSAL_INCLUDES_ANONYMOUS LOGICAL FoundWorld; LOGICAL FoundAnonymous; #endif PAGED_CODE(); // // Note that I/O calls this function even if the traverse bypass privilege // is set. This is done as I/O doesn't want the performance override to // apply to the DeviceObject->FileName boundary, as not all filesystems // supply file-level security. // // ASSERT( (!ARGUMENT_PRESENT(AccessState)) || // (!(AccessState->Flags & TOKEN_HAS_TRAVERSE_PRIVILEGE)) ); // ASSERT ( AccessMode != KernelMode ); if (SecurityDescriptor == NULL) { return( FALSE ); } // // See if there is a valid DACL in the passed Security Descriptor. // No DACL, no security, all is granted. Note that this function returns // NULL if SE_DACL_PRESENT isn't set. // Dacl = RtlpDaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ); // // If no DACL is supplied, the object has no security, so all accesses are // granted. // if ( Dacl == NULL ) { return(TRUE); } // // There is security on this object. If the DACL is empty, deny all access // immediately // if ((AceCount = Dacl->AceCount) == 0) { return( FALSE ); } // // A restricted token is a token with two lists of SIDs. Access checks are // done against each list, passing only if both lists grant access. The // second "restricting SID" list can contain *any allow* SID. // // This routine doesn't walk a restricted token's restricting-SID list. As // such, restricted tokens require a full access check. // if (AccessState->Flags & TOKEN_IS_RESTRICTED) { return FALSE; } // // There's stuff in the DACL, walk down the list and see // if both Everyone and Anonymous have been granted TraverseAccess // #if !WORLD_TRAVERSAL_INCLUDES_ANONYMOUS FoundWorld = FALSE; FoundAnonymous = FALSE; #endif for ( i = 0, Ace = FirstAce( Dacl ) ; i < AceCount ; i++, Ace = NextAce( Ace ) ) { if (((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE) { continue; } if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) { if ( (TraverseAccess & ((PACCESS_ALLOWED_ACE)Ace)->Mask) ) { if ( RtlEqualSid( SeWorldSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart ) ) { #if WORLD_TRAVERSAL_INCLUDES_ANONYMOUS return( TRUE ); } #else if (FoundAnonymous) { return( TRUE ); } else { FoundWorld = TRUE; } } else if ( RtlEqualSid( SeAnonymousLogonSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart ) ) { if (FoundWorld) { return( TRUE ); } else { FoundAnonymous = TRUE; } } #endif } } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) { if ( (TraverseAccess & ((PACCESS_DENIED_ACE)Ace)->Mask) ) { // // This ACE might refer to a group the user belongs to // (or could be the user). Force a full access check. // return( FALSE ); } } } return( FALSE ); } #ifdef SE_NTFS_WORLD_CACHE /*++ Note: Do not delete SeGetWorldRights. It might be used by NTFS in future. When that happens: - Add this line to #ifdef ALLOC_PRAGMA. #pragma alloc_text(PAGE,SeGetWorldRights) - Uncomment the function prototype declaration in ntos\inc\se.h KedarD - 07/05/2000 --*/ VOID SeGetWorldRights ( IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN PGENERIC_MAPPING GenericMapping, OUT PACCESS_MASK GrantedAccess ) /*++ Routine Descriptions: This call acquires the minimum rights that are available to all tokens. This takes into account all deny access ACE(s) that would reduce the rights granted to an ACE for Everyone. Arguments: SecurityDescriptor - Supplies the security descriptor protecting the object being accessed GenericMapping - Supplies a pointer to the generic mapping associated with this object type. GrantedAccess - Returns an access mask describing the granted access. Return Value: None. --*/ { ACCESS_MASK AlreadyDenied; PACL Dacl; PVOID Ace; ULONG AceCount = 0; ULONG Index; PAGED_CODE(); *GrantedAccess = 0; // // Get a pointer to the ACL. // Dacl = RtlpDaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ); // // If the SE_DACL_PRESENT bit is not set, the object has no // security, so all accesses are granted. If he's asking for // MAXIMUM_ALLOWED, return the GENERIC_ALL field from the generic // mapping. // // Also grant all access if the Dacl is NULL. // if ( (Dacl == NULL) || !RtlpAreControlBitsSet( (PISECURITY_DESCRIPTOR)SecurityDescriptor, SE_DACL_PRESENT) ) { #ifndef SECURE_NULL_DACLS // // Grant all access. // *GrantedAccess = GenericMapping->GenericAll; #endif //!SECURE_NULL_DACLS } else { AceCount = Dacl->AceCount; } for ( Index = 0, Ace = FirstAce( Dacl ), AlreadyDenied = 0 ; Index < AceCount ; Index += 1, Ace = NextAce( Ace ) ) { if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) { if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) { if ( RtlEqualSid( SeWorldSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart ) ) { // // Only grant access types from this mask that have // not already been denied // *GrantedAccess |= (((PACCESS_ALLOWED_ACE)Ace)->Mask & ~AlreadyDenied); } // // Handle an object specific Access Allowed ACE // } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_OBJECT_ACE_TYPE) ) { // // If no object type is in the ACE, // treat this as an ACCESS_ALLOWED_ACE. // if ( RtlObjectAceObjectType( Ace ) == NULL ) { if ( RtlEqualSid( SeWorldSid, RtlObjectAceSid(Ace) ) ) { *GrantedAccess |= (((PACCESS_ALLOWED_ACE)Ace)->Mask & ~AlreadyDenied); } } } else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_COMPOUND_ACE_TYPE) ) { if ( RtlEqualSid( SeWorldSid, RtlCompoundAceClientSid(Ace) ) && RtlEqualSid( SeWorldSid, RtlCompoundAceServerSid(Ace) ) ) { // // Only grant access types from this mask that have // not already been denied // *GrantedAccess |= (((PACCESS_ALLOWED_ACE)Ace)->Mask & ~AlreadyDenied); } } else if ( ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) || ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_OBJECT_ACE_TYPE) ) ) { // // We include all of the deny access ACE(s), regardless of to // what SID they apply. // // // Only deny access types from this mask that have // not already been granted // AlreadyDenied |= (((PACCESS_DENIED_ACE)Ace)->Mask & ~*GrantedAccess); } } } return; } #endif