Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

Access Checks, part 2

Access Checks, part 2

  • Comments 4

Yesterday I discussed the format of an ACL.  For todays post, I want to talk about how the system uses ACLs to perform access checks.  Once again, the post on security terms is likely to be helpful.

There are two forms of access check accessable from the same API – an access check can either be for a specific set of access rights, or it can be for the well known right “MAXIMUM_ALLOWED” – a MAXIMUM_ALLOWED access check basically grants the user as many rights as they can have, and no more.  In general, asking for MAXIMUM_ALLOWED is not advised, instead you should ask for the rights you need and no more.

As I mentioned yesterday, Access Check takes three inputs: The user’s token, a desired access mask, and a security descriptor, and performs bitwise manipulation to determine a Boolean result: granted or not (the actual check is more complicated than that, but…).

In a nutshell, the AccessCheck logic is as follows:

Iterate through the ACEs in the ACL.
            If the SID in the ACE is active in the users token, then if the ACE is a grant ACE, turn off the bits in the desired access mask that correspond to the bits in the AccessMask field in the ACE.
            If the current desired access mask is 0, grant access.
            If the ACE is a deny ACE, then if the bits in the AccessMask are on in the desired access mask, then deny access.

One feature of this algorithm that merits calling out: The user is denied access by default – if you aren’t granted access to the resource by virtue of the SIDs active in your token, then you don’t have access to the resource.  Period, end of discussion.

The addition of restricted SIDs makes the access check process a check a smidge more complicated.  There are actually two checks performed, the first against the ACL using the normal SIDs in the users token.  If that succeeds, a second check is done on the restricted SIDs in the users token.  If either fails, access is denied.  In addition, there are two types of SIDs that can appear in the token – “normal” SIDs and “deny-only” SIDs – the deny-only SIDs will never grant access, but WILL deny access (in other words, the deny-only SIDs only apply for deny ACEs, not grant ACEs).  You can find the list of SIDs used in the AccessCheck process by calling GetTokenInformation asking for the TokenGroupsAndPrivileges information level.  The TOKEN_GROUPS_AND_PRIVILEGES structure contains the both lists of SIDs checked in AccessCheck.  The list of SIDs used for the first check is contained in the Sids structure field.  The list of restricted SIDs used for the second check is contained in the RestrictedSids structure field.

The following is a rough pseudo-code version for the AccessCheck API:

AccessCheck (desiredAccess, Token, SecurityDescriptor)

{

      //

      // Handle the implicit WRITE_DAC and READ_CONTROL access rights.

      //

      if (<SecurityDescriptor->Owner is active in Token>)

      {

            desiredAccess &= ~(WRITE_DAC | READ_CONTROL);

            grantedAccess |= (WRITE_DAC | READ_CONTROL);

      }

      //

      //    Handle the ACCESS_SYSTEM_SECURITY meta access right.

      //

      if (desiredAccess & ACCESS_SYSTEM_SECURITY)

      {

            if (SE_SYSTEM_SECURITY_PRIVILEGE enabled in Token)

            {

                  desiredAccess &= ~ACCESS_SYSTEM_SECURITY;

                  grantedAccess |= ACCESS_SYSTEM_SECURITY;

            }

            else if (desiredAccess != MAXIMUM_ALLOWED)

                  return failure;

            }

      }

      //

      //    Handle NULL DACL case.

      //

      if (SecurityDescriptor->Control & SE_DACL_PRESENT == 0)

            return success, desiredAccess;
      //
      //    Empty DACL means no access.
      //

      if (SecurityDescriptor->Dacl->AceCount == 0)

            return failure, grantedAccess;

      //

      //    If we’ve granted all the desired accesses, access is allowed.

      //

      if (desiredAccess == 0)

            return success, grantedAccess;

 

      //

      //    Handle MAXIMUM_ALLOWED meta-right

      //

      if (desiredAccess == MAXIMUM_ALLOWED)

      {

            result = <MAXIMUM_ALLOWED Access Check, with normal token SIDs>

            If (result == success && Token is restricted)

            {

                  result = <MAXIMUM_ALLOWED Access Check, with restricted token SIDs>

            }

return result;
      }

      else  // Handle “normal” access rights.

      {

            result = <Simple Access Check with normal token SIDs>

            If (result == success && Token is restricted)

            {

            result = <Simple Access Check with restricted token SIDs>

      }

      }

}

 The MAXIMUM_ALLOWED access check is (roughly):

for (i = 0; i< SecurityDescriptor->Dacl->AceCount ; i+=1)

{

      Ace = SecurityDescriptor->Dacl->Ace[i];

      if (<Ace->Sid is active in Token>)

      {

            if (Ace->AceType==ACCESS_ALLOWED_ACE)

            {

                  grantedAccess |= Ace->AccessMask;

            }

            else if (Ace->AceType==ACCESS_DENIED_ACE|| Ace->AceType==ACCESS_DENIED_OBJECT_ACE)

            {

                  deniedAccess |= Ace->AccessMask;

            }

      }

}

returnedAccess = grantedAccess | ~deniedAccess;

if (returnedAccess != 0)

      return success, returnedAccess;

else

      return failure, returnedAccess;

The “Normal” access check is (roughly):

for (i = 0; i< SecurityDescriptor->Dacl->AceCount ; i+=1)

{

      Ace = SecurityDescriptor->Dacl->Ace[i];

      if (<Ace->Sid is active in Token>)

      {

            if (Ace->AceType==ACCESS_ALLOWED_ACE)

            {

                  desiredAccess &= ~Ace->AccessMask;

                  grantedAccess |= (Ace->AccessMask & ~deniedAccess);

            }

            else if (Ace->AceType==ACCESS_DENIED_ACE||

                  Ace->AceType==ACCESS_DENIED_OBJECT_ACE)

            {

                  deniedAccess |= (Ace->AceMask & ~grantedAccess);

                  if (desiredAccess & Ace->AceMask)

                  {

                        return failure, desiredAccess;

                  }

            }

      }

      if (desiredAccess==0)

      {

            return success, grantedAccess|~deniedAccess;

      }

}

if (desiredAccess != 0)

{

      return failure, grantedAccess|~deniedAccess;

}

The big difference between the “normal” and the “maximum allowed” access check is that the normal access check has an early-out when all the desired accesses are granted, while the maximum allowed access check needs to iterate over all the ACEs to determine the full set of rights granted to the user.

Edit: Added recommendation against using MAXIMUM_ALLOWED.

 

  • This http://64.233.167.104/search?q=cache:L24fGPz5jeQJ:docs.waglo.com/cygwin/ntsec.html (a google cache since the orignal seems to be unavailable) is an interesting page from the Cygwin (a free POSIX environment for Windows) docs that gives a good description of how ACLs and access checks are performed, a comparison of POSIX security vs NT security, how to implement POSIX permissions using NT ACLs and a complaint about the GUI ACL editor re-arranging ACEs.

    From the page:"The system reads [the ACLs] in sequence until either any needed right is denied or all needed rights are granted. Later ACEs are then not taken into account."

    I don't like the built in ACL editor; the one before Windows 2000 didn't show access denied ACEses at all, and the new one will mush an access allowed ACE and an access denied ACE into one entry in the list (so you can have allowed/denied checkboxes next to each other). Like it says in the page, the ACE order is important but the dialog re-arranges them by SID type (groups, then users). Also, a set owner permission will allow you to set the owner to anybody but the dialog will only let you set the owner to yourself, or your primary group; although this was fixed in WS2K3. I know you aren't a UI person, but I still wanted to complain :)
  • A good point Foolhardy.

    The problem is that the UI expects that the ACL is in canonical order, but if the ACL isn't in canonical order, it reorders it to be canonical.

    Since the UI will only ever generate an ACL in canonical order, this isn't a huge problem.

    On the other hand, there are issues with inherited ACLs that can force a non canonical ordering of ACEs, it wasn't until the Win2K ACL editor that that case was correctly handled (the Win2K ACL editor ignores the order of inherited ACEs).
  • Ping Back来自:www.donews.net
Page 1 of 1 (4 items)