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 should 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 should 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
END IF
END IF
IF RemainingAccess contains WRITE_OWNER access bit THEN
IF Token.Privileges contains SeTakeOwnershipPrivilege THEN
Remove WRITE_OWNER access bit from RemainingAccess
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
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
ELSE
Remove ACE.AccessMask from RemainingAccess
FOR each node in LocalTree DO
Remove ACE.AccessMask from node.Remaining
END FOR
END IF
END IF
CASE Deny Access:
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
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.
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
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
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 CASE
END IF
END FOR
Set GrantedAccess to AllowedAccesses and (not DeniedAccesses)
IF MaxAllowedMode equals TRUE THEN
-- The not operator below is a bit-wise operator
Set GrantedAccess to AllowedAccesses and (not DeniedAccesses)
Return success
END IF
IF
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,
[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,
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 DesiredAccess
Set CentralAccessPolicyStagedAccess to DesiredAccess
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,
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,
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)
Return success
END IF
IF RemainingAccess is 0 THEN
Return success
Else
Return access_denied
END IF
END-SUBROUTINE