/*++ 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 "tokenp.h" // // Define the local macros and procedure for this module // #if DBG extern BOOLEAN SepDumpSD; extern BOOLEAN SepDumpToken; BOOLEAN SepShowAccessFail; #endif // DBG #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,SepValidateAce) #pragma alloc_text(PAGE,SepSidInToken) #pragma alloc_text(PAGE,SepAccessCheck) #pragma alloc_text(PAGE,NtAccessCheck) #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) #endif BOOLEAN SepValidateAce ( IN PVOID Ace, IN PACL Dacl ) /*++ Routine Description: Performs rudimentary validation on an Ace. Ace must be within the passed ACl, the SID must be within the Ace, and the Ace must be of at least a minimal size. Arguments: Ace - Pointer to Ace to be examined Dacl - Pointer to Acl in which Ace is supposed to exist Return Value: A value of TRUE indicates the Ace is well formed, FALSE otherwise. --*/ { USHORT AceSize; USHORT AclSize; PAGED_CODE(); // // make sure ACE is within ACL // AceSize = ((PACE_HEADER)Ace)->AceSize; AclSize = Dacl->AclSize; if ( (PVOID)((PUCHAR)Ace + AceSize) > (PVOID)((PUSHORT)Dacl + AclSize)) { return(FALSE); } // // make sure SID is within ACE // if (IsKnownAceType( Ace )) { if ( (PVOID) ( (ULONG)Ace + SeLengthSid(&(((PKNOWN_ACE)Ace)->SidStart)) ) > (PVOID) ( (PUCHAR)Ace + AceSize ) ) { return(FALSE); } } // // Make sure ACE is big enough for minimum grant ACE // if (AceSize < sizeof(KNOWN_ACE)) { return(FALSE); } return(TRUE); } BOOLEAN SepSidInToken ( IN PACCESS_TOKEN AToken, IN PSID Sid ) /*++ 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. Arguments: Token - Pointer to the token to be examined Sid - Pointer to the SID of interest 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; PAGED_CODE(); #if DBG SepDumpTokenInfo(AToken); #endif // // 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. // 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 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)) { // // 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)) { return TRUE; } else { return FALSE; } } } TokenSid += 1; } return FALSE; } BOOLEAN SepAccessCheck ( IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN PTOKEN PrimaryToken, IN PTOKEN ClientToken OPTIONAL, IN ACCESS_MASK DesiredAccess, IN PGENERIC_MAPPING GenericMapping, IN ACCESS_MASK PreviouslyGrantedAccess, IN KPROCESSOR_MODE PreviousMode, OUT PACCESS_MASK GrantedAccess, OUT PPRIVILEGE_SET *Privileges OPTIONAL, OUT PNTSTATUS AccessStatus ) /*++ 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. 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. 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 Return Value: A value of TRUE indicates that some access' were granted, FALSE otherwise. --*/ { ACCESS_MASK CurrentDenied = 0; ACCESS_MASK CurrentGranted = 0; ACCESS_MASK Remaining; PACL Dacl; PVOID Ace; ULONG AceCount; ULONG i; ULONG PrivilegeCount = 0; BOOLEAN Success = FALSE; BOOLEAN SystemSecurity = FALSE; BOOLEAN WriteOwner = FALSE; PTOKEN EToken; 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) { *AccessStatus = STATUS_PRIVILEGE_NOT_HELD; return( FALSE ); } // // 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 ) { SepAssemblePrivileges( PrivilegeCount, SystemSecurity, WriteOwner, Privileges ); *AccessStatus = STATUS_SUCCESS; *GrantedAccess = PreviouslyGrantedAccess; return( TRUE ); } } // // Get pointer to client SID's // Dacl = SepDaclAddrSecurityDescriptor( (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 ( !SepAreControlBitsSet( (PISECURITY_DESCRIPTOR)SecurityDescriptor, SE_DACL_PRESENT ) || (Dacl == NULL)) { if (DesiredAccess & MAXIMUM_ALLOWED) { // // Give him: // GenericAll // Anything else he asked for // *GrantedAccess = GenericMapping->GenericAll; *GrantedAccess |= (DesiredAccess & ~MAXIMUM_ALLOWED); } else { *GrantedAccess = DesiredAccess | PreviouslyGrantedAccess; } if ( PrivilegeCount > 0 ) { SepAssemblePrivileges( PrivilegeCount, SystemSecurity, WriteOwner, Privileges ); } *AccessStatus = STATUS_SUCCESS; return(TRUE); } // // 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 ) { SepAssemblePrivileges( PrivilegeCount, SystemSecurity, WriteOwner, Privileges ); *AccessStatus = STATUS_SUCCESS; *GrantedAccess = PreviouslyGrantedAccess; return( TRUE ); } } } // // 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) ) { *AccessStatus = STATUS_SUCCESS; *GrantedAccess = PreviouslyGrantedAccess; if ( PrivilegeCount > 0 ) { SepAssemblePrivileges( PrivilegeCount, SystemSecurity, WriteOwner, Privileges ); } return( TRUE ); } else { *AccessStatus = STATUS_ACCESS_DENIED; *GrantedAccess = (ACCESS_MASK)0L; return( FALSE ); } } // // 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 // if (DesiredAccess & MAXIMUM_ALLOWED) { 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 ( SepSidInToken( EToken, &((PACCESS_ALLOWED_ACE)Ace)->SidStart )) { // // Only grant access types from this mask that have // not already been denied // CurrentGranted |= (((PACCESS_ALLOWED_ACE)Ace)->Mask & ~CurrentDenied); } continue; } 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 ( SepSidInToken(EToken, RtlCompoundAceClientSid( Ace )) && SepSidInToken(PrimaryToken, RtlCompoundAceServerSid( Ace )) ) { CurrentGranted |= (((PACCESS_ALLOWED_ACE)Ace)->Mask & ~CurrentDenied); } continue; } if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) { if ( SepSidInToken( EToken, &((PACCESS_DENIED_ACE)Ace)->SidStart )) { // // Only deny access types from this mask that have // not already been granted // CurrentDenied |= (((PACCESS_DENIED_ACE)Ace)->Mask & ~CurrentGranted); } continue; } } } // // Turn off the MAXIMUM_ALLOWED bit and whatever we found that // he was granted. If the user passed in extra bits in addition // to MAXIMUM_ALLOWED, make sure that he was granted those access // types. If not, he didn't get what he wanted, so return failure. // Remaining &= ~(MAXIMUM_ALLOWED | CurrentGranted); if (Remaining != 0) { *AccessStatus = STATUS_ACCESS_DENIED; *GrantedAccess = 0; return(FALSE); } *GrantedAccess = CurrentGranted | PreviouslyGrantedAccess; if ( *GrantedAccess != 0 ) { *AccessStatus = STATUS_SUCCESS; if ( PrivilegeCount != 0 ) { SepAssemblePrivileges( PrivilegeCount, SystemSecurity, WriteOwner, Privileges ); } return(TRUE); } else { *AccessStatus = STATUS_ACCESS_DENIED; return(FALSE); } } // if MAXIMUM_ALLOWED... for ( i = 0, Ace = FirstAce( Dacl ) ; ( i < AceCount ) && ( Remaining != 0 ) ; i++, Ace = NextAce( Ace ) ) { if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) { if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) { if ( SepSidInToken( EToken, &((PACCESS_ALLOWED_ACE)Ace)->SidStart ) ) { Remaining &= ~((PACCESS_ALLOWED_ACE)Ace)->Mask; } continue; } 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, RtlCompoundAceClientSid( Ace )) && SepSidInToken(PrimaryToken, RtlCompoundAceServerSid( Ace )) ) { Remaining &= ~((PACCESS_ALLOWED_ACE)Ace)->Mask; } continue; } if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) { if ( SepSidInToken( EToken, &((PACCESS_DENIED_ACE)Ace)->SidStart ) ) { if (Remaining & ((PACCESS_DENIED_ACE)Ace)->Mask) { break; } } } } } if (Remaining != 0) { *GrantedAccess = 0; *AccessStatus = STATUS_ACCESS_DENIED; return(FALSE); } *GrantedAccess = DesiredAccess | PreviouslyGrantedAccess; if ( *GrantedAccess == 0 ) { *AccessStatus = STATUS_ACCESS_DENIED; return( FALSE ); } *AccessStatus = STATUS_SUCCESS; if ( PrivilegeCount != 0 ) { SepAssemblePrivileges( PrivilegeCount, SystemSecurity, WriteOwner, Privileges ); } return(TRUE); } NTSTATUS NtAccessCheck ( IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN HANDLE ClientToken, IN ACCESS_MASK DesiredAccess, IN PGENERIC_MAPPING GenericMapping, OUT PPRIVILEGE_SET PrivilegeSet, IN OUT PULONG PrivilegeSetLength, OUT PACCESS_MASK GrantedAccess, OUT PNTSTATUS AccessStatus ) /*++ Routine Description: See module abstract. Arguments: SecurityDescriptor - Supplies the security descriptor protecting the object being accessed ClientToken - Supplies the handle of the user's token. DesiredAccess - Supplies the desired access mask. GenericMapping - Supplies the generic mapping associated with this object type. PrivilegeSet - A pointer to a buffer that upon return will contain any privileges that were used to perform the access validation. If no privileges were used, the buffer will contain a privilege set consisting of zero privileges. PrivilegeSetLength - The size of the PrivilegeSet buffer in bytes. GrantedAccess - Returns an access mask describing the granted access. AccessStatus - Status value that may be returned indicating the reason why access was denied. Routines should avoid hardcoding a return value of STATUS_ACCESS_DENIED so that a different value can be returned when mandatory access control is implemented. Return Value: STATUS_SUCCESS - The attempt proceeded normally. This does not mean access was granted, rather that the parameters were correct. STATUS_GENERIC_NOT_MAPPED - The DesiredAccess mask contained an unmapped generic access. STATUS_BUFFER_TOO_SMALL - The passed buffer was not large enough to contain the information being returned. STATUS_NO_IMPERSONTAION_TOKEN - The passed Token was not an impersonation token. --*/ { ACCESS_MASK LocalGrantedAccess; KPROCESSOR_MODE PreviousMode; NTSTATUS Status; PTOKEN Token; PSECURITY_DESCRIPTOR CapturedSecurityDescriptor = NULL; ACCESS_MASK PreviouslyGrantedAccess = 0; GENERIC_MAPPING LocalGenericMapping; PPRIVILEGE_SET Privileges = NULL; SECURITY_SUBJECT_CONTEXT SubjectContext; ULONG LocalPrivilegeSetLength; PAGED_CODE(); PreviousMode = KeGetPreviousMode(); if (PreviousMode == KernelMode) { *AccessStatus = STATUS_SUCCESS; *GrantedAccess = DesiredAccess; return(STATUS_SUCCESS); } try { ProbeForWrite( AccessStatus, sizeof(NTSTATUS), sizeof(ULONG) ); ProbeForWrite( GrantedAccess, sizeof(ACCESS_MASK), sizeof(ULONG) ); ProbeForRead( PrivilegeSetLength, sizeof(ULONG), sizeof(ULONG) ); ProbeForWrite( PrivilegeSet, *PrivilegeSetLength, sizeof(ULONG) ); ProbeForRead( GenericMapping, sizeof(GENERIC_MAPPING), sizeof(ULONG) ); LocalGenericMapping = *GenericMapping; LocalPrivilegeSetLength = *PrivilegeSetLength; } except (EXCEPTION_EXECUTE_HANDLER) { return( GetExceptionCode() ); } if (DesiredAccess & ( GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL )) { return(STATUS_GENERIC_NOT_MAPPED); } // // Obtain a pointer to the passed token // Status = ObReferenceObjectByHandle( ClientToken, // Handle (ACCESS_MASK)TOKEN_QUERY, // DesiredAccess SepTokenObjectType, // ObjectType PreviousMode, // AccessMode (PVOID *)&Token, // Object 0 // GrantedAccess ); if (!NT_SUCCESS(Status)) { return( Status ); } // // It must be an impersonation token, and at impersonation // level of Identification or above. // if (Token->TokenType != TokenImpersonation) { ObDereferenceObject( Token ); return( STATUS_NO_IMPERSONATION_TOKEN ); } if ( Token->ImpersonationLevel < SecurityIdentification ) { ObDereferenceObject( Token ); return( STATUS_BAD_IMPERSONATION_LEVEL ); } // // 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 )) { ObDereferenceObject( Token ); try { *AccessStatus = Status; *GrantedAccess = 0; } except(EXCEPTION_EXECUTE_HANDLER) { return( GetExceptionCode() ); } return( STATUS_SUCCESS ); } // // Make sure the passed privileges buffer is large enough for // whatever we have to put into it. // if (Privileges != NULL) { if ( ((ULONG)SepPrivilegeSetSize( Privileges )) > LocalPrivilegeSetLength ) { ObDereferenceObject( Token ); SeFreePrivileges( Privileges ); try { *PrivilegeSetLength = SepPrivilegeSetSize( Privileges ); } except ( EXCEPTION_EXECUTE_HANDLER ) { return( GetExceptionCode() ); } return( STATUS_BUFFER_TOO_SMALL ); } else { try { RtlCopyMemory( PrivilegeSet, Privileges, SepPrivilegeSetSize( Privileges ) ); } except ( EXCEPTION_EXECUTE_HANDLER ) { ObDereferenceObject( Token ); SeFreePrivileges( Privileges ); return( GetExceptionCode() ); } } SeFreePrivileges( Privileges ); } else { // // No privileges were used, construct an empty privilege set // if ( LocalPrivilegeSetLength < sizeof(PRIVILEGE_SET) ) { ObDereferenceObject( Token ); try { *PrivilegeSetLength = sizeof(PRIVILEGE_SET); } except ( EXCEPTION_EXECUTE_HANDLER ) { return( GetExceptionCode() ); } return( STATUS_BUFFER_TOO_SMALL ); } try { PrivilegeSet->PrivilegeCount = 0; PrivilegeSet->Control = 0; } except ( EXCEPTION_EXECUTE_HANDLER ) { ObDereferenceObject( Token ); return( GetExceptionCode() ); } } // // 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)) { ObDereferenceObject( Token ); try { *AccessStatus = Status; } except (EXCEPTION_EXECUTE_HANDLER) { return( GetExceptionCode() ); } return(STATUS_SUCCESS); } if ( CapturedSecurityDescriptor == NULL ) { // // If there's no security descriptor, then we've been // called without all the parameters we need. // Return invalid security descriptor. // ObDereferenceObject( Token ); return(STATUS_INVALID_SECURITY_DESCR); } // // A valid security descriptor must have an owner and a group // if ( SepOwnerAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor ) == NULL || SepGroupAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor ) == NULL ) { SeReleaseSecurityDescriptor ( CapturedSecurityDescriptor, PreviousMode, FALSE ); ObDereferenceObject( Token ); return( STATUS_INVALID_SECURITY_DESCR ); } 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) { LocalGrantedAccess = PreviouslyGrantedAccess; Status = STATUS_SUCCESS; } else { SepAccessCheck ( CapturedSecurityDescriptor, SubjectContext.PrimaryToken, Token, DesiredAccess, &LocalGenericMapping, PreviouslyGrantedAccess, PreviousMode, &LocalGrantedAccess, NULL, &Status ); } SepReleaseTokenReadLock( Token ); SeReleaseSubjectContext( &SubjectContext ); SeReleaseSecurityDescriptor ( CapturedSecurityDescriptor, PreviousMode, FALSE ); ObDereferenceObject( Token ); try { *AccessStatus = Status; *GrantedAccess = LocalGrantedAccess; } except (EXCEPTION_EXECUTE_HANDLER) { return( GetExceptionCode() ); } return(STATUS_SUCCESS); } VOID SeFreePrivileges( IN PPRIVILEGE_SET Privileges ) /*++ Routine Description: This routine frees a privilege set returned by SeAccessCheck. Arguments: Privileges - Supplies a pointer to the privilege set to be freed. Return Value: None. --*/ { PAGED_CODE(); ExFreePool( Privileges ); } BOOLEAN SeAccessCheck ( IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext, IN BOOLEAN SubjectContextLocked, IN ACCESS_MASK DesiredAccess, IN ACCESS_MASK PreviouslyGrantedAccess, OUT PPRIVILEGE_SET *Privileges OPTIONAL, IN PGENERIC_MAPPING GenericMapping, IN KPROCESSOR_MODE AccessMode, OUT PACCESS_MASK GrantedAccess, OUT PNTSTATUS AccessStatus ) /*++ Routine Description: See module abstract This routine MAY perform tests for the following privileges: SeTakeOwnershipPrivilege SeSecurityPrivilege depending upon the accesses being requested. This routine may also check to see if the subject is the owner of the object (to grant WRITE_DAC access). Arguments: SecurityDescriptor - Supplies the security descriptor protecting the object being accessed SubjectSecurityContext - A pointer to the subject's captured security context SubjectContextLocked - Supplies a flag indiciating whether or not the user's subject context is locked, so that it does not have to be locked again. DesiredAccess - Supplies the access mask that the user is attempting to acquire PreviouslyGrantedAccess - Supplies any accesses that the user has already been granted, for example, as a result of holding a privilege. Privileges - Supplies a pointer in which will be returned a privilege set indicating any privileges that were used as part of the access validation. GenericMapping - Supplies the generic mapping associated with this object type. AccessMode - Supplies the access mode to be used in the check GrantedAccess - Pointer to a returned access mask indicatating the granted access AccessStatus - Status value that may be returned indicating the reason why access was denied. Routines should avoid hardcoding a return value of STATUS_ACCESS_DENIED so that a different value can be returned when mandatory access control is implemented. Return Value: BOOLEAN - TRUE if access is allowed and FALSE otherwise --*/ { BOOLEAN Success; PAGED_CODE(); if (AccessMode == KernelMode) { if (DesiredAccess & MAXIMUM_ALLOWED) { // // Give him: // GenericAll // Anything else he asked for // *GrantedAccess = GenericMapping->GenericAll; *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 { Success = SepAccessCheck( SecurityDescriptor, SubjectSecurityContext->PrimaryToken, SubjectSecurityContext->ClientToken, DesiredAccess, GenericMapping, PreviouslyGrantedAccess, AccessMode, GrantedAccess, Privileges, AccessStatus ); #if DBG if (!Success && SepShowAccessFail) { DbgPrint("SE: Access check failed\n"); 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 ); } return( Success ); } } BOOLEAN SeProxyAccessCheck ( IN PUNICODE_STRING Volume, IN PUNICODE_STRING RelativePath, IN BOOLEAN ContainerObject, IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext, IN BOOLEAN SubjectContextLocked, IN ACCESS_MASK DesiredAccess, IN ACCESS_MASK PreviouslyGrantedAccess, OUT PPRIVILEGE_SET *Privileges OPTIONAL, IN PGENERIC_MAPPING GenericMapping, IN KPROCESSOR_MODE AccessMode, OUT PACCESS_MASK GrantedAccess, OUT PNTSTATUS AccessStatus ) /*++ Routine Description: Arguments: Volume - Supplies the volume information of the file being opened. RelativePath - The volume-relative path of the file being opened. The full path of the file is the RelativePath appended to the Volume string. ContainerObject - Indicates if the access is to a container object (TRUE), or a leaf object (FALSE). SecurityDescriptor - Supplies the security descriptor protecting the object being accessed SubjectSecurityContext - A pointer to the subject's captured security context SubjectContextLocked - Supplies a flag indiciating whether or not the user's subject context is locked, so that it does not have to be locked again. DesiredAccess - Supplies the access mask that the user is attempting to acquire PreviouslyGrantedAccess - Supplies any accesses that the user has already been granted, for example, as a result of holding a privilege. Privileges - Supplies a pointer in which will be returned a privilege set indicating any privileges that were used as part of the access validation. GenericMapping - Supplies the generic mapping associated with this object type. AccessMode - Supplies the access mode to be used in the check GrantedAccess - Pointer to a returned access mask indicatating the granted access AccessStatus - Status value that may be returned indicating the reason why access was denied. Routines should avoid hardcoding a return value of STATUS_ACCESS_DENIED so that a different value can be returned when mandatory access control is implemented. Return Value: BOOLEAN - TRUE if access is allowed and FALSE otherwise --*/ { return SeAccessCheck ( SecurityDescriptor, SubjectSecurityContext, SubjectContextLocked, DesiredAccess, PreviouslyGrantedAccess, Privileges, GenericMapping, AccessMode, GrantedAccess, AccessStatus ); } 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. 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; if (!TokenLocked) { SepAcquireTokenReadLock( Token ); } Owner = SepOwnerAddrSecurityDescriptor( ISecurityDescriptor ); ASSERT( Owner != NULL ); rc = SepSidInToken( Token, Owner ); if (!TokenLocked) { SepReleaseTokenReadLock( Token ); } return( rc ); } BOOLEAN SeFastTraverseCheck( PSECURITY_DESCRIPTOR SecurityDescriptor, ACCESS_MASK TraverseAccess, KPROCESSOR_MODE AccessMode ) /*++ Routine Description: This routine will examine the DACL of the passed Security Descriptor to see if WORLD has Traverse access. 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. Arguments: SecurityDescriptor - The Security Descriptor protecting the container object being traversed. TraverseAccess - Access mask describing Traverse access for this object type. AccessMode - Supplies the access mode to be used in the check Return Value: TRUE - WORLD has Traverse access to this container. FALSE otherwise. --*/ { PACL Dacl; ULONG i; PVOID Ace; ULONG AceCount; PAGED_CODE(); if ( AccessMode == KernelMode ) { return( TRUE ); } if (SecurityDescriptor == NULL) { return( FALSE ); } // // See if there is a valid DACL in the passed Security Descriptor. // No DACL, no security, all is granted. // Dacl = SepDaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ); // // If the SE_DACL_PRESENT bit is not set, the object has no // security, so all accesses are granted. // // Also grant all access if the Dacl is NULL. // if ( !SepAreControlBitsSet( (PISECURITY_DESCRIPTOR)SecurityDescriptor, SE_DACL_PRESENT ) || (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 ); } // // There's stuff in the DACL, walk down the list and see // if WORLD has been granted TraverseAccess // 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 ( (TraverseAccess & ((PACCESS_ALLOWED_ACE)Ace)->Mask) ) { if ( RtlEqualSid( SeWorldSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart ) ) { return( TRUE ); } } } else { if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) { if ( (TraverseAccess & ((PACCESS_DENIED_ACE)Ace)->Mask) ) { if ( RtlEqualSid( SeWorldSid, &((PACCESS_DENIED_ACE)Ace)->SidStart ) ) { return( FALSE ); } } } } } } return( FALSE ); }