Access Checks

Before I begin today’s post, a caveat: In this discussion, when I use the term “security”, I don’t mean “security defect free”, instead I mean “using the NT security subsystem”.  The two often become confused, and I just want to make sure that people know which aspect of security I’m talking about.  Also, people should refer to my glossary on security terms if there is any confusion about terms I’m using inside this post.

Whenever discussions of NT security come up, the first comment that people make is “Oooh, security is hard”.  And I always respond “No it’s not, security in NT is just bit manipulation”.

Well, if you look at it at the fine grained details (the myriad types of groups, the internal data structure of a SID, how to look up user information from the SAM database, etc) then security can seem daunting.

But the reality is that the core of NT’s security architecture is a single routine: AccessCheck.  And the AccessCheck API is all about bit twiddling. Over the next couple of posts, I’ll describe how NT’s access check functionality works.  There are about a half a dozen functions that perform access checks, but they all share the basic structure of the AccessCheck API (in fact, internally all the NT access check functions are performed by a single routine).

At its core, the AccessCheck API takes three inputs: The user’s token, a desired access mask, and a security descriptor.  The access mask and the user’s token are applied to the SecurityDescriptor and the API determines if the user should have access or not.

Before we can discuss AccessCheck, we first need to discuss security descriptors (or, to be more specific, Access Control Lists (ACLs)).  In general, in NT, a security descriptor has four components:

1)      The security descriptor owner (a SID).

2)      The security descriptor group (a SID).

3)      The security descriptor discretionary ACL (DACL).

4)      The security descriptor System ACL (SACL).

First, the easiest of the four: The security descriptor group exists in NT for posix compliance; it’s not used as a part of the access check process…

The security descriptor owner is usually the creator of the object (if the creator is a member of the administrators group, then the owner of the object is the administrators group, this is because all administrators are considered to be interchangeable for the purposes of object ownership).  The owner of a security descriptor is automatically granted WRITE_DAC access to the object – in other words, the owner of an object can ALWAYS modify the security descriptor of that object.

The SACL describes the auditing semantics of the object – if the application calls an access check function that does auditing (AccessCheckAndAuditAlarm, AccessCheckByTypeAndAuditAlarm, AccessCheckByTypeResultListAndAuditAlarm, and AccessCheckByTypeResultListAndAuditAlarmByHandle), then after the AccessCheck functionality is executed, the system applies the result to the SACL and if the SACL indicates that the result should be audited, then an audit entry is generated.

The DACL’s the big kahuna.  It’s the structure that completely describes the access rights that the user has to the object being protected. 

The DACL and SACL are both access control lists, and thus share the same structure.

Essentially an access list is an ordered list of Access Control Entries (ACEs).  There are about 15 different types of access control entries, in general they fall into four different categories:

1)      Access Allowed ACEs – these grant access to an object for a particular SID.

2)      Access Denied ACEs – these deny access to an object for a particular SID.

3)      Audit ACEs – these indicate if an audit should be generated when a particular user is either granted or denied a particular access right.

4)      Alarm ACEs – currently unused, but similar to audit aces – an alarm ACE would trigger a system alarm if their criteria were met instead of generating an audit record.

Regardless of the type of the ACE, each ACE shares a common header, the ACE_HEADER.  There are two important fields in the ACE header – the first is the ACE type, which describes the semantics of the ACE, the second is the AceFlags – this field describes the ACL inheritance rules for this ACE.  It also contains the FAILED_ACCESS_ACE_FLAG and SUCCESSFUL_ACCESS_ACE_FLAG which is used for the audit ACEs.

Tomorrow: How are ACL’s used in the access check algorithm.


Comments (4)

  1. Anonymous says:

    I have to be picky: on Windows XP and Server 2003, whether the owner is Administrators or not when the creator is a member of Administrators is dependent on the setting of the "System objects: Default owner for objects created by members of the Administrators group" policy (under Computer ConfigurationWindows SettingsSecurity SettingsLocal PoliciesSecurity Options). On Windows XP, the default is ‘Object creator’; on Server 2003, it’s ‘Administrators group’.

    The reasoning I think is that the user should own their own files even if they’re a Computer Administrator. This works with the ACLs on their own profile folders, so that others can’t see this user’s data unless they choose to share. It’s particularly an issue for XP Home.

    The net effect is a little interesting for anyone, like me, who’s trying to reduce their privileges. Any software you installed while you were an administrator is owned by you, not by the Administrators group, and since the CREATOR OWNER SID typically has Full Control (inherited from Program Files), you actually have greater permissions than you normally would have. This limits the benefits of running as a normal user.

    I think I need a tool to change the owner on everything in Program Files!

  2. Anonymous says:

    This might help, Mike:

    Might be easist to run as an administrator and "takeown" them.

    Googling for "takeown" also shows a Perl script to automate taking ownership on Win2k. Bizarre! I guess we used to ship Perl scripts in the Resource Kit.

  3. Anonymous says:

    I’m not sure if I’m understanding you right, Mike, but it sounds like you’re adding and removing one account from the administrators group? I just use two seperate accounts and stuff mostly works fine, with the exception that I’ve yet to see virtually any games (even Microsoft-published ones, ironically) that were well-behaved enough to not require write to %ProgramFiles%.

    BTW, see takeown /R if you want to change the ownership of a directory tree and all it’s contents.

    As for the complexity of the security API — perhaps the basic principles behind it aren’t so bad, but *working* with NT security is certainly annoying. I’m not saying I have a better solution for a system as featured as NT, but rather I’m trying to justify why people always say it’s such a pain to do security things on NT.

    For instance, consider the steps involved with adding a ACL to an existing DACL given an account name. First, you have to translate the account name to a SID – first you need to call LookupAccountSid (potentially twice, if you don’t know that you can allocate a buffer of SECURITY_MAX_SID_SIZE length). Then you need to check the existing ACL to see if you have enough room in it to add your ACE; if not, you have to reallocate the whole thing. Then you have to insert the ACE at the right position in the ACL (remember that access denied ACEs must always come first, but you are responsible for inserting them before access allowed ACEs). You might just skip the checking for free space step and always reallocate the ACL to save yourself some trouble. Don’t forget to do error checking each step of the way, either.

    And if you are working with an SD stored in a file or in the registry, then you also have absolute <–> self-relative conversions to do, which means reallocating the entire security descriptor (and it’s individual components, if you’re doing self-relative to absolute) and putting it together again.

    Of course there are things like SDDL and the newer "high level" ACL/ACE APIs in Windows 2000 and later, but in the ISV world (at least most people I know) you generally don’t have the luxury of not supporting downlevel platforms.

    It all ends up to be very annoying to be writing several hundred lines of code just to set the security on some kernel object at program startup, or something like that.

  4. Anonymous says:

    Minor correction to Skywing’s post:

    LookupAccountName gets a SID for a principal name. LookupAccountSid takes a SID and fetches an account name.

Skip to main content