1887 lines
46 KiB
C
1887 lines
46 KiB
C
|
/*++
|
|||
|
|
|||
|
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 );
|
|||
|
}
|