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