2.5.3.2 Access Check Algorithm Pseudocode

In overview, the Access Check algorithm takes an access request and a security descriptor. It iterates through the DACL of the security descriptor, processing each ACE. If the ACE contains a SID that is also in the Token authorization context, then the ACE is processed, otherwise it is skipped. If an ACE grants access to that SID, then those access rights from the Access Request Mask are considered satisfied, and removed from the mask. If the ACE denies access to that SID, and the access rights in the ACE are present in the request mask, the whole request is denied. At the end of the algorithm, if there are any access rights still pending in the Access Request Mask, then the request is considered denied.

There are two noteworthy configurations of the security descriptor in light of the access check algorithm: an empty DACL, and a NULL (or absent) DACL. No DACL in the security descriptor implies that there is no policy in place to govern access to the object; any access check will succeed. An empty DACL, where the DACL is marked as being present but contains no ACEs, means that no principal can gain access to the object, except through the implied access of the owner.

If the access request is MAXIMUM_ALLOWED, the algorithm operates in a different mode.  It iterates through every ACE in the DACL of the security descriptor, remembering which access rights were granted or denied for each ACE. After all ACEs have been examined, the complete set of grantable access rights is computed and returned via the GrantedAccess parameter (described later in this section).

Note that the use of MAXIMUM_ALLOWED is not recommended; instead, callers can request the specific minimum level of access required to accomplish their requirements.

The detailed processing of the list is as follows.

On entrance:

  • SecurityDescriptor: SECURITY_DESCRIPTOR structure that is assigned to the object.

  • Token: Authorization context as described above.

  • Access Request mask: Set of permissions requested on the object.

  • Object Tree: An array of OBJECT_TYPE_LIST structures representing a hierarchy of objects for which to check access. Each node represents an object with three values: A GUID that represents the object itself; a value called Remaining, which can be zero, and which specifies the user rights requests for that node that have not yet been satisfied; and a value called Level, which indicates the level of the object type in the hierarchy.

  • PrincipalSelfSubst SID: A SID that logically replaces the SID in any ACE that contains the well-known PRINCIPAL_SELF SID. It can be null.

  • GrantedAccess: An optional ACCESS_MASK output parameter used when the Access Request Mask parameter equals MAXIMUM_ALLOWED. Upon return this parameter contains the set of permissions granted to Token by the SecurityDescriptor.

     STATUS_CODE
     EvaluateTokenAgainstDescriptor(
         TOKEN Token,
         SECURITY_DESCRIPTOR SecurityDescriptor,
         ACCESS_MASK Access_Request_mask,
         OBJECT_TYPE_LIST Object Tree,
         Sid PrincipalSelfSubstitute,
         [out] ACCESS_MASK GrantedAccess)
      
         Dim OBJECT_TYPE_LIST LocalTree
         Dim ULONG Result
      
         Set DACL to SecurityDescriptor Dacl field
         Set SACL to SecurityDescriptor Sacl field
         Set RemainingAccess to Access Request mask
         Set AllowedAccesses to 0
         Set DeniedAccesses to 0
         Set MaxAllowedMode to FALSE
      
         IF RemainingAccess contains ACCESS_SYSTEM_SECURITY access bit THEN
             IF Token.Privileges contains SeSecurityPrivilege THEN
                 Remove ACCESS_SYSTEM_SECURITY access bit from RemainingAccess
                 Set GrantedAccess to GrantedAccess or ACCESS_SYSTEM_SECURITY
      
                 IF RemainingAccess to 0 THEN
                     Return success
                 Else
      
             ELSE
                 Set GrantedAccess to 0
                 Return access_denied
      
             END IF
         END IF
      
         IF RemainingAccess contains WRITE_OWNER access bit and Token.Privileges is not NULL THEN
             IF  Token.Privileges contains SeTakeOwnershipPrivilege THEN
               Remove WRITE_OWNER access bit from RemainingAccess
               Set GrantedAccess to GrantedAccess or WRITE_OWNER
            END IF
         END IF
      
         -- the owner of an object is always granted READ_CONTROL and WRITE_DAC.
         CALL SidInToken(Token, SecurityDescriptor.Owner, PrincipalSelfSubst)
         IF SidInToken returns True THEN
             IF DACL does not contain ACEs from object owner THEN
                 Remove READ_CONTROL and WRITE_DAC from RemainingAccess
                 Set GrantedAccess to GrantedAccess or READ_CONTROL or WRITE_DAC
             END IF
         END IF
      
         -- Support for MAXIMUM_ALLOWED 
         IF RemainingAccess contains MAXIMUM_ALLOWED access bit THEN
             Set MaxAllowedMode to TRUE
         END IF
      
         IF Object Tree is not NULL THEN
             Set LocalTree to Object Tree
      
         -- node is of type OBJECT_TYPE_LIST
             FOR each node in LocalTree DO
                 Set node.Remaining to RemainingAccess
             END FOR
      
             END IF
      
         FOR each ACE in DACL DO
             IF ACE.AceFlags does not contain INHERIT_ONLY_ACE THEN
            
                 CASE ACE.Type OF
      
                     CASE Allow Access:
      
                         CALL SidInToken( Token, ACE.Sid, and PrincipalSelfSubst )
                         IF SidInToken returns True THEN
                             IF MaxAllowedMode equals TRUE THEN
                               Set AllowedAccesses to AllowedAccesses or ACE.AccessMask
                               Set GrantedAccess to GrantedAccess or ACE.AccessMask
                             ELSE
                                 Remove ACE.AccessMask from RemainingAccess
                                 Set GrantedAccess to GrantedAccess or(RemainingAccess and 
                                     ACE.AccessMask)
                                 FOR each node in LocalTree DO
                                     Remove ACE.AccessMask from node.Remaining                                
                                 END FOR
                             END IF
                         END IF
            
                     CASE Deny Access:
                         IF ACE.AccessMask equals 0 returns True THEN Break             
      
                         CALL SidInToken( Token, ACE.Sid, PrincipalSelfSubst )
                         IF SidInToken returns True THEN
                             IF MaxAllowedMode equals TRUE THEN
                                 Set DeniedAccesses to DeniedAccesses or ACE.AccessMask
                             ELSE
                                 IF any bit of RemainingAccess is in ACE.AccessMask THEN
                                     Set GrantedAccess to 0
                                     Return access_denied
                                 END IF
                             END IF
                         END IF
            
                     CASE Object Allow Access:
      
                         CALL SidInToken( Token, ACE.Sid, PrincipalSelfSubst )
                         IF SidInToken returns True THEN
                             IF ACE.Object is contained in LocalTree THEN
                                 Locate node n in LocalTree such that 
                                   n.GUID is the same as ACE.Object
                                 Remove ACE.AccessMask from n.Remaining
                                 FOR each node ns such that ns is a descendent of n DO
                                     Remove ACE.AccessMask from ns.Remaining
                                 END FOR
                                 FOR each node np such that np is an ancestor of n DO
                                   Set np.Remaining to np.Remaining or np-1.Remaining
      
                               --  the 'or' above is a logical bitwise OR operator.  For
                               --  Some uses (like Active Directory), a hierarchical list
                               --  of types can be passed in; if the requestor is granted 
                               --  access to a specific node, this will grant access to 
                               --  all its children. The preceding lines implement this by 
                               --  removing, from each child, the permissions just found for
                               --  the parent. The change is propagated upwards in
                               --  the tree: once a permission request has been satisfied
                               --  we can tell the next-higher node that we do not need
                               --  to inherit it from the higher node (we already have it 
                               --  in the current node). And since we must not blindly 
                               --  replace the parent's RemainingAccess, we BIT_OR the  
                               --  parent's RemainingAccess with the current node's. This 
                               --  way, if the parent needs, say, READ_CONTROL, and the
                               --  current node was just granted that, the parent's 
                               --  RemainingAccess still contains this bit since satisfying 
                               --  the request at a lower level does nothing to affect 
                               --  the higher level node. Active Directory has its own  
                               --  checking rules--see [MS-ADTS] section 3.1.1.4.3. 
                               
                                 END FOR
                             END IF
                         END IF
            
                     CASE Object Deny Access:
      
                         CALL SidInToken( Token, ACE.Sid, PrincipalSelfSubst )
                         IF SidInToken returns True THEN
                             Locate node n in LocalTree such that 
                               n.GUID is the same as ACE.Object
                             IF n exists THEN
                                 If any bit of n.Remaining is in ACE.AccessMask THEN
                                     Set GrantedAccess to 0
                                     Return access_denied
                                 END IF
                             END IF
                         END IF
      
                     CASE Allow Access Callback Ace:
      
                         EvaluateAceCondition(Token,
                                              Sacl,
                                              ApplicationData,
                                              ApplicationDataSize) returning Result 
      
                         IF Result is 1 THEN
                             IF (SidInToken(Token, ACE.Sid, PrincipalSelfSubst)) THEN
                                 IF MaxAllowedMode equals TRUE THEN
                                     Set GrantedAccess to GrantedAccess or ACE.AccessMask
                                     Set AllowedAccesses to AllowedAccesses or ACE.AccessMask
      
                                 ELSE
                                     Remove  ACE.AccessMask from RemainingAccess
      
                                     SET n = root node of object tree
                                     FOR each node np such that np is an ancestor of n DO
                                       Set np.Remaining to np.Remaining or np-1.Remaining
      
                               --  the 'or' above is a logical bitwise OR operator.  For
                               --  Some uses (like Active Directory), a hierarchical list
                               --  of types can be passed in; if the requestor is granted 
                               --  access to a specific node, this will grant access to 
                               --  all children. The preceding lines implement this by 
                               --  removing, from each child, the permissions just found for 
                               --  the parent. The change is propagated upwards in
                               --  the tree: once a permission request has been satisfied
                               --  we can tell the next-higher node that we do not need
                               --  to inherit it from the higher node (we already have it 
                               --  in the current node). And since we must not blindly 
                               --  replace the parent's RemainingAccess, we BIT_OR the  
                               --  parent's RemainingAccess with the current node's. This 
                               --  way, if the parent needs, say, READ_CONTROL, and the
                               --  current node was just granted that, the parent's 
                               --  RemainingAccess still contains this bit since satisfying 
                               --  the request at a lower level does nothing to affect 
                               --  the higher level node.
      
                                     END FOR
                                 END IF
                             END IF
                         END IF
                 END CASE            
             END IF
         END FOR
      
         IF MaxAllowedMode equals TRUE THEN
             -- The not operator below is a bit-wise operator
             Set GrantedAccess to AllowedAccesses and (not DeniedAccesses)
             IF GrantedAccess not equals 0 THEN
                 Return success
             ElSE
                 Return access_denied
      
     END IF
      
     SET GrantedAccess to 0
      
         IF RemainingAccess to 0 THEN
             Return success
         Else
             Return access_denied
         END IF
      
     END-SUBROUTINE
      
     STATUS_CODE
     AccessCheck(
         TOKEN Token,
         SECURITY_DESCRIPTOR SecurityDescriptor,
         ACCESS_MASK Access Request mask,
     OBJECT_TYPE_LIST Object Tree,
     Sid PrincipalSelfSubstitute,
         [out] ACCESS_MASK GrantedAccess)
      
         Dim CentralAccessPolicy CentralizedAccessPolicy
         Dim SECURITY_DESCRIPTOR CaprSecurityDescriptor
         Dim SECURITY_DESCRIPTOR StagedCaprSecurityDescriptor
         Dim ACCESS_MASK DesiredAccess
         Dim ACCESS_MASK CentralAccessPolicyEffectiveAccess
         Dim ACCESS_MASK CentralAccessPolicyEntryEffectiveAccess
         Dim ACCESS_MASK CentralAccessPolicyStagedAccess
         Dim ACCESS_MASK CentralAccessPolicyEntryStagedAccess
         Dim ULONG Result
         Dim STATUS_CODE Status
         
         Set DACL to SecurityDescriptor Dacl field
         Set SACL to SecurityDescriptor Sacl field
         Set RemainingAccess to Access Request mask
         Set AllowedAccesses to 0
         Set DeniedAccesses to 0
         Set DesiredAccess to Access Request mask
      
         CALL EvaluateTokenAgainstDescriptor(Token,
                                             SecurityDescriptor,
                                             DesiredAccess,
                                             Object Tree,
                                             PrincipalSelfSubstitute,
                                             GrantedAccess) returning Status
      
         IF Status is access_denied THEN
             return Status
         END IF
      
         CALL GetCentralizedAccessPolicy(SACL) returning CentralizedAccessPolicy
      
         IF CentralizedAccessPolicy is not NULL THEN
      
             Set CentralAccessPolicyEffectiveAccess to GrantedAccess
             Set CentralAccessPolicyStagedAccess to GrantedAccess
      
             FOR each CentralAccessPolicyRule in CentralAccessPolicy.RulesList
      
                EvaluateAceCondition(Token,
                                     SACL,
                                     AppliesTo,
                                     AppliesToSize) returning Result
      
                 IF Result is not 1 THEN
                     GOTO NextRule
                 END IF
      
                 Copy SecurityDescriptor to CaprSecurityDescriptor
                 Set CaprSecurityDescriptor.DACL to
         CentralAccessPolicyRule.EffectiveCentralAccessPolicy.AccessCondition.DACL
      
                 EvaluateTokenAgainstDescriptor
                                        (Token,
                                         CaprSecurityDescriptor,
                                         DesiredAccess,
                                         NULL,
                                         PrincipalSelfSubstitute,
                                         CentralAccessPolicyEntryEffectiveAccess)
      
                 -- The and operator below is a bit-wise operator
                 Set CentralAccessPolicyEffectiveAccess to 
              CentralAccessPolicyEffectiveAccess and CentralAccessPolicyEntryEffectiveAccess
      
                -- StagingLocalPolicyEnabled = True if MS-GPAC ADM variable
                -- "System Advanced Audit Policy" (MS-GPAC section 3.2.1.1) contains the GUID
                -- for "Central Access Policy Staging" as specified in MS-GPAC section 2.2.1.2
      
                 IF IfStagingLocalPolicyEnabled THEN 
      
                     Copy SecurityDescriptor to StagedCaprSecurityDescriptor
                     Set StagedCaprSecurityDescriptor.DACL to 
              CentralAccessPolicyRule.StagedCentralAccessPolicy.AccessControl.DACL
      
                     EvaluateTokenAgainstDescriptor
                                             (Token,
                                             StagedCaprSecurityDescriptor,
                                             DesiredAccess,
                                             NULL,
                                             PrincipalSelfSubstitute,
                                             CentralAccessPolicyEntryStagedAccess)
      
                     -- The and operator below is a bit-wise operator
      
                     Set CentralAccessPolicyStagedAccess to CentralAccessPolicyStagedAccess 
                                                 and CentralAccessPolicyEntryStagedAccess
      
                 ELSE IF CentralAccessPolicyEffectiveAccess is 0 THEN
                     Set GrantedAccess to 0
                     return access_denied
                 END IF
      
      
     NextRule:
             END FOR
      
             IF CentralAccessPolicyEffectiveAccess is not equal to 
                                             CentralAccessPolicyStagedAccess THEN
                 -- Log the difference between the Effective and Staged Access
             END IF
      
             -- The "not" and "and" operator below is a bit-wise operator
      
             Set AllowedAccess to AllowedAccess and CentralAccessPolicyEffectiveAccess
             Set RemainingAccess to DesiredAccess and not CentralAccessPolicyEffectiveAccess
      
             FOR each node in Object Tree DO
                 Set node.Remaining to RemainingAccess
             END FOR
      
         ELSE
            Return success
         END IF
      
         IF MaxAllowedMode equals TRUE THEN
             -- The not operator below is a bit-wise operator
             Set GrantedAccess to AllowedAccesses and (not DeniedAccesses)
      
             IF GrantedAccess is 0 THEN
                 Return access_denied
             Else
                 Return success
             END IF
      
         END IF
      
         SET GrantedAccess to 0
        
         IF RemainingAccess is 0 THEN
             Return success
         Else
             Return access_denied
         END IF
      
     END-SUBROUTINE